microsoft/vscode · error · Error
Only "Plan" agent is supported. Received: "{0}"
Error message
Only "Plan" agent is supported. Received: "{0}" What it means
This is the prepareInvocation() counterpart of the same SwitchAgentTool restriction. prepareInvocation runs BEFORE invoke to build the user-facing invocation/pastTense messages. It independently re-checks agentName === 'Plan' and throws a richer message that interpolates the offending value via l10n placeholder {0}. Because it executes earlier in the tool-call lifecycle, this is typically the first throw a caller sees.
Source
Thrown at extensions/copilot/src/extension/tools/vscode-node/switchAgentTool.ts:55
const searchSubagentEnabled = this.configurationService.getExperimentBasedConfig(ConfigKey.Advanced.SearchSubagentToolEnabled, this.experimentationService);
const planAgentBody = PlanAgentProvider.buildAgentBody(exploreEnabled, searchSubagentEnabled);
// Execute command to switch agent
await vscode.commands.executeCommand('workbench.action.chat.toggleAgentMode', {
modeId: agentName,
sessionResource: options.chatSessionResource
});
return new LanguageModelToolResult([
new LanguageModelTextPart(`Switched to ${agentName} agent. You are now the ${agentName} agent. This tool may no longer be available in the new agent.\n\n${planAgentBody}`)
]);
}
prepareInvocation(options: vscode.LanguageModelToolInvocationPrepareOptions<ISwitchAgentParams>, token: vscode.CancellationToken): vscode.ProviderResult<vscode.PreparedToolInvocation> {
const { agentName } = options.input;
if (agentName !== 'Plan') {
throw new Error(vscode.l10n.t('Only "Plan" agent is supported. Received: "{0}"', agentName));
}
return {
invocationMessage: new MarkdownString(vscode.l10n.t('Switching to {0} agent', agentName)),
pastTenseMessage: new MarkdownString(vscode.l10n.t('Switched to {0} agent', agentName))
};
}
}
ToolRegistry.registerTool(SwitchAgentTool);
View on GitHub (pinned to a94b963a32)
Solutions
- Pass agentName exactly 'Plan' (case-sensitive) — the only value both prepareInvocation and invoke accept.
- If adding a new supported mode, update the guard in BOTH prepareInvocation (line 54) and invoke (line 32) so they cannot diverge.
- For tests/simulations, drive prepareInvocation with { agentName: 'Plan' } or catch the throw to assert rejection behavior.
- Strip non-Plan agent names from any prompt context fed to the model so it never requests an unsupported switch.
Example fix
// before
const { agentName } = options.input;
if (agentName !== 'Plan') {
throw new Error(vscode.l10n.t('Only "Plan" agent is supported. Received: "{0}"', agentName));
}
// after — single source of truth shared by invoke() and prepareInvocation()
const SUPPORTED_AGENTS = new Set(['Plan']);
function assertSupportedAgent(agentName: string, includeReceived: boolean): void {
if (!SUPPORTED_AGENTS.has(agentName)) {
const tmpl = includeReceived
? 'Only "Plan" agent is supported. Received: "{0}"'
: 'Only "Plan" agent is supported';
throw new Error(vscode.l10n.t(tmpl, agentName));
}
} Defensive patterns
Strategy: validation
Validate before calling
const SUPPORTED_SWITCH_AGENT = 'Plan' as const;
function validateSwitchAgentInput(input: { agentName: string }): vscode.ProviderResult<vscode.PreparedToolInvocation> {
if (input.agentName !== SUPPORTED_SWITCH_AGENT) {
return undefined; // caller decides to skip rather than throw
}
return {
invocationMessage: new MarkdownString(`Switching to ${input.agentName} agent`),
pastTenseMessage: new MarkdownString(`Switched to ${input.agentName} agent`),
};
}
// call validateSwitchAgentInput(options.input) before prepareInvocation to avoid the throw Type guard
function isSupportedAgentName(name: string): name is 'Plan' {
return name === 'Plan';
} Try / catch
try {
const prepared = await tool.prepareInvocation(options, token);
// use prepared.invocationMessage / pastTenseMessage
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.startsWith('Only "Plan" agent is supported')) {
// log the offending value (already in the message) and degrade gracefully
return;
}
throw e;
} Prevention
- Validate agentName === 'Plan' before calling prepareInvocation to avoid the synchronous throw.
- Keep the accepted-values set in one place and reuse it in both prepareInvocation and invoke to prevent guard drift.
- When extending supported modes, update the guard and add coverage before shipping.
When it happens
Trigger: The chat framework calls prepareInvocation() with options.input.agentName not equal to 'Plan'. The throw at switchAgentTool.ts:55 fires during the prepare phase, before any command is dispatched or invoke() reached. The interpolated {0} shows the exact rejected value.
Common situations: The LLM proposes a switch to an unsupported mode (Edit, Ask, agent, etc.); case mismatch ('plan'); a downstream caller reuses the tool for a new mode without updating both guards; prepareInvocation is invoked in a dry-run/preview path that passes unvalidated input.
Related errors
AI-assisted analysis of microsoft/vscode@a94b963a32 (2026-08-12).
Data as JSON: /api/errors/f93432c9586676e1.
Report an issue: GitHub.