Path: blob/main/extensions/copilot/src/platform/endpoint/common/chatModelCapabilities.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { LanguageModelChat } from 'vscode';6import { getCachedSha256Hash } from '../../../util/common/crypto';7import { ServicesAccessor } from '../../../util/vs/platform/instantiation/common/instantiation';8import { ConfigKey, IConfigurationService } from '../../configuration/common/configurationService';9import type { IChatEndpoint } from '../../networking/common/networking';10import { IExperimentationService } from '../../telemetry/common/nullExperimentationService';1112const HIDDEN_MODEL_A_HASHES = [13'a99dd17dfee04155d863268596b7f6dd36d0a6531cd326348dbe7416142a21a3',14'6b0f165d0590bf8d508540a796b4fda77bf6a0a4ed4e8524d5451b1913100a95'15];161718const HIDDEN_MODEL_B_HASHES = [19'1f48b3271e760c69ab2b17dcae5f5c661fa5b644c5976a8a99b23e05ae3cb6d6',20'ffc50c70661c227edf8daae6f8dbed2dd0645386c12d43bc7fc44da166e043bd',21'257c934076307881132be702a901618969591f0e11e1df51b22b1d4010f0a0d0',22];2324const VSC_MODEL_HASHES_A = [25'6db59e9bfe6e2ce608c0ee0ade075c64e4d054f05305e3034481234703381bb5',26'd7b81f23b6ab47d41130359bc203a6c653bba461b3da0185406353ce2b3abfa7',27];2829const VSC_MODEL_HASHES_B = [30'6b0f165d0590bf8d508540a796b4fda77bf6a0a4ed4e8524d5451b1913100a95',31'1cdd4febbc7ee6b1abe0fbdd42217744c5912c79366db4befd91698b46c40a3c',32];3334const VSC_MODEL_HASHES_C = [35'0425aeda24d2fd93e2a879c4d813e4f3997aa444f1f4a633241236f9f773df73',36];3738const VSC_MODEL_HASHES_D = [39'e82ff0e2d4e4bae1f012dc599d520f8d61becfc4762f3717577b270be199db92',40];414243// subset to allow replace string instead of apply patch.44const VSC_MODEL_HASHES_EDIT_TOOL_SET = [45'6db59e9bfe6e2ce608c0ee0ade075c64e4d054f05305e3034481234703381bb5',46'6b0f165d0590bf8d508540a796b4fda77bf6a0a4ed4e8524d5451b1913100a95',47'd7b81f23b6ab47d41130359bc203a6c653bba461b3da0185406353ce2b3abfa7',48'1cdd4febbc7ee6b1abe0fbdd42217744c5912c79366db4befd91698b46c40a3c',49'0425aeda24d2fd93e2a879c4d813e4f3997aa444f1f4a633241236f9f773df73',50'e82ff0e2d4e4bae1f012dc599d520f8d61becfc4762f3717577b270be199db92',51];5253const HIDDEN_MODEL_E_HASHES: string[] = [54'6013de0381f648b7f21518885c02b40b7583adfb33c6d9b64d3aed52c3934798'55];5657const HIDDEN_MODEL_F_HASHES: string[] = [58'ab45e8474269b026f668d49860b36850122e18a50d5ea38f3fefdae08261865c',59'9542d5c077c2bc379f92be32272b14be8b94a8841323465db0d5b3d6f4f0dab0',60];6162const HIDDEN_MODEL_J_HASHES: string[] = [63'0a4346f806b28b3ce94905c3ac56fcd5ee2337d8613161696aba52eb0c3551cc',64'2a7b79b0151aa44a0abee17adc0e18df1c07d8d15d7affa989c3b3afb6bee0a0',65'f3c2984127dd2db50a555194925ca0d55c3c7b676e889c9406b2e6875a67e29c',66'5a81e6aa7556585ba7c569881d1103683adc9e0124ff7952df423afba2f167b5',67];6869const HIDDEN_MODEL_K_HASH = 'a62e299160a1075d9973c28a7aa77f446c21c09887c7aa65c11022918cf83eda';7071const HIDDEN_FAMILY_H_HASHES: string[] = [72'70fcded3f255d368e868cc807d8838a62108bfa5c86ce7d37966f58cda229e33',73];7475function getModelId(model: LanguageModelChat | IChatEndpoint): string {76return 'id' in model ? model.id : model.model;77}7879export function isHiddenModelA(model: LanguageModelChat | IChatEndpoint) {80const h = getCachedSha256Hash(model.family);81return HIDDEN_MODEL_A_HASHES.includes(h);82}8384export function isHiddenModelB(model: LanguageModelChat | IChatEndpoint | string) {85const h = getCachedSha256Hash(typeof model === 'string' ? model : model.family);86return HIDDEN_MODEL_B_HASHES.includes(h);87}888990export function isHiddenModelE(model: LanguageModelChat | IChatEndpoint) {91const h = getCachedSha256Hash(model.family);92return HIDDEN_MODEL_E_HASHES.includes(h);93}9495export function isHiddenModelF(model: LanguageModelChat | IChatEndpoint) {96const h = getCachedSha256Hash(model.family);97return HIDDEN_MODEL_F_HASHES.includes(h);98}99100export function isHiddenModelG(model: LanguageModelChat | IChatEndpoint | string) {101const family_hash = getCachedSha256Hash(typeof model === 'string' ? model : model.family);102return family_hash === '3ae755cc6122a54cc873e3ba2bd8703883b4a711d1af2707ef00f2c2c963ee8d';103}104105export function isHiddenFamilyH(model: LanguageModelChat | IChatEndpoint) {106const family_hash = getCachedSha256Hash(model.family);107return HIDDEN_FAMILY_H_HASHES.includes(family_hash);108}109110export function isHiddenModelK(model: LanguageModelChat | IChatEndpoint) {111const h = getCachedSha256Hash(model.family);112return h === HIDDEN_MODEL_K_HASH;113}114115116export function isGpt54(model: LanguageModelChat | IChatEndpoint | string) {117const h = getCachedSha256Hash(typeof model === 'string' ? model : model.family);118const family = typeof model === 'string' ? model : model.family;119return family.startsWith('gpt-5.4') || HIDDEN_MODEL_J_HASHES.includes(h);120}121122export function isGpt54ConcisePromptExp(123accessor: ServicesAccessor,124model: LanguageModelChat | IChatEndpoint | string,125) {126const configurationService = accessor.get(IConfigurationService);127const experimentationService = accessor.get(IExperimentationService);128return isGpt54(model) && configurationService.getExperimentBasedConfig(ConfigKey.EnableGpt54ConcisePromptExp, experimentationService);129}130131export function isGpt54LargePromptExp(132accessor: ServicesAccessor,133model: LanguageModelChat | IChatEndpoint | string,134) {135const configurationService = accessor.get(IConfigurationService);136const experimentationService = accessor.get(IExperimentationService);137return isGpt54(model) && configurationService.getExperimentBasedConfig(ConfigKey.EnableGpt54LargePromptExp, experimentationService);138}139140141export function isGpt53Codex(model: LanguageModelChat | IChatEndpoint | string) {142const family = typeof model === 'string' ? model : model.family;143return family.startsWith('gpt-5.3-codex');144}145146export function isVSCModelA(model: LanguageModelChat | IChatEndpoint) {147148const ID_hash = getCachedSha256Hash(getModelId(model));149const family_hash = getCachedSha256Hash(model.family);150return VSC_MODEL_HASHES_A.includes(ID_hash) || VSC_MODEL_HASHES_A.includes(family_hash);151}152153export function isVSCModelB(model: LanguageModelChat | IChatEndpoint) {154const ID_hash = getCachedSha256Hash(getModelId(model));155const family_hash = getCachedSha256Hash(model.family);156return VSC_MODEL_HASHES_B.includes(ID_hash) || VSC_MODEL_HASHES_B.includes(family_hash);157}158159export function isVSCModelReplaceStringSet(model: LanguageModelChat | IChatEndpoint) {160const ID_hash = getCachedSha256Hash(getModelId(model));161const family_hash = getCachedSha256Hash(model.family);162return VSC_MODEL_HASHES_EDIT_TOOL_SET.includes(ID_hash) || VSC_MODEL_HASHES_EDIT_TOOL_SET.includes(family_hash);163}164165export function isVSCModelC(model: LanguageModelChat | IChatEndpoint) {166const ID_hash = getCachedSha256Hash(getModelId(model));167const family_hash = getCachedSha256Hash(model.family);168return VSC_MODEL_HASHES_C.includes(ID_hash) || VSC_MODEL_HASHES_C.includes(family_hash);169}170171export function isVSCModelD(model: LanguageModelChat | IChatEndpoint) {172const ID_hash = getCachedSha256Hash(getModelId(model));173const family_hash = getCachedSha256Hash(model.family);174return VSC_MODEL_HASHES_D.includes(ID_hash) || VSC_MODEL_HASHES_D.includes(family_hash);175}176177export function isGpt52CodexFamily(model: LanguageModelChat | IChatEndpoint | string): boolean {178const family = typeof model === 'string' ? model : model.family;179return family === 'gpt-5.2-codex';180}181182export function isGpt52Family(model: LanguageModelChat | IChatEndpoint | string): boolean {183const family = typeof model === 'string' ? model : model.family;184return family === 'gpt-5.2';185}186187/**188* Returns whether the instructions should be given in a user message instead189* of a system message when talking to the model.190*/191export function modelPrefersInstructionsInUserMessage(modelFamily: string) {192return modelFamily.includes('claude-3.5-sonnet');193}194195/**196* Returns whether the instructions should be presented after the history197* for the given model.198*/199export function modelPrefersInstructionsAfterHistory(modelFamily: string) {200return modelFamily.includes('claude-3.5-sonnet');201}202203/**204* Model supports apply_patch as an edit tool.205*/206export function modelSupportsApplyPatch(model: LanguageModelChat | IChatEndpoint): boolean {207// only using replace string as edit tool, disable apply_patch for VSC Models208if (isVSCModelReplaceStringSet(model)) {209return false;210}211return (model.family.startsWith('gpt') && !model.family.includes('gpt-4o'))212|| model.family === 'o4-mini'213|| isGpt52CodexFamily(model.family)214|| isGpt53Codex(model.family)215|| isVSCModelA(model)216|| isVSCModelB(model)217|| isGpt52Family(model.family)218|| isGpt54(model)219|| isHiddenModelB(model);220}221222/**223* Model prefers JSON notebook representation.224*/225export function modelPrefersJsonNotebookRepresentation(model: LanguageModelChat | IChatEndpoint): boolean {226return (model.family.startsWith('gpt') && !model.family.includes('gpt-4o'))227|| model.family === 'o4-mini'228|| isGpt52CodexFamily(model.family)229|| isGpt53Codex(model.family)230|| isGpt52Family(model.family)231|| isGpt54(model)232|| isHiddenModelB(model);233}234235/**236* Model supports replace_string_in_file as an edit tool.237*/238export function modelSupportsReplaceString(model: LanguageModelChat | IChatEndpoint): boolean {239return isGeminiFamily(model) || model.family.includes('grok-code') || modelSupportsMultiReplaceString(model) || isHiddenModelF(model) || isMinimaxFamily(model) || isHiddenFamilyH(model);240}241242/**243* Model supports multi_replace_string_in_file as an edit tool.244*/245export function modelSupportsMultiReplaceString(model: LanguageModelChat | IChatEndpoint): boolean {246return isAnthropicFamily(model) || isHiddenModelE(model) || isVSCModelReplaceStringSet(model) || isMinimaxFamily(model) || isHiddenFamilyH(model);247}248249/**250* The model is capable of using replace_string_in_file exclusively,251* without needing insert_edit_into_file.252*/253export function modelCanUseReplaceStringExclusively(model: LanguageModelChat | IChatEndpoint): boolean {254return isAnthropicFamily(model) || model.family.includes('grok-code') || isHiddenModelE(model) || model.family.toLowerCase().includes('gemini-3') || isVSCModelReplaceStringSet(model) || isHiddenModelF(model) || isMinimaxFamily(model) || isHiddenFamilyH(model);255}256257/**258* We should attempt to automatically heal incorrect edits the model may emit.259* @note whether this is respected is currently controlled via EXP260*/261export function modelShouldUseReplaceStringHealing(model: LanguageModelChat | IChatEndpoint) {262return model.family.includes('gemini-2');263}264265/**266* The model can accept image urls as the `image_url` parameter in mcp tool results.267*/268export function modelCanUseMcpResultImageURL(model: LanguageModelChat | IChatEndpoint): boolean {269return !isAnthropicFamily(model) && !isHiddenModelE(model);270}271272/**273* The model can accept image urls as the `image_url` parameter in requests.274*/275export function modelCanUseImageURL(model: LanguageModelChat | IChatEndpoint): boolean {276return true;277}278279/**280* The model supports native PDF document processing via document content parts.281*/282export function modelSupportsPDFDocuments(model: LanguageModelChat | IChatEndpoint): boolean {283return isAnthropicFamily(model);284}285286/**287* The model is capable of using apply_patch as an edit tool exclusively,288* without needing insert_edit_into_file.289*/290export function modelCanUseApplyPatchExclusively(model: LanguageModelChat | IChatEndpoint): boolean {291// only using replace string as edit tool, disable apply_patch for VSC Models292if (isVSCModelReplaceStringSet(model)) {293return false;294}295return isGpt5PlusFamily(model) || isVSCModelA(model) || isVSCModelB(model);296}297298/**299* Whether, when replace_string and insert_edit tools are both available,300* verbiage should be added in the system prompt directing the model to prefer301* replace_string.302*/303export function modelNeedsStrongReplaceStringHint(model: LanguageModelChat | IChatEndpoint): boolean {304return isGeminiFamily(model) || isHiddenModelF(model);305}306307/**308* Model can take the simple, modern apply_patch instructions.309*/310export function modelSupportsSimplifiedApplyPatchInstructions(model: LanguageModelChat | IChatEndpoint): boolean {311return isGpt5PlusFamily(model) || isVSCModelA(model) || isVSCModelB(model);312}313314export function isAnthropicFamily(model: LanguageModelChat | IChatEndpoint): boolean {315return model.family.startsWith('claude') || model.family.startsWith('Anthropic') || isHiddenModelG(model);316}317318export function isGeminiFamily(model: LanguageModelChat | IChatEndpoint | string): boolean {319const family = typeof model === 'string' ? model : model.family;320return family.toLowerCase().startsWith('gemini') || getCachedSha256Hash(family) === HIDDEN_MODEL_K_HASH;321}322323export function isMinimaxFamily(model: LanguageModelChat | IChatEndpoint): boolean {324return model.family.toLowerCase().includes('minimax');325}326327export function isGpt5PlusFamily(model: LanguageModelChat | IChatEndpoint | string | undefined): boolean {328if (!model) {329return false;330}331332const family = typeof model === 'string' ? model : model.family;333return !!family.startsWith('gpt-5');334}335336/**337* Matches gpt-5-codex, gpt-5.1-codex, gpt-5.1-codex-mini, and any future models in this general family338*/339export function isGptCodexFamily(model: LanguageModelChat | IChatEndpoint | string | undefined): boolean {340if (!model) {341return false;342}343344const family = typeof model === 'string' ? model : model.family;345return (!!family.startsWith('gpt-') && family.includes('-codex'));346}347348/**349* GPT-5, -mini, -codex, not 5.1+350*/351export function isGpt5Family(model: LanguageModelChat | IChatEndpoint | string | undefined): boolean {352if (!model) {353return false;354}355356const family = typeof model === 'string' ? model : model.family;357return family === 'gpt-5' || family === 'gpt-5-mini' || family === 'gpt-5-codex';358}359360export function isGptFamily(model: LanguageModelChat | IChatEndpoint | string | undefined): boolean {361if (!model) {362return false;363}364365const family = typeof model === 'string' ? model : model.family;366return !!family.startsWith('gpt-');367}368369/**370* Any GPT-5.1+ model371*/372export function isGpt51Family(model: LanguageModelChat | IChatEndpoint | string | undefined): boolean {373if (!model) {374return false;375}376377const family = typeof model === 'string' ? model : model.family;378return !!family.startsWith('gpt-5.1');379}380381/**382* This takes a sync shortcut and should only be called when a model hash would have already been computed while rendering the prompt.383*/384export function getVerbosityForModelSync(model: IChatEndpoint): 'low' | 'medium' | 'high' | undefined {385if (model.family === 'gpt-5.1' || model.family === 'gpt-5-mini') {386return 'low';387}388389return undefined;390}391392/**393* Tool search is supported by:394* - Claude Sonnet 4.5 (claude-sonnet-4-5-* or claude-sonnet-4.5-*)395* - Claude Sonnet 4.6 (claude-sonnet-4-6-* or claude-sonnet-4.6-*)396* - Claude Opus 4.5 (claude-opus-4-5-* or claude-opus-4.5-*)397* - Claude Opus 4.6 (claude-opus-4-6-* or claude-opus-4.6-*)398* - Claude Opus 4.7 (claude-opus-4-7-* or claude-opus-4.7-*)399* - OpenAI gpt-5.4/gpt-5.5, but only when the `ResponsesApiToolSearchEnabled` setting is enabled400*/401export function modelSupportsToolSearch(modelId: string, configurationService?: IConfigurationService, experimentationService?: IExperimentationService): boolean {402const normalized = modelId.toLowerCase().replace(/\./g, '-');403if (isResponsesApiToolSearchModelId(normalized)) {404return !!configurationService && !!experimentationService && isResponsesApiToolSearchEnabled(modelId, configurationService, experimentationService);405}406407return normalized.startsWith('claude-sonnet-4-5') ||408normalized.startsWith('claude-sonnet-4-6') ||409normalized.startsWith('claude-opus-4-5') ||410normalized.startsWith('claude-opus-4-6') ||411normalized.startsWith('claude-opus-4-7') ||412isHiddenModelG(modelId);413}414415function isResponsesApiToolSearchModelId(normalizedModelId: string): boolean {416return normalizedModelId === 'gpt-5-4' || normalizedModelId === 'gpt-5-5';417}418419export function isResponsesApiToolSearchEnabled(420endpoint: IChatEndpoint | string,421configurationService: IConfigurationService,422experimentationService: IExperimentationService,423): boolean {424const modelId = typeof endpoint === 'string' ? endpoint : endpoint.model;425const normalized = modelId.toLowerCase().replace(/\./g, '-');426return isResponsesApiToolSearchModelId(normalized) && configurationService.getExperimentBasedConfig(ConfigKey.ResponsesApiToolSearchEnabled, experimentationService);427}428429/**430* Context editing is supported by:431* - Claude Haiku 4.5 (claude-haiku-4-5-* or claude-haiku-4.5-*)432* - Claude Sonnet 4.6 (claude-sonnet-4-6-* or claude-sonnet-4.6-*)433* - Claude Sonnet 4.5 (claude-sonnet-4-5-* or claude-sonnet-4.5-*)434* - Claude Sonnet 4 (claude-sonnet-4-*)435* - Claude Opus 4.6 (claude-opus-4-6-* or claude-opus-4.6-*)436* - Claude Opus 4.5 (claude-opus-4-5-* or claude-opus-4.5-*)437* - Claude Opus 4.1 (claude-opus-4-1-* or claude-opus-4.1-*)438* - Claude Opus 4 (claude-opus-4-*)439* Provider-agnostic: add additional model prefixes here as other providers adopt context editing.440*/441export function modelSupportsContextEditing(modelId: string): boolean {442const normalized = modelId.toLowerCase().replace(/\./g, '-');443// The 1M context variant doesn't need context editing444if (normalized.includes('1m')) {445return false;446}447return normalized.startsWith('claude-haiku-4-5') ||448normalized.startsWith('claude-sonnet-4-6') ||449normalized.startsWith('claude-sonnet-4-5') ||450normalized.startsWith('claude-sonnet-4') ||451normalized.startsWith('claude-opus-4-6') ||452normalized.startsWith('claude-opus-4-5') ||453normalized.startsWith('claude-opus-4-1') ||454normalized.startsWith('claude-opus-4');455}456457458