Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/vs/platform/agentHost/common/copilotCliConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
Expand All @@ -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';

Expand Down Expand Up @@ -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<boolean>({
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<CopilotCliModelCapabilityOverrides>({
type: 'object',
title: localize('agentHost.config.modelCapabilityOverrides.title', "Model Capability Overrides"),
Expand Down
16 changes: 16 additions & 0 deletions src/vs/platform/agentHost/node/copilot/copilotAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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.
Comment thread
lramos15 marked this conversation as resolved.
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-<platform>` CLI and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
lramos15 marked this conversation as resolved.
env['AUTO_V2_ENDPOINT'] = 'true';
return env;
}
35 changes: 35 additions & 0 deletions src/vs/platform/agentHost/test/node/copilotAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -58,6 +58,11 @@ export class AgentHostCopilotCliSettingsContribution extends Disposable implemen
computeValue: () => this._configurationService.getValue<boolean>(AgentHostReasoningSummaryEnabledSettingId),
registerTriggers: (store, push) => this._pushOnSettingChange(store, push, AgentHostReasoningSummaryEnabledSettingId),
},
{
key: CopilotCliConfigKey.MultiTurnContextRouting,
computeValue: () => this._configurationService.getValue<boolean>(AgentHostMultiTurnContextRoutingEnabledSettingId) === true,
registerTriggers: (store, push) => this._pushOnSettingChange(store, push, AgentHostMultiTurnContextRoutingEnabledSettingId),
},
{
key: CopilotCliConfigKey.ModelCapabilityOverrides,
computeValue: () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -76,6 +76,7 @@ const fullSchema: Record<string, ConfigPropertySchema> = {
[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' },
};

Expand Down Expand Up @@ -119,20 +120,22 @@ 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',
[CopilotCliConfigKey.Opus48Prompt]: true,
[CopilotCliConfigKey.ToolSearchEnabled]: true,
[CopilotCliConfigKey.ToolSearchDeferThreshold]: 5,
[CopilotCliConfigKey.ReasoningSummary]: true,
[CopilotCliConfigKey.MultiTurnContextRouting]: true,
[CopilotCliConfigKey.ModelCapabilityOverrides]: capabilityOverrides,
});
});
Expand Down Expand Up @@ -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();
Expand Down
Loading