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

  1. Pass agentName exactly 'Plan' (case-sensitive) — the only value both prepareInvocation and invoke accept.
  2. If adding a new supported mode, update the guard in BOTH prepareInvocation (line 54) and invoke (line 32) so they cannot diverge.
  3. For tests/simulations, drive prepareInvocation with { agentName: 'Plan' } or catch the throw to assert rejection behavior.
  4. 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

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.