{"record":{"id":"f93432c9586676e1","repo":"microsoft/vscode","slug":"only-plan-agent-is-supported-received-0","errorCode":null,"errorMessage":"Only \"Plan\" agent is supported. Received: \"{0}\"","messagePattern":"Only \"Plan\" agent is supported\\. Received: \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/copilot/src/extension/tools/vscode-node/switchAgentTool.ts","lineNumber":55,"sourceCode":"\t\tconst searchSubagentEnabled = this.configurationService.getExperimentBasedConfig(ConfigKey.Advanced.SearchSubagentToolEnabled, this.experimentationService);\n\t\tconst planAgentBody = PlanAgentProvider.buildAgentBody(exploreEnabled, searchSubagentEnabled);\n\n\t\t// Execute command to switch agent\n\t\tawait vscode.commands.executeCommand('workbench.action.chat.toggleAgentMode', {\n\t\t\tmodeId: agentName,\n\t\t\tsessionResource: options.chatSessionResource\n\t\t});\n\n\t\treturn new LanguageModelToolResult([\n\t\t\tnew 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}`)\n\t\t]);\n\t}\n\n\tprepareInvocation(options: vscode.LanguageModelToolInvocationPrepareOptions<ISwitchAgentParams>, token: vscode.CancellationToken): vscode.ProviderResult<vscode.PreparedToolInvocation> {\n\t\tconst { agentName } = options.input;\n\n\t\tif (agentName !== 'Plan') {\n\t\t\tthrow new Error(vscode.l10n.t('Only \"Plan\" agent is supported. Received: \"{0}\"', agentName));\n\t\t}\n\n\t\treturn {\n\t\t\tinvocationMessage: new MarkdownString(vscode.l10n.t('Switching to {0} agent', agentName)),\n\t\t\tpastTenseMessage: new MarkdownString(vscode.l10n.t('Switched to {0} agent', agentName))\n\t\t};\n\t}\n}\n\nToolRegistry.registerTool(SwitchAgentTool);\n","sourceCodeStart":37,"sourceCodeEnd":66,"githubUrl":"https://github.com/microsoft/vscode/blob/a94b963a32aa9749d69504576791fa29700ae46d/extensions/copilot/src/extension/tools/vscode-node/switchAgentTool.ts#L37-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst { agentName } = options.input;\nif (agentName !== 'Plan') {\n\tthrow new Error(vscode.l10n.t('Only \"Plan\" agent is supported. Received: \"{0}\"', agentName));\n}\n\n// after — single source of truth shared by invoke() and prepareInvocation()\nconst SUPPORTED_AGENTS = new Set(['Plan']);\nfunction assertSupportedAgent(agentName: string, includeReceived: boolean): void {\n\tif (!SUPPORTED_AGENTS.has(agentName)) {\n\t\tconst tmpl = includeReceived\n\t\t\t? 'Only \"Plan\" agent is supported. Received: \"{0}\"'\n\t\t\t: 'Only \"Plan\" agent is supported';\n\t\tthrow new Error(vscode.l10n.t(tmpl, agentName));\n\t}\n}","handlingStrategy":"validation","validationCode":"const SUPPORTED_SWITCH_AGENT = 'Plan' as const;\nfunction validateSwitchAgentInput(input: { agentName: string }): vscode.ProviderResult<vscode.PreparedToolInvocation> {\n\tif (input.agentName !== SUPPORTED_SWITCH_AGENT) {\n\t\treturn undefined; // caller decides to skip rather than throw\n\t}\n\treturn {\n\t\tinvocationMessage: new MarkdownString(`Switching to ${input.agentName} agent`),\n\t\tpastTenseMessage: new MarkdownString(`Switched to ${input.agentName} agent`),\n\t};\n}\n\n// call validateSwitchAgentInput(options.input) before prepareInvocation to avoid the throw","typeGuard":"function isSupportedAgentName(name: string): name is 'Plan' {\n\treturn name === 'Plan';\n}","tryCatchPattern":"try {\n\tconst prepared = await tool.prepareInvocation(options, token);\n\t// use prepared.invocationMessage / pastTenseMessage\n} catch (e) {\n\tconst msg = e instanceof Error ? e.message : String(e);\n\tif (msg.startsWith('Only \"Plan\" agent is supported')) {\n\t\t// log the offending value (already in the message) and degrade gracefully\n\t\treturn;\n\t}\n\tthrow e;\n}","preventionTips":["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."],"tags":["vscode","copilot","agent-mode","language-model-tool","validation","prepare-invocation"],"analyzedSha":"a94b963a32aa9749d69504576791fa29700ae46d","analyzedAt":"2026-08-12T01:08:56.794Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}