Skip to content

Commit 78acc29

Browse files
authored
Implement Auto Model (#415)
* Auto mode work * Implement auto mode * Auto mode!
1 parent 2616ab2 commit 78acc29

File tree

9 files changed

+176
-124
lines changed

9 files changed

+176
-124
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3667,7 +3667,7 @@
36673667
"@humanwhocodes/gitignore-to-minimatch": "1.0.2",
36683668
"@microsoft/tiktokenizer": "^1.0.10",
36693669
"@roamhq/mac-ca": "^1.0.7",
3670-
"@vscode/copilot-api": "^0.1.1",
3670+
"@vscode/copilot-api": "^0.1.3",
36713671
"@vscode/extension-telemetry": "^1.0.0",
36723672
"@vscode/l10n": "^0.0.18",
36733673
"@vscode/prompt-tsx": "^0.4.0-alpha.5",

src/extension/conversation/vscode-node/languageModelAccess.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import { IBlockedExtensionService } from '../../../platform/chat/common/blockedE
1111
import { ChatFetchResponseType, ChatLocation, getErrorDetailsFromChatFetchError } from '../../../platform/chat/common/commonTypes';
1212
import { getTextPart } from '../../../platform/chat/common/globalStringUtils';
1313
import { EmbeddingType, getWellKnownEmbeddingTypeInfo, IEmbeddingsComputer } from '../../../platform/embeddings/common/embeddingsComputer';
14+
import { AutoChatEndpoint, isAutoModeEnabled } from '../../../platform/endpoint/common/autoChatEndpoint';
15+
import { IAutomodeService } from '../../../platform/endpoint/common/automodeService';
1416
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
15-
import { AutoChatEndpoint, isAutoModeEnabled } from '../../../platform/endpoint/node/autoChatEndpoint';
1617
import { IEnvService } from '../../../platform/env/common/envService';
1718
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
1819
import { ILogService } from '../../../platform/log/common/logService';
@@ -25,6 +26,7 @@ import { BaseTokensPerCompletion } from '../../../platform/tokenizer/node/tokeni
2526
import { Emitter } from '../../../util/vs/base/common/event';
2627
import { Disposable, MutableDisposable } from '../../../util/vs/base/common/lifecycle';
2728
import { isDefined, isNumber, isString, isStringArray } from '../../../util/vs/base/common/types';
29+
import { generateUuid } from '../../../util/vs/base/common/uuid';
2830
import { localize } from '../../../util/vs/nls';
2931
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
3032
import { ExtensionMode } from '../../../vscodeTypes';
@@ -51,7 +53,9 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
5153
@IEndpointProvider private readonly _endpointProvider: IEndpointProvider,
5254
@IEmbeddingsComputer private readonly _embeddingsComputer: IEmbeddingsComputer,
5355
@IVSCodeExtensionContext private readonly _vsCodeExtensionContext: IVSCodeExtensionContext,
54-
@IExperimentationService private readonly _expService: IExperimentationService
56+
@IExperimentationService private readonly _expService: IExperimentationService,
57+
@IAutomodeService private readonly _automodeService: IAutomodeService,
58+
@IEnvService private readonly _envService: IEnvService
5559
) {
5660
super();
5761

@@ -96,11 +100,11 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
96100

97101
const models: vscode.LanguageModelChatInformation[] = [];
98102
const chatEndpoints = await this._endpointProvider.getAllChatEndpoints();
99-
if (isAutoModeEnabled(this._expService)) {
100-
chatEndpoints.push(this._instantiationService.createInstance(AutoChatEndpoint));
101-
}
102103

103104
const defaultChatEndpoint = chatEndpoints.find(e => e.isDefault) ?? await this._endpointProvider.getChatEndpoint('gpt-4.1') ?? chatEndpoints[0];
105+
if (isAutoModeEnabled(this._expService, this._envService)) {
106+
chatEndpoints.push(await this._automodeService.resolveAutoModeEndpoint(generateUuid(), chatEndpoints));
107+
}
104108
const seenFamilies = new Set<string>();
105109

106110
for (const endpoint of chatEndpoints) {
@@ -140,7 +144,7 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
140144

141145
const model: vscode.LanguageModelChatInformation = {
142146
id: endpoint.model,
143-
name: endpoint.name,
147+
name: endpoint.model === AutoChatEndpoint.id ? 'Auto' : endpoint.name,
144148
family: endpoint.family,
145149
description: modelDescription,
146150
cost: multiplierString,

src/extension/extension/vscode/services.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { EditSurvivalTrackerService, IEditSurvivalTrackerService } from '../../.
2727
import { IEmbeddingsComputer } from '../../../platform/embeddings/common/embeddingsComputer';
2828
import { RemoteEmbeddingsComputer } from '../../../platform/embeddings/common/remoteEmbeddingsComputer';
2929
import { ICombinedEmbeddingIndex, VSCodeCombinedIndexImpl } from '../../../platform/embeddings/common/vscodeIndex';
30+
import { AutomodeService, IAutomodeService } from '../../../platform/endpoint/common/automodeService';
3031
import { IEnvService } from '../../../platform/env/common/envService';
3132
import { EnvServiceImpl } from '../../../platform/env/vscode/envServiceImpl';
3233
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
@@ -107,6 +108,7 @@ export function registerServices(builder: IInstantiationServiceBuilder, extensio
107108
const isTestMode = extensionContext.extensionMode === ExtensionMode.Test;
108109

109110
builder.define(IInteractionService, new SyncDescriptor(InteractionService));
111+
builder.define(IAutomodeService, new SyncDescriptor(AutomodeService));
110112
builder.define(ICopilotTokenStore, new CopilotTokenStore());
111113
builder.define(IDebugOutputService, new DebugOutputServiceImpl());
112114
builder.define(IDialogService, new DialogServiceImpl());

src/extension/prompt/vscode-node/endpointProviderImpl.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
import { LanguageModelChat, type ChatRequest } from 'vscode';
77
import { IAuthenticationService } from '../../../platform/authentication/common/authentication';
88
import { ConfigKey, EMBEDDING_MODEL, IConfigurationService } from '../../../platform/configuration/common/configurationService';
9+
import { AutoChatEndpoint } from '../../../platform/endpoint/common/autoChatEndpoint';
10+
import { IAutomodeService } from '../../../platform/endpoint/common/automodeService';
911
import { ICAPIClientService } from '../../../platform/endpoint/common/capiClient';
1012
import { IDomainService } from '../../../platform/endpoint/common/domainService';
1113
import { ChatEndpointFamily, EmbeddingsEndpointFamily, IChatModelInformation, IEmbeddingModelInformation, IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
12-
import { AutoChatEndpoint, resolveAutoChatEndpoint } from '../../../platform/endpoint/node/autoChatEndpoint';
1314
import { CopilotChatEndpoint } from '../../../platform/endpoint/node/copilotChatEndpoint';
1415
import { EmbeddingEndpoint } from '../../../platform/endpoint/node/embeddingsEndpoint';
1516
import { IModelMetadataFetcher, ModelMetadataFetcher } from '../../../platform/endpoint/node/modelMetadataFetcher';
@@ -38,6 +39,7 @@ export class ProductionEndpointProvider implements IEndpointProvider {
3839
@IDomainService domainService: IDomainService,
3940
@ICAPIClientService capiClientService: ICAPIClientService,
4041
@IFetcherService fetcher: IFetcherService,
42+
@IAutomodeService private readonly _autoModeService: IAutomodeService,
4143
@IExperimentationService private readonly _expService: IExperimentationService,
4244
@ITelemetryService private readonly _telemetryService: ITelemetryService,
4345
@ILogService private readonly _logService: ILogService,
@@ -140,7 +142,9 @@ export class ProductionEndpointProvider implements IEndpointProvider {
140142
if (experimentModelConfig && model && model.id === experimentModelConfig.id) {
141143
endpoint = (await this.getAllChatEndpoints()).find(e => e.model === experimentModelConfig.selected) || await this.getChatEndpoint('gpt-4.1');
142144
} else if (model && model.vendor === 'copilot' && model.id === AutoChatEndpoint.id) {
143-
return resolveAutoChatEndpoint(this, this._expService, (requestOrFamilyOrModel as ChatRequest)?.prompt);
145+
// TODO @lramos15 - This may be the ugliest cast I've ever seen but our types seem to be incorrect
146+
const conversationdId = ((requestOrFamilyOrModel as ChatRequest).toolInvocationToken as { sessionId: string }).sessionId || 'unknown';
147+
return this._autoModeService.getCachedAutoEndpoint(conversationdId) || this._autoModeService.resolveAutoModeEndpoint(conversationdId, await this.getAllChatEndpoints());
144148
} else if (model && model.vendor === 'copilot') {
145149
let modelMetadata = await this._modelFetcher.getChatModelFromApiModel(model);
146150
if (modelMetadata) {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import type { RequestMetadata } from '@vscode/copilot-api';
7+
import { ChatMessage } from '@vscode/prompt-tsx/dist/base/output/rawTypes';
8+
import type { CancellationToken } from 'vscode';
9+
import { ITokenizer, TokenizerType } from '../../../util/common/tokenizer';
10+
import { AsyncIterableObject } from '../../../util/vs/base/common/async';
11+
import { IntentParams, Source } from '../../chat/common/chatMLFetcher';
12+
import { ChatLocation, ChatResponse } from '../../chat/common/commonTypes';
13+
import { IEnvService } from '../../env/common/envService';
14+
import { ILogService } from '../../log/common/logService';
15+
import { FinishedCallback, OptionalChatRequestParams } from '../../networking/common/fetch';
16+
import { Response } from '../../networking/common/fetcherService';
17+
import { IChatEndpoint } from '../../networking/common/networking';
18+
import { ChatCompletion } from '../../networking/common/openai';
19+
import { IExperimentationService } from '../../telemetry/common/nullExperimentationService';
20+
import { ITelemetryService, TelemetryProperties } from '../../telemetry/common/telemetry';
21+
import { TelemetryData } from '../../telemetry/common/telemetryData';
22+
23+
/**
24+
* This endpoint represents the "Auto" model in the model picker.
25+
* It just effectively wraps a different endpoint and adds the auto stuff on top
26+
*/
27+
export class AutoChatEndpoint implements IChatEndpoint {
28+
public static readonly id = 'auto';
29+
maxOutputTokens: number = this._wrappedEndpoint.maxOutputTokens;
30+
model: string = AutoChatEndpoint.id;
31+
supportsToolCalls: boolean = this._wrappedEndpoint.supportsToolCalls;
32+
supportsVision: boolean = this._wrappedEndpoint.supportsVision;
33+
supportsPrediction: boolean = this._wrappedEndpoint.supportsPrediction;
34+
showInModelPicker: boolean = true;
35+
isPremium?: boolean | undefined = this._wrappedEndpoint.isPremium;
36+
multiplier?: number | undefined = this._wrappedEndpoint.multiplier;
37+
restrictedToSkus?: string[] | undefined = this._wrappedEndpoint.restrictedToSkus;
38+
isDefault: boolean = this._wrappedEndpoint.isDefault;
39+
isFallback: boolean = this._wrappedEndpoint.isFallback;
40+
policy: 'enabled' | { terms: string } = this._wrappedEndpoint.policy;
41+
urlOrRequestMetadata: string | RequestMetadata = this._wrappedEndpoint.urlOrRequestMetadata;
42+
modelMaxPromptTokens: number = this._wrappedEndpoint.modelMaxPromptTokens;
43+
name: string = this._wrappedEndpoint.name;
44+
version: string = this._wrappedEndpoint.version;
45+
family: string = this._wrappedEndpoint.family;
46+
tokenizer: TokenizerType = this._wrappedEndpoint.tokenizer;
47+
48+
constructor(
49+
private readonly _wrappedEndpoint: IChatEndpoint,
50+
private readonly _sessionToken: string
51+
) { }
52+
53+
getExtraHeaders(): Record<string, string> {
54+
return {
55+
...(this._wrappedEndpoint.getExtraHeaders?.() || {}),
56+
'Copilot-Session-Token': this._sessionToken
57+
};
58+
}
59+
60+
processResponseFromChatEndpoint(telemetryService: ITelemetryService, logService: ILogService, response: Response, expectedNumChoices: number, finishCallback: FinishedCallback, telemetryData: TelemetryData, cancellationToken?: CancellationToken): Promise<AsyncIterableObject<ChatCompletion>> {
61+
return this._wrappedEndpoint.processResponseFromChatEndpoint(telemetryService, logService, response, expectedNumChoices, finishCallback, telemetryData, cancellationToken);
62+
}
63+
acceptChatPolicy(): Promise<boolean> {
64+
return this._wrappedEndpoint.acceptChatPolicy();
65+
}
66+
cloneWithTokenOverride(modelMaxPromptTokens: number): IChatEndpoint {
67+
return this._wrappedEndpoint.cloneWithTokenOverride(modelMaxPromptTokens);
68+
}
69+
acquireTokenizer(): ITokenizer {
70+
return this._wrappedEndpoint.acquireTokenizer();
71+
}
72+
73+
async makeChatRequest(debugName: string, messages: ChatMessage[], finishedCb: FinishedCallback | undefined, token: CancellationToken, location: ChatLocation, source?: Source, requestOptions?: Omit<OptionalChatRequestParams, 'n'>, userInitiatedRequest?: boolean, telemetryProperties?: TelemetryProperties, intentParams?: IntentParams): Promise<ChatResponse> {
74+
return this._wrappedEndpoint.makeChatRequest(debugName, messages, finishedCb, token, location, source, requestOptions, userInitiatedRequest, telemetryProperties, intentParams);
75+
}
76+
}
77+
78+
/**
79+
* Checks if the auto chat mode is enabled.
80+
* @param expService The experimentation service to use to check if the auto mode is enabled
81+
* @param envService The environment service to use to check if the auto mode is enabled
82+
* @returns True if the auto mode is enabled, false otherwise
83+
*/
84+
export function isAutoModeEnabled(expService: IExperimentationService, envService: IEnvService): boolean {
85+
return !!expService.getTreatmentVariable<boolean>('vscode', 'copilotchatcapiautomode') || envService.isPreRelease();
86+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { RequestType } from '@vscode/copilot-api';
7+
import { createServiceIdentifier } from '../../../util/common/services';
8+
import { IAuthenticationService } from '../../authentication/common/authentication';
9+
import { IChatEndpoint } from '../../networking/common/networking';
10+
import { AutoChatEndpoint } from './autoChatEndpoint';
11+
import { ICAPIClientService } from './capiClient';
12+
13+
interface AutoModeAPIResponse {
14+
available_models: string[];
15+
selected_model: string;
16+
session_token: string;
17+
}
18+
19+
export const IAutomodeService = createServiceIdentifier<IAutomodeService>('IAutomodeService');
20+
21+
export interface IAutomodeService {
22+
readonly _serviceBrand: undefined;
23+
24+
getCachedAutoEndpoint(conversationId: string): IChatEndpoint | undefined;
25+
26+
resolveAutoModeEndpoint(conversationId: string, knownEndpoints: IChatEndpoint[]): Promise<IChatEndpoint>;
27+
}
28+
29+
export class AutomodeService implements IAutomodeService {
30+
readonly _serviceBrand: undefined;
31+
private readonly _autoModelCache: Map<string, IChatEndpoint> = new Map();
32+
33+
constructor(
34+
@ICAPIClientService private readonly _capiClientService: ICAPIClientService,
35+
@IAuthenticationService private readonly _authService: IAuthenticationService
36+
) {
37+
this._serviceBrand = undefined;
38+
}
39+
40+
getCachedAutoEndpoint(conversationId: string): IChatEndpoint | undefined {
41+
return this._autoModelCache.get(conversationId);
42+
}
43+
44+
async resolveAutoModeEndpoint(conversationId: string, knownEndpoints: IChatEndpoint[]): Promise<IChatEndpoint> {
45+
if (this.getCachedAutoEndpoint(conversationId)) {
46+
return this.getCachedAutoEndpoint(conversationId)!;
47+
}
48+
const authToken = (await this._authService.getCopilotToken()).token;
49+
const response = await this._capiClientService.makeRequest<Response>({
50+
json: {
51+
"auto_mode": { "model_hints": ["auto"] },
52+
},
53+
headers: {
54+
'Content-Type': 'application/json',
55+
'Authorization': `Bearer ${authToken}`
56+
},
57+
method: 'POST'
58+
}, { type: RequestType.AutoModels });
59+
const data: AutoModeAPIResponse = await response.json() as AutoModeAPIResponse;
60+
const selectedModel = knownEndpoints.find(e => e.model === data.selected_model) || knownEndpoints[0];
61+
const autoEndpoint = new AutoChatEndpoint(selectedModel, data.session_token);
62+
this._autoModelCache.set(conversationId, autoEndpoint);
63+
return autoEndpoint;
64+
}
65+
}

0 commit comments

Comments
 (0)