Skip to content
Open
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
14 changes: 9 additions & 5 deletions src/vs/platform/agentHost/node/claude/claudeAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,20 @@ import { ClaudeSessionMetadataStore, IClaudeSessionOverlay } from './claudeSessi
* Returns true if `m` is a Claude-family model that should be advertised
* to clients picking a model for the Claude provider.
*
* Combines the same surface checks the extension uses (vendor, picker
* eligibility, tool-call support, `/v1/messages` endpoint) with a parse
* of the model id via {@link tryParseClaudeModelId}, which excludes
* synthetic ids like `auto` that aren't real Claude endpoints.
* Intentionally does NOT check `model_picker_enabled`: CAPI sets that flag
* conservatively (false for many models), and the Copilot extension overrides
* it via an A/B experiment (`copilotchat.showInModelPicker`) so the normal
* Claude Code window shows all capable models. The agent host has no access
* to that experiment service, so relying on the raw flag would cause it to
* show a much smaller set than the extension window does. The remaining four
* conditions (Anthropic vendor, `/v1/messages` endpoint, tool-call support,
* parseable model ID) are sufficient guards against synthetic or ineligible
* entries.
Comment on lines +51 to +59
*/
function isClaudeModel(m: CCAModel): boolean {
return (
m.vendor === 'Anthropic' &&
!!m.supported_endpoints?.includes('/v1/messages') &&
!!m.model_picker_enabled &&
!!m.capabilities?.supports?.tool_calls &&
tryParseClaudeModelId(m.id) !== undefined
);
Expand Down
13 changes: 11 additions & 2 deletions src/vs/platform/agentHost/test/node/claudeAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,12 @@ suite('ClaudeAgent', () => {
});

test('authenticate populates models filtered to Claude family', async () => {
// model_picker_enabled is intentionally NOT checked — CAPI sets it
// conservatively and the Copilot extension overrides it via
// experiments; the agent host has no experiment service, so we rely
// on vendor/endpoint/tool-call/model-id guards instead.
// ANTHROPIC_PICKER_DISABLED (claude-opus-4.5, model_picker_enabled:false)
// must appear here alongside the other Claude models.
const { agent, proxy } = createTestContext(disposables);

const accepted = await agent.authenticate('https://api.github.com', 'tok');
Expand All @@ -709,6 +715,7 @@ suite('ClaudeAgent', () => {
models: [
{ provider: 'claude', id: 'claude-opus-4.6', name: 'Claude Opus 4.6', maxContextWindow: 200_000, supportsVision: false, policyState: 'enabled', _meta: { multiplierNumeric: 1 } },
{ provider: 'claude', id: 'claude-sonnet-4.6', name: 'Claude Sonnet 4.6', maxContextWindow: 200_000, supportsVision: false, policyState: 'enabled', _meta: { multiplierNumeric: 1 } },
{ provider: 'claude', id: 'claude-opus-4.5', name: 'Claude Opus 4.5', maxContextWindow: 200_000, supportsVision: false, policyState: 'enabled', _meta: { multiplierNumeric: 1 } },
],
});
});
Expand Down Expand Up @@ -940,20 +947,22 @@ suite('ClaudeAgent', () => {
accepted: true,
startTokens: ['tok', 'tok'],
disposeCount: 0,
modelIds: [CLAUDE_OPUS.id, CLAUDE_SONNET.id],
modelIds: [CLAUDE_OPUS.id, CLAUDE_SONNET.id, ANTHROPIC_PICKER_DISABLED.id],
});
});

test('model filter excludes non-Claude entries', async () => {
// Same fixture set as the populate test, but assert on ids only —
// catches every exclusion criterion in one snapshot.
// Note: model_picker_enabled is NOT a filter criterion (see isClaudeModel);
// ANTHROPIC_PICKER_DISABLED (claude-opus-4.5) therefore appears here.
const { agent } = createTestContext(disposables);
await agent.authenticate('https://api.github.com', 'tok');
await tick();

assert.deepStrictEqual(
agent.models.get().map(m => m.id),
['claude-opus-4.6', 'claude-sonnet-4.6'],
['claude-opus-4.6', 'claude-sonnet-4.6', 'claude-opus-4.5'],
);
});

Expand Down