diff --git a/src/vs/platform/agentHost/common/copilotCliConfig.ts b/src/vs/platform/agentHost/common/copilotCliConfig.ts index 692088bc49dff7..c9ec787e1862fa 100644 --- a/src/vs/platform/agentHost/common/copilotCliConfig.ts +++ b/src/vs/platform/agentHost/common/copilotCliConfig.ts @@ -30,6 +30,8 @@ export const enum CopilotCliConfigKey { ReasoningEffortOverride = 'reasoningEffortOverride', /** Enable concise reasoning summaries for supported models. Off by default. */ ReasoningSummary = 'reasoningSummary', + /** Let the Auto router score prior turns instead of the latest message alone. Off by default. */ + MultiTurnContextRouting = 'multiTurnContextRouting', /** Per-model capability overrides (family aliases) keyed by model id. */ ModelCapabilityOverrides = 'modelCapabilityOverrides', } @@ -55,6 +57,8 @@ export const AgentHostReasoningEffortOverrideSettingId = 'chat.agentHost.copilot export const AgentHostReasoningSummaryEnabledSettingId = 'chat.agentHost.copilot.reasoningSummary.enabled'; +export const AgentHostMultiTurnContextRoutingEnabledSettingId = 'chat.agentHost.copilot.multiTurnContextRouting.enabled'; + export const AgentHostModelCapabilityOverridesSettingId = 'chat.agentHost.modelCapabilityOverrides'; export const AgentHostCopilotModelCapabilityOverridesSettingId = 'chat.agentHost.copilot.modelCapabilityOverrides'; @@ -164,6 +168,12 @@ export const copilotCliConfigSchema = createSchema({ description: localize('agentHost.config.reasoningSummary.description', "When enabled, requests concise reasoning summaries for supported Copilot SDK sessions."), default: false, }), + [CopilotCliConfigKey.MultiTurnContextRouting]: schemaProperty({ + type: 'boolean', + title: localize('agentHost.config.multiTurnContextRouting.title', "Auto Multi-Turn Context Routing"), + description: localize('agentHost.config.multiTurnContextRouting.description', "When enabled, Auto model selection sends prior user messages to the router so it scores the conversation so far instead of the latest message alone."), + default: false, + }), [CopilotCliConfigKey.ModelCapabilityOverrides]: schemaProperty({ type: 'object', title: localize('agentHost.config.modelCapabilityOverrides.title', "Model Capability Overrides"), diff --git a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts index 2c709c0e36906a..ce52c65745eecb 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts @@ -973,6 +973,10 @@ export class CopilotAgent extends Disposable implements IAgent { return this._configurationService.getRootValue(copilotCliConfigSchema, CopilotCliConfigKey.RubberDuck) ?? DEFAULT_COPILOT_RUBBER_DUCK_ENABLED; } + private _isMultiTurnContextRoutingEnabled(): boolean { + return this._configurationService.getRootValue(copilotCliConfigSchema, CopilotCliConfigKey.MultiTurnContextRouting) === true; + } + private _getCopilotSdkLogLevelSetting(): CopilotSdkLogLevelSetting { return this._configurationService.getRootValue(copilotCliConfigSchema, CopilotCliConfigKey.CopilotSdkLogLevel) ?? 'info'; } @@ -1001,6 +1005,7 @@ export class CopilotAgent extends Disposable implements IAgent { return new CopilotAgentStartupConfig( this._isSessionSyncEnabled(), this._isRubberDuckEnabled(), + this._isMultiTurnContextRoutingEnabled(), this._getCopilotSdkLogLevelSetting(), this._getEnterpriseHost(), this._isSystemProxyEnabled(), @@ -1935,6 +1940,17 @@ export class CopilotAgent extends Disposable implements IAgent { delete env['RUBBER_DUCK_AGENT']; } + // Let the Auto router score prior user messages instead of the latest + // message alone. `MULTI_TURN_CONTEXT_ROUTING` is the runtime's local + // override for the matching ExP flag, and only takes effect on top of + // the single-call Auto endpoint that `createCopilotCliEnvironment` + // already opts into. + if (startupConfig.multiTurnContextRouting) { + env['MULTI_TURN_CONTEXT_ROUTING'] = 'true'; + } else { + delete env['MULTI_TURN_CONTEXT_ROUTING']; + } + // Resolve the CLI entry point and native SDK binaries from node_modules. // In the desktop app these live next to the ASAR archive in // `node_modules.asar.unpacked` (the `@github/copilot-` CLI and diff --git a/src/vs/platform/agentHost/node/copilot/copilotAgentStartupConfig.ts b/src/vs/platform/agentHost/node/copilot/copilotAgentStartupConfig.ts index c3cb3dc1ed3237..8cedc2eaa10369 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotAgentStartupConfig.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotAgentStartupConfig.ts @@ -11,6 +11,7 @@ export class CopilotAgentStartupConfig { constructor( readonly sessionSync: boolean, readonly rubberDuck: boolean, + readonly multiTurnContextRouting: boolean, readonly copilotSdkLogLevel: CopilotSdkLogLevelSetting, readonly enterpriseHost: string | undefined, readonly systemProxy: boolean, diff --git a/src/vs/platform/agentHost/node/copilot/copilotCliEnvironment.ts b/src/vs/platform/agentHost/node/copilot/copilotCliEnvironment.ts index 5d6c8938529b35..bf7bb75fc78474 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotCliEnvironment.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotCliEnvironment.ts @@ -24,5 +24,9 @@ export function createCopilotCliEnvironment(environment: NodeJS.ProcessEnv = pro env['COPILOT_MCP_APPS'] = 'true'; env[AiAgentEnvVar] = AiAgentEnvValue; env['AUTO_APPROVAL'] = 'true'; + // Resolve Auto mode through the CLI's single-call `POST /auto` endpoint. The + // runtime gates this on an ExP flag whose local override is the flag name + // itself, so VS Code opts its whole population in rather than splitting it. + env['AUTO_V2_ENDPOINT'] = 'true'; return env; } diff --git a/src/vs/platform/agentHost/test/node/copilotAgent.test.ts b/src/vs/platform/agentHost/test/node/copilotAgent.test.ts index 92fac546cb4ff6..53e0e5df4a7b0e 100644 --- a/src/vs/platform/agentHost/test/node/copilotAgent.test.ts +++ b/src/vs/platform/agentHost/test/node/copilotAgent.test.ts @@ -3719,6 +3719,41 @@ suite('CopilotAgent', () => { } }); + test('enables the auto v2 endpoint always and multi-turn context routing only when configured', async () => { + const defaultClient = new TestCopilotClient([]); + const { agent: defaultAgent } = createTestAgentContext(disposables, { copilotClient: defaultClient }); + try { + await defaultAgent.listChatsToMigrate(); + + const routingClient = new TestCopilotClient([]); + const { agent: routingAgent } = createTestAgentContext(disposables, { + copilotClient: routingClient, + rootConfig: { [CopilotCliConfigKey.MultiTurnContextRouting]: true }, + }); + try { + await routingAgent.listChatsToMigrate(); + + const defaultEnv = getCreatedClientOptions(defaultAgent).at(-1)?.env; + const routingEnv = getCreatedClientOptions(routingAgent).at(-1)?.env; + assert.deepStrictEqual({ + defaultAutoV2: defaultEnv?.['AUTO_V2_ENDPOINT'], + defaultMultiTurn: defaultEnv?.['MULTI_TURN_CONTEXT_ROUTING'], + routingAutoV2: routingEnv?.['AUTO_V2_ENDPOINT'], + routingMultiTurn: routingEnv?.['MULTI_TURN_CONTEXT_ROUTING'], + }, { + defaultAutoV2: 'true', + defaultMultiTurn: undefined, + routingAutoV2: 'true', + routingMultiTurn: 'true', + }); + } finally { + await disposeAgent(routingAgent); + } + } finally { + await disposeAgent(defaultAgent); + } + }); + test('enables the built-in GitHub MCP server by default and removes its environment variable when disabled', async () => { const enabledClient = new TestCopilotClient([]); const { agent: enabledAgent } = createTestAgentContext(disposables, { copilotClient: enabledClient }); diff --git a/src/vs/platform/agentHost/test/node/copilotAgentStartupConfig.test.ts b/src/vs/platform/agentHost/test/node/copilotAgentStartupConfig.test.ts index a217459a26b06b..2bcbccd625b96b 100644 --- a/src/vs/platform/agentHost/test/node/copilotAgentStartupConfig.test.ts +++ b/src/vs/platform/agentHost/test/node/copilotAgentStartupConfig.test.ts @@ -11,9 +11,9 @@ suite('CopilotAgentStartupConfig', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('compares and describes startup configuration changes', () => { - const previous = new CopilotAgentStartupConfig(false, true, 'info', undefined, true, true, {}); - const same = new CopilotAgentStartupConfig(false, true, 'info', undefined, true, true, {}); - const changed = new CopilotAgentStartupConfig(true, true, 'trace', 'github.example.com', false, false, { deny: ['shell(*)'] }); + const previous = new CopilotAgentStartupConfig(false, true, false, 'info', undefined, true, true, {}); + const same = new CopilotAgentStartupConfig(false, true, false, 'info', undefined, true, true, {}); + const changed = new CopilotAgentStartupConfig(true, true, true, 'trace', 'github.example.com', false, false, { deny: ['shell(*)'] }); assert.deepStrictEqual({ same: same.equals(previous), @@ -24,7 +24,7 @@ suite('CopilotAgentStartupConfig', () => { same: true, changed: false, proxyTargetChanged: true, - description: 'sessionSync=true, copilotSdkLogLevel=trace, enterpriseHost=github.example.com, systemProxy=false, githubMcpServer=false, managedSettingsPermissions', + description: 'sessionSync=true, multiTurnContextRouting=true, copilotSdkLogLevel=trace, enterpriseHost=github.example.com, systemProxy=false, githubMcpServer=false, managedSettingsPermissions', }); }); }); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostCopilotCliSettingsContribution.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostCopilotCliSettingsContribution.ts index 9765e54e1cea0a..c9d9e5131e6c58 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostCopilotCliSettingsContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostCopilotCliSettingsContribution.ts @@ -8,7 +8,7 @@ import { autorun } from '../../../../../../base/common/observable.js'; import { isObject } from '../../../../../../base/common/types.js'; import { IAgentHostEnablementService } from '../../../../../../platform/agentHost/common/agentHostEnablementService.js'; import { IAgentHostService } from '../../../../../../platform/agentHost/common/agentService.js'; -import { AgentHostCopilotModelCapabilityOverridesSettingId, AgentHostCopilotSdkLogLevelSettingId, AgentHostOpus48PromptEnabledSettingId, AgentHostReasoningSummaryEnabledSettingId, AgentHostToolSearchDeferThresholdSettingId, AgentHostToolSearchEnabledSettingId, CopilotCliConfigKey, normalizeToolSearchDeferThreshold, type CopilotCliModelCapabilityOverrides, type CopilotSdkLogLevelSetting } from '../../../../../../platform/agentHost/common/copilotCliConfig.js'; +import { AgentHostCopilotModelCapabilityOverridesSettingId, AgentHostCopilotSdkLogLevelSettingId, AgentHostMultiTurnContextRoutingEnabledSettingId, AgentHostOpus48PromptEnabledSettingId, AgentHostReasoningSummaryEnabledSettingId, AgentHostToolSearchDeferThresholdSettingId, AgentHostToolSearchEnabledSettingId, CopilotCliConfigKey, normalizeToolSearchDeferThreshold, type CopilotCliModelCapabilityOverrides, type CopilotSdkLogLevelSetting } from '../../../../../../platform/agentHost/common/copilotCliConfig.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IWorkbenchContribution } from '../../../../../../workbench/common/contributions.js'; import { AgentHostRootConfigForwarder, type IForwardedRootConfigKey } from './agentHostRootConfigForwarder.js'; @@ -58,6 +58,11 @@ export class AgentHostCopilotCliSettingsContribution extends Disposable implemen computeValue: () => this._configurationService.getValue(AgentHostReasoningSummaryEnabledSettingId), registerTriggers: (store, push) => this._pushOnSettingChange(store, push, AgentHostReasoningSummaryEnabledSettingId), }, + { + key: CopilotCliConfigKey.MultiTurnContextRouting, + computeValue: () => this._configurationService.getValue(AgentHostMultiTurnContextRoutingEnabledSettingId) === true, + registerTriggers: (store, push) => this._pushOnSettingChange(store, push, AgentHostMultiTurnContextRoutingEnabledSettingId), + }, { key: CopilotCliConfigKey.ModelCapabilityOverrides, computeValue: () => { diff --git a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts index 30e9a1e3a72788..caf77a01582b57 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts @@ -20,7 +20,7 @@ import { AgentHostAutoReplyEnabledConfigKey, AgentHostEditAutoApprovePatternsCon import '../../../../platform/agentHost/common/agentHostStarter.config.contribution.js'; import { AgentMergeSettingId } from '../../../../platform/agentHost/common/agentMerge.js'; import { AgentHostAhpJsonlLoggingSettingId, AgentHostAllowSignedOutWhenUsableSettingId, AgentHostSdkSandboxEnabledSettingId, AgentHostSdkSandboxWindowsEnabledSettingId, CodexPreferAgentHostEditorSettingId } from '../../../../platform/agentHost/common/agentService.js'; -import { AgentHostCopilotModelCapabilityOverridesSettingId, AgentHostCopilotSdkLogLevelSettingId, AgentHostCustomTerminalToolEnabledSettingId, AgentHostOpus48PromptEnabledSettingId, AgentHostReasoningEffortOverrideSettingId, AgentHostReasoningSummaryEnabledSettingId, AgentHostToolSearchDeferThresholdSettingId, AgentHostToolSearchEnabledSettingId, copilotSdkLogLevelSettingValues } from '../../../../platform/agentHost/common/copilotCliConfig.js'; +import { AgentHostCopilotModelCapabilityOverridesSettingId, AgentHostCopilotSdkLogLevelSettingId, AgentHostCustomTerminalToolEnabledSettingId, AgentHostMultiTurnContextRoutingEnabledSettingId, AgentHostOpus48PromptEnabledSettingId, AgentHostReasoningEffortOverrideSettingId, AgentHostReasoningSummaryEnabledSettingId, AgentHostToolSearchDeferThresholdSettingId, AgentHostToolSearchEnabledSettingId, copilotSdkLogLevelSettingValues } from '../../../../platform/agentHost/common/copilotCliConfig.js'; import { DEFAULT_EDIT_AUTO_APPROVE_PATTERNS, mergeChatEditAutoApprovePatterns } from '../../../../platform/chat/common/chatSettings.js'; import { reasoningEffortLevels } from '../../../../platform/agentHost/common/reasoningEffort.js'; import { ChatSessionArchiveActionWordingSettingId } from '../../../../platform/chat/common/sessionArchiveActions.js'; @@ -1597,6 +1597,13 @@ configurationRegistry.registerConfiguration({ experiment: { mode: 'startup' }, tags: ['experimental', 'advanced'], }, + [AgentHostMultiTurnContextRoutingEnabledSettingId]: { + type: 'boolean', + markdownDescription: nls.localize('chat.agentHost.copilot.multiTurnContextRouting', "When enabled, Auto model selection in Copilot SDK agent sessions routes on the conversation so far, sending prior user messages to the router instead of scoring the latest message alone."), + default: false, + experiment: { mode: 'startup' }, + tags: ['experimental', 'advanced'], + }, [AgentHostCopilotModelCapabilityOverridesSettingId]: { type: 'object', markdownDescription: nls.localize('chat.agentHost.copilot.modelCapabilityOverrides', "Per-model capability overrides for Copilot SDK agent sessions, keyed by model id (`*` matches every model; a specific entry wins field-by-field), intended for evaluating models against an existing model's profile. Declare an aliased `family` (for example `claude-opus-4.8`) to route the model to that family's tuned system prompt and tool profile without changing the model id sent to the runtime — so a preview model can be evaluated against a known prompt while still running on its own endpoint — a `reasoningEffort` to pin its effort level, `availableTools`/`excludedTools` to filter its tool set, or `modelCapabilities` to override individual capability limits (e.g. vision support, context window size) passed through to the SDK. All overrides apply when a session launches or resumes. On a mid-session model change, only the new model's `reasoningEffort` is applied; the session keeps its launch-time family, tool filters, and model capabilities. Only affects Copilot agent sessions.\n\n**Note**: This is an advanced setting for experimentation."), diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostCopilotCliSettingsContribution.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostCopilotCliSettingsContribution.test.ts index 68d1bd7a8bd4fa..9e7b1d385118d4 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostCopilotCliSettingsContribution.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostCopilotCliSettingsContribution.test.ts @@ -11,7 +11,7 @@ import { mock } from '../../../../../../base/test/common/mock.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { IAgentHostEnablementService } from '../../../../../../platform/agentHost/common/agentHostEnablementService.js'; import { IAgentHostService } from '../../../../../../platform/agentHost/common/agentService.js'; -import { AgentHostCopilotModelCapabilityOverridesSettingId, AgentHostCopilotSdkLogLevelSettingId, AgentHostOpus48PromptEnabledSettingId, AgentHostReasoningSummaryEnabledSettingId, AgentHostToolSearchDeferThresholdSettingId, AgentHostToolSearchEnabledSettingId, CopilotCliConfigKey } from '../../../../../../platform/agentHost/common/copilotCliConfig.js'; +import { AgentHostCopilotModelCapabilityOverridesSettingId, AgentHostCopilotSdkLogLevelSettingId, AgentHostMultiTurnContextRoutingEnabledSettingId, AgentHostOpus48PromptEnabledSettingId, AgentHostReasoningSummaryEnabledSettingId, AgentHostToolSearchDeferThresholdSettingId, AgentHostToolSearchEnabledSettingId, CopilotCliConfigKey } from '../../../../../../platform/agentHost/common/copilotCliConfig.js'; import { IAgentSubscription } from '../../../../../../platform/agentHost/common/state/agentSubscription.js'; import type { ClientAnnotationsAction, INotification, IRootConfigChangedAction, SessionAction, TerminalAction } from '../../../../../../platform/agentHost/common/state/sessionActions.js'; import type { ConfigPropertySchema, RootState } from '../../../../../../platform/agentHost/common/state/sessionState.js'; @@ -76,6 +76,7 @@ const fullSchema: Record = { [CopilotCliConfigKey.ToolSearchEnabled]: { type: 'boolean', title: 'Agent Host Tool Search' }, [CopilotCliConfigKey.ToolSearchDeferThreshold]: { type: 'number', title: 'Tool Search Defer Threshold' }, [CopilotCliConfigKey.ReasoningSummary]: { type: 'boolean', title: 'Reasoning Summary' }, + [CopilotCliConfigKey.MultiTurnContextRouting]: { type: 'boolean', title: 'Auto Multi-Turn Context Routing' }, [CopilotCliConfigKey.ModelCapabilityOverrides]: { type: 'object', title: 'Model Capability Overrides' }, }; @@ -119,13 +120,14 @@ suite('AgentHostCopilotCliSettingsContribution', () => { [AgentHostToolSearchDeferThresholdSettingId]: 5.9, [AgentHostCopilotModelCapabilityOverridesSettingId]: capabilityOverrides, [AgentHostReasoningSummaryEnabledSettingId]: true, + [AgentHostMultiTurnContextRoutingEnabledSettingId]: true, }); agentHostService.setRootState(makeRootStateWithSchema(fullSchema)); await flush(); // The shared forwarder dispatches one RootConfigChanged per key; merge them // and assert the full forwarded set (order-independent). - assert.strictEqual(agentHostService.dispatchedActions.length, 6); + assert.strictEqual(agentHostService.dispatchedActions.length, 7); const merged = Object.assign({}, ...agentHostService.dispatchedActions.map(a => (a.action as IRootConfigChangedAction).config)); assert.deepStrictEqual(merged, { [CopilotCliConfigKey.CopilotSdkLogLevel]: 'trace', @@ -133,6 +135,7 @@ suite('AgentHostCopilotCliSettingsContribution', () => { [CopilotCliConfigKey.ToolSearchEnabled]: true, [CopilotCliConfigKey.ToolSearchDeferThreshold]: 5, [CopilotCliConfigKey.ReasoningSummary]: true, + [CopilotCliConfigKey.MultiTurnContextRouting]: true, [CopilotCliConfigKey.ModelCapabilityOverrides]: capabilityOverrides, }); }); @@ -187,6 +190,7 @@ suite('AgentHostCopilotCliSettingsContribution', () => { [CopilotCliConfigKey.ToolSearchEnabled]: false, [CopilotCliConfigKey.ToolSearchDeferThreshold]: 1, [CopilotCliConfigKey.ReasoningSummary]: false, + [CopilotCliConfigKey.MultiTurnContextRouting]: false, [CopilotCliConfigKey.ModelCapabilityOverrides]: { 'preview-model-x': { family: 'claude-opus-4-8' } }, })); await flush();