From cb3e67df04598b7250876d490907fe9d044fed74 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 16 Jun 2026 16:41:29 -0700 Subject: [PATCH] Unify reasoning effort strings for agent host (#321674) --- .../common/languageModelAccess.ts | 20 ++++++- .../agentHost/common/claudeModelConfig.ts | 14 +---- .../agentHost/common/reasoningEffort.ts | 58 +++++++++++++++++++ .../agentHost/node/codex/codexAgent.ts | 17 ++---- .../agentHost/node/copilot/copilotAgent.ts | 14 +---- .../test/common/claudeModelConfig.test.ts | 4 ++ .../agentHost/test/node/claudeAgent.test.ts | 3 + 7 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 src/vs/platform/agentHost/common/reasoningEffort.ts diff --git a/extensions/copilot/src/extension/conversation/common/languageModelAccess.ts b/extensions/copilot/src/extension/conversation/common/languageModelAccess.ts index 47debb20bee..872e11cda42 100644 --- a/extensions/copilot/src/extension/conversation/common/languageModelAccess.ts +++ b/extensions/copilot/src/extension/conversation/common/languageModelAccess.ts @@ -48,6 +48,24 @@ export function getReasoningEffortDescription(level: string): string { } } +/** + * Returns the localized, title-cased picker label for a given + * reasoning-effort `level`. Centralised here so every provider surfaces the + * same wording. Falls back to capitalizing an unknown value. + */ +export function getReasoningEffortLabel(level: string): string { + switch (level) { + case 'none': return l10n.t('None'); + case 'minimal': return l10n.t('Minimal'); + case 'low': return l10n.t('Low'); + case 'medium': return l10n.t('Medium'); + case 'high': return l10n.t('High'); + case 'xhigh': return l10n.t('Extra High'); + case 'max': return l10n.t('Max'); + default: return level.charAt(0).toUpperCase() + level.slice(1); + } +} + /** * Builds the `reasoningEffort` property descriptor for a model's * {@link LanguageModelConfigurationSchema}. Centralises the default-selection @@ -59,7 +77,7 @@ export function buildReasoningEffortSchemaProperty(effortLevels: readonly string type: 'string', title: l10n.t('Thinking Effort'), enum: effortLevels, - enumItemLabels: effortLevels.map(level => level.charAt(0).toUpperCase() + level.slice(1)), + enumItemLabels: effortLevels.map(getReasoningEffortLabel), enumDescriptions: effortLevels.map(getReasoningEffortDescription), default: pickDefaultReasoningEffort(effortLevels, family), group: 'navigation', diff --git a/src/vs/platform/agentHost/common/claudeModelConfig.ts b/src/vs/platform/agentHost/common/claudeModelConfig.ts index 0887b340db4..c98640bc719 100644 --- a/src/vs/platform/agentHost/common/claudeModelConfig.ts +++ b/src/vs/platform/agentHost/common/claudeModelConfig.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../nls.js'; +import { getReasoningEffortDescription, getReasoningEffortLabel } from './reasoningEffort.js'; import type { ConfigSchema, ModelSelection } from './state/protocol/state.js'; /** @@ -80,16 +81,6 @@ export function isClaudeEffortLevel(value: string): value is ClaudeEffortLevel { return (CLAUDE_EFFORT_LEVELS as readonly string[]).includes(value); } -function labelForClaudeEffort(level: ClaudeEffortLevel): string { - switch (level) { - case 'low': return localize('claude.modelThinkingLevel.low', "Low"); - case 'medium': return localize('claude.modelThinkingLevel.medium', "Medium"); - case 'high': return localize('claude.modelThinkingLevel.high', "High"); - case 'xhigh': return localize('claude.modelThinkingLevel.xhigh', "Extra High"); - case 'max': return localize('claude.modelThinkingLevel.max', "Max"); - } -} - /** * Synthesize the per-model `configSchema` advertising the `thinkingLevel` * picker entry on Claude models that support adaptive thinking. Mirror of @@ -124,7 +115,8 @@ export function createClaudeThinkingLevelSchema(supportedEfforts: readonly Claud title: localize('claude.modelThinkingLevel.title', "Thinking Level"), description: localize('claude.modelThinkingLevel.description', "Controls how much reasoning effort Claude uses."), enum: [...supportedEfforts], - enumLabels: supportedEfforts.map(labelForClaudeEffort), + enumLabels: supportedEfforts.map(getReasoningEffortLabel), + enumDescriptions: supportedEfforts.map(effort => getReasoningEffortDescription(effort) ?? ''), ...(defaultEffort !== undefined ? { default: defaultEffort } : {}), }, }, diff --git a/src/vs/platform/agentHost/common/reasoningEffort.ts b/src/vs/platform/agentHost/common/reasoningEffort.ts new file mode 100644 index 00000000000..08f56ee0fdb --- /dev/null +++ b/src/vs/platform/agentHost/common/reasoningEffort.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from '../../../nls.js'; + +/** + * Union of every reasoning-effort / thinking-level value surfaced by any + * agent-host provider. Individual providers expose a subset: + * - Codex: `'minimal' | 'low' | 'medium' | 'high'` + * - Copilot / Claude: `'low' | 'medium' | 'high' | 'xhigh' | 'max'` + * + * The label/description helpers below are the single source of truth for + * the localized picker strings so every provider renders the same value + * consistently. + */ +export type ReasoningEffortLevel = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'; + +/** + * Localized, title-cased picker label for a reasoning-effort value. + * Falls back to capitalizing an unrecognized value so a newly-introduced + * effort tier never surfaces raw (e.g. lowercase `'max'`). + */ +export function getReasoningEffortLabel(level: string): string { + switch (level) { + case 'none': return localize('reasoningEffort.none', "None"); + case 'minimal': return localize('reasoningEffort.minimal', "Minimal"); + case 'low': return localize('reasoningEffort.low', "Low"); + case 'medium': return localize('reasoningEffort.medium', "Medium"); + case 'high': return localize('reasoningEffort.high', "High"); + case 'xhigh': return localize('reasoningEffort.xhigh', "Extra High"); + case 'max': return localize('reasoningEffort.max', "Max"); + default: return level.charAt(0).toUpperCase() + level.slice(1); + } +} + +/** + * Localized description for a reasoning-effort value, shown beneath the + * label in the picker. Returns `undefined` for an unrecognized value so + * callers can omit the description rather than show an empty string. + * + * Wording mirrors the canonical extension helper `getReasoningEffortDescription` + * in `extensions/copilot/src/extension/conversation/common/languageModelAccess.ts` + * so every provider surfaces the same descriptions. + */ +export function getReasoningEffortDescription(level: string): string | undefined { + switch (level) { + case 'none': return localize('reasoningEffort.noneDescription', "No reasoning applied"); + case 'minimal': return localize('reasoningEffort.minimalDescription', "Minimal reasoning for fastest responses"); + case 'low': return localize('reasoningEffort.lowDescription', "Faster responses with less reasoning"); + case 'medium': return localize('reasoningEffort.mediumDescription', "Balanced reasoning and speed"); + case 'high': return localize('reasoningEffort.highDescription', "Greater reasoning depth but slower"); + case 'xhigh': return localize('reasoningEffort.xhighDescription', "Highest reasoning depth but slowest"); + case 'max': return localize('reasoningEffort.maxDescription', "Absolute maximum capability with no constraints"); + default: return undefined; + } +} diff --git a/src/vs/platform/agentHost/node/codex/codexAgent.ts b/src/vs/platform/agentHost/node/codex/codexAgent.ts index 2fc3547b8ce..6950b0964ea 100644 --- a/src/vs/platform/agentHost/node/codex/codexAgent.ts +++ b/src/vs/platform/agentHost/node/codex/codexAgent.ts @@ -16,6 +16,7 @@ import { IInstantiationService } from '../../../instantiation/common/instantiati import { localize } from '../../../../nls.js'; import { ILogService } from '../../../log/common/log.js'; import { createSchema, platformSessionSchema, schemaProperty } from '../../common/agentHostSchema.js'; +import { getReasoningEffortDescription, getReasoningEffortLabel } from '../../common/reasoningEffort.js'; import { AgentHostCodexAgentBinaryArgsEnvVar, AgentHostCodexAgentCodexHomeEnvVar, AgentHostCodexAgentSdkRootEnvVar, AgentSession, AgentSignal, GITHUB_COPILOT_PROTECTED_RESOURCE, IAgent, IAgentCreateSessionConfig, IAgentCreateSessionResult, IAgentDescriptor, IAgentMaterializeSessionEvent, IAgentModelInfo, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, type AgentProvider } from '../../common/agentService.js'; import { SessionConfigKey } from '../../common/sessionConfigKeys.js'; import { AHP_AUTH_REQUIRED, ProtocolError } from '../../common/state/sessionProtocol.js'; @@ -122,12 +123,8 @@ const codexSessionConfigSchema = createSchema({ title: localize('codex.sessionConfig.modelReasoningEffort', "Reasoning Effort"), description: localize('codex.sessionConfig.modelReasoningEffortDescription', "Controls how much reasoning effort Codex uses."), enum: [...CODEX_REASONING_EFFORTS], - enumLabels: [ - localize('codex.sessionConfig.modelReasoningEffort.minimal', "Minimal"), - localize('codex.sessionConfig.modelReasoningEffort.low', "Low"), - localize('codex.sessionConfig.modelReasoningEffort.medium', "Medium"), - localize('codex.sessionConfig.modelReasoningEffort.high', "High"), - ], + enumLabels: CODEX_REASONING_EFFORTS.map(getReasoningEffortLabel), + enumDescriptions: CODEX_REASONING_EFFORTS.map(effort => getReasoningEffortDescription(effort) ?? ''), default: 'medium', sessionMutable: true, }), @@ -404,12 +401,8 @@ export class CodexAgent extends Disposable implements IAgent { description: localize('codex.modelThinkingLevel.description', "Controls how much reasoning effort Codex uses."), default: 'medium', enum: [...CODEX_REASONING_EFFORTS], - enumLabels: [ - localize('codex.modelThinkingLevel.minimal', "Minimal"), - localize('codex.modelThinkingLevel.low', "Low"), - localize('codex.modelThinkingLevel.medium', "Medium"), - localize('codex.modelThinkingLevel.high', "High"), - ], + enumLabels: CODEX_REASONING_EFFORTS.map(getReasoningEffortLabel), + enumDescriptions: CODEX_REASONING_EFFORTS.map(effort => getReasoningEffortDescription(effort) ?? ''), }, }, }; diff --git a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts index d14eb210537..4b88facf916 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts @@ -34,6 +34,7 @@ import { AgentHostSessionSyncEnabledConfigKey, AutoApproveLevel, ISchemaProperty import { IAgentPluginManager, ISyncedCustomization } from '../../common/agentPluginManager.js'; import { AgentSession, AgentSignal, GITHUB_COPILOT_PROTECTED_RESOURCE, IAgent, IAgentCreateSessionConfig, IAgentCreateSessionResult, IAgentDescriptor, IAgentMaterializeSessionEvent, IAgentModelInfo, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IAgentSessionProjectInfo, IMcpNotification } from '../../common/agentService.js'; import { getEffectiveAgents } from '../../common/customAgents.js'; +import { getReasoningEffortDescription, getReasoningEffortLabel } from '../../common/reasoningEffort.js'; import type { IAgentServerToolHost } from '../../common/agentServerTools.js'; import { IAgentHostOTelService } from '../../common/otel/agentHostOTelService.js'; import { SessionConfigKey } from '../../common/sessionConfigKeys.js'; @@ -674,23 +675,14 @@ export class CopilotAgent extends Disposable implements IAgent { return undefined; } - const enumLabels = supportedReasoningEfforts.map(value => { - switch (value) { - case 'low': return localize('copilot.modelThinkingLevel.low', "Low"); - case 'medium': return localize('copilot.modelThinkingLevel.medium', "Medium"); - case 'high': return localize('copilot.modelThinkingLevel.high', "High"); - case 'xhigh': return localize('copilot.modelThinkingLevel.xhigh', "Extra High"); - default: return value; - } - }); - return { type: 'string', title: localize('copilot.modelThinkingLevel.title', "Thinking Level"), description: localize('copilot.modelThinkingLevel.description', "Controls how much reasoning effort the model uses."), default: defaultReasoningEffort, enum: [...supportedReasoningEfforts], - enumLabels, + enumLabels: supportedReasoningEfforts.map(getReasoningEffortLabel), + enumDescriptions: supportedReasoningEfforts.map(value => getReasoningEffortDescription(value) ?? ''), }; } diff --git a/src/vs/platform/agentHost/test/common/claudeModelConfig.test.ts b/src/vs/platform/agentHost/test/common/claudeModelConfig.test.ts index 379388248cb..388ae38157f 100644 --- a/src/vs/platform/agentHost/test/common/claudeModelConfig.test.ts +++ b/src/vs/platform/agentHost/test/common/claudeModelConfig.test.ts @@ -109,6 +109,7 @@ suite('createClaudeThinkingLevelSchema (Phase 6.1 / Cycle D3)', () => { description: 'Controls how much reasoning effort Claude uses.', enum: ['low', 'medium', 'high', 'xhigh', 'max'], enumLabels: ['Low', 'Medium', 'High', 'Extra High', 'Max'], + enumDescriptions: ['Faster responses with less reasoning', 'Balanced reasoning and speed', 'Greater reasoning depth but slower', 'Highest reasoning depth but slowest', 'Absolute maximum capability with no constraints'], default: 'high', }, }, @@ -122,6 +123,7 @@ suite('createClaudeThinkingLevelSchema (Phase 6.1 / Cycle D3)', () => { description: 'Controls how much reasoning effort Claude uses.', enum: ['low', 'medium', 'high'], enumLabels: ['Low', 'Medium', 'High'], + enumDescriptions: ['Faster responses with less reasoning', 'Balanced reasoning and speed', 'Greater reasoning depth but slower'], default: 'high', }, }, @@ -135,6 +137,7 @@ suite('createClaudeThinkingLevelSchema (Phase 6.1 / Cycle D3)', () => { description: 'Controls how much reasoning effort Claude uses.', enum: ['high'], enumLabels: ['High'], + enumDescriptions: ['Greater reasoning depth but slower'], default: 'high', }, }, @@ -148,6 +151,7 @@ suite('createClaudeThinkingLevelSchema (Phase 6.1 / Cycle D3)', () => { description: 'Controls how much reasoning effort Claude uses.', enum: ['max', 'low'], enumLabels: ['Max', 'Low'], + enumDescriptions: ['Absolute maximum capability with no constraints', 'Faster responses with less reasoning'], }, }, }, diff --git a/src/vs/platform/agentHost/test/node/claudeAgent.test.ts b/src/vs/platform/agentHost/test/node/claudeAgent.test.ts index 0f611695f7b..130827b8ed5 100644 --- a/src/vs/platform/agentHost/test/node/claudeAgent.test.ts +++ b/src/vs/platform/agentHost/test/node/claudeAgent.test.ts @@ -821,6 +821,7 @@ suite('ClaudeAgent', () => { description: 'Controls how much reasoning effort Claude uses.', enum: ['low', 'medium', 'high'], enumLabels: ['Low', 'Medium', 'High'], + enumDescriptions: ['Faster responses with less reasoning', 'Balanced reasoning and speed', 'Greater reasoning depth but slower'], default: 'high', }, }, @@ -834,6 +835,7 @@ suite('ClaudeAgent', () => { description: 'Controls how much reasoning effort Claude uses.', enum: ['high'], enumLabels: ['High'], + enumDescriptions: ['Greater reasoning depth but slower'], default: 'high', }, }, @@ -848,6 +850,7 @@ suite('ClaudeAgent', () => { description: 'Controls how much reasoning effort Claude uses.', enum: ['low', 'high'], enumLabels: ['Low', 'High'], + enumDescriptions: ['Faster responses with less reasoning', 'Greater reasoning depth but slower'], default: 'high', }, },