diff --git a/package.json b/package.json index 76213b62c..ec0c4d506 100644 --- a/package.json +++ b/package.json @@ -4586,6 +4586,15 @@ "advanced" ] }, + "github.copilot.chat.modelPickerVendorOrdering": { + "type": "boolean", + "default": false, + "markdownDescription": "%github.copilot.config.modelPickerVendorOrdering%", + "tags": [ + "experimental", + "onExp" + ] + }, "github.copilot.chat.searchSubagent.enabled": { "type": "boolean", "default": false, diff --git a/package.nls.json b/package.nls.json index 12b0eb6e5..2a7919530 100644 --- a/package.nls.json +++ b/package.nls.json @@ -474,6 +474,7 @@ "copilot.tools.runSubagent.description": "Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management.", "copilot.tools.searchSubagent.name": "Search Subagent", "copilot.tools.searchSubagent.description": "Launch an iterative search-focused subagent to find relevant code in your workspace.", + "github.copilot.config.modelPickerVendorOrdering": "Sort models in the model picker by vendor: OpenAI first, then Anthropic, then Gemini.", "github.copilot.config.searchSubagent.enabled": "Enable the search subagent tool for iterative code exploration in the workspace.", "github.copilot.config.searchSubagent.useAgenticProxy": "Use the agentic proxy for the search subagent tool.", "github.copilot.config.searchSubagent.model": "Model to use for the search subagent. When useAgenticProxy is enabled, defaults to 'agentic-search-v3'. Otherwise defaults to the main agent model.", diff --git a/src/extension/conversation/vscode-node/languageModelAccess.ts b/src/extension/conversation/vscode-node/languageModelAccess.ts index c341dd609..2d89b4c05 100644 --- a/src/extension/conversation/vscode-node/languageModelAccess.ts +++ b/src/extension/conversation/vscode-node/languageModelAccess.ts @@ -11,7 +11,7 @@ import { CopilotToken } from '../../../platform/authentication/common/copilotTok import { IBlockedExtensionService } from '../../../platform/chat/common/blockedExtensionService'; import { ChatFetchResponseType, ChatLocation, getErrorDetailsFromChatFetchError } from '../../../platform/chat/common/commonTypes'; import { getTextPart } from '../../../platform/chat/common/globalStringUtils'; -import { IConfigurationService } from '../../../platform/configuration/common/configurationService'; +import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService'; import { EmbeddingType, getWellKnownEmbeddingTypeInfo, IEmbeddingsComputer } from '../../../platform/embeddings/common/embeddingsComputer'; import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider'; import { CustomDataPartMimeTypes } from '../../../platform/endpoint/common/endpointTypes'; @@ -180,6 +180,7 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib @IVSCodeExtensionContext private readonly _vsCodeExtensionContext: IVSCodeExtensionContext, @IAutomodeService private readonly _automodeService: IAutomodeService, @IExperimentationService private readonly _expService: IExperimentationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); @@ -240,6 +241,21 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib const chatEndpoints = allEndpoints.filter(e => e.showInModelPicker || e.model === 'gpt-4o-mini'); const autoEndpoint = await this._automodeService.resolveAutoModeEndpoint(undefined, allEndpoints); chatEndpoints.push(autoEndpoint); + + // Experiment: sort endpoints by vendor priority (OpenAI, Anthropic, Gemini, others). + // Auto endpoint is always first via its category order. + if (this._configurationService.getExperimentBasedConfig(ConfigKey.Shared.ModelPickerVendorOrdering, this._expService)) { + const getVendorPriority = (e: IChatEndpoint): number => { + if (e instanceof AutoChatEndpoint) { return -1; } + const provider = e.modelProvider.toLowerCase(); + if (provider.includes('openai')) { return 0; } + if (provider.includes('anthropic')) { return 1; } + if (provider.includes('google')) { return 2; } + return 3; + }; + chatEndpoints.sort((a, b) => getVendorPriority(a) - getVendorPriority(b)); + } + let defaultChatEndpoint: IChatEndpoint; const defaultExpModel = this._expService.getTreatmentVariable('chat.defaultLanguageModel')?.replace('copilot/', ''); if (this._authenticationService.copilotToken?.isNoAuthUser || !defaultExpModel || defaultExpModel === AutoChatEndpoint.pseudoModelId) { diff --git a/src/platform/configuration/common/configurationService.ts b/src/platform/configuration/common/configurationService.ts index 77c0e1989..05b970400 100644 --- a/src/platform/configuration/common/configurationService.ts +++ b/src/platform/configuration/common/configurationService.ts @@ -642,6 +642,7 @@ export namespace ConfigKey { export const PromptFileContext = defineAndMigrateExpSetting('chat.advanced.promptFileContextProvider.enabled', 'chat.promptFileContextProvider.enabled', true); export const DefaultToolsGrouped = defineAndMigrateExpSetting('chat.advanced.tools.defaultToolsGrouped', 'chat.tools.defaultToolsGrouped', false); export const Gpt5AlternativePatch = defineAndMigrateExpSetting('chat.advanced.gpt5AlternativePatch', 'chat.gpt5AlternativePatch', false); + export const ModelPickerVendorOrdering = defineSetting('chat.modelPickerVendorOrdering', ConfigType.ExperimentBased, false); export const SearchSubagentToolEnabled = defineSetting('chat.searchSubagent.enabled', ConfigType.ExperimentBased, false); /** Use the agentic proxy for the search subagent tool */ export const SearchSubagentUseAgenticProxy = defineSetting('chat.searchSubagent.useAgenticProxy', ConfigType.ExperimentBased, false);