Description
Since 3.0.80, streaming a text_editor_code_execution or bash_code_execution server tool call fails validation when code_execution is registered as a tool. In practice this breaks Anthropic skills: as soon as the model reads /skills/<name>/SKILL.md, the stream errors with:
AI_InvalidToolInputError: Invalid input for tool code_execution: Type validation failed:
Value: {"type":"programmatic-tool-call","command":"view","path":"/skills/my-skill/SKILL.md"}
[{"expected":"string","code":"invalid_type","path":["code"],...}]
The API never sent that type field — it's injected by the provider. What happens:
- on
content_block_start, text_editor_code_execution/bash_code_execution blocks are remapped to providerToolName = 'code_execution', losing the original name:
|
const providerToolName = |
|
part.name === 'text_editor_code_execution' || |
|
part.name === 'bash_code_execution' |
|
? 'code_execution' |
|
: part.name; |
- on the first
input_json_delta, "type": "programmatic-tool-call" is injected for every code_execution block:
|
|
|
// for the code execution 20250825, we need to add |
|
// the type to the delta and change the tool name. |
|
if ( |
|
contentBlock.firstDelta && |
|
contentBlock.providerToolName === 'code_execution' |
|
) { |
|
delta = `{"type": "programmatic-tool-call",${delta.substring(1)}`; |
|
} |
So a text editor view ends up as {"type":"programmatic-tool-call","command":"view","path":...}, which fails the discriminated union (that branch requires code: string).
Up to 3.0.79 this worked — the delta path kept the sub-tool name and injected it as the type. 3.0.80 (#15566) normalized everything to code_execution + programmatic-tool-call. That PR was about dynamic calls triggered by web_fetch/web_search, where validation is bypassed, so the mismatch didn't show up there. With an explicitly registered tool, validation applies and every sub-tool call fails.
Repro without an API key (fake fetch returning the SSE the API produces for a skill read):
import { createAnthropic } from '@ai-sdk/anthropic';
import { streamText } from 'ai';
const events = [
{ type: 'message_start', message: { id: 'msg_1', type: 'message', role: 'assistant', content: [], model: 'claude-sonnet-4-6', stop_reason: null, stop_sequence: null, usage: { input_tokens: 10, output_tokens: 1 } } },
{ type: 'content_block_start', index: 0, content_block: { type: 'server_tool_use', id: 'srvtoolu_1', name: 'text_editor_code_execution', input: {} } },
{ type: 'content_block_delta', index: 0, delta: { type: 'input_json_delta', partial_json: '{"command":"view",' } },
{ type: 'content_block_delta', index: 0, delta: { type: 'input_json_delta', partial_json: '"path":"/skills/my-skill/SKILL.md"}' } },
{ type: 'content_block_stop', index: 0 },
{ type: 'message_delta', delta: { stop_reason: 'tool_use', stop_sequence: null }, usage: { output_tokens: 5 } },
{ type: 'message_stop' },
];
const sse = events.map(e => `event: ${e.type}\ndata: ${JSON.stringify(e)}\n\n`).join('');
const anthropic = createAnthropic({
apiKey: 'test-key',
fetch: async () => new Response(sse, { status: 200, headers: { 'content-type': 'text/event-stream' } }),
});
const result = streamText({
model: anthropic('claude-sonnet-4-6'),
tools: { code_execution: anthropic.tools.codeExecution_20260120() },
prompt: 'read the skill file',
});
for await (const part of result.fullStream) {
if (part.type === 'tool-call') console.log(JSON.stringify(part, null, 2));
}
On 3.0.79 the tool call comes out with type: "text_editor_code_execution" and validates. On 3.0.80/3.0.81 it comes out invalid: true with the error above.
I'm working around it with a local patch that keeps part.name on the content block and injects the matching discriminator on the first delta — happy to open a PR if that approach sounds right.
AI SDK Version
- ai: 6.0.198
- @ai-sdk/anthropic: 3.0.81 (regression introduced in 3.0.80, last good 3.0.79)
Code of Conduct
Description
Since 3.0.80, streaming a
text_editor_code_executionorbash_code_executionserver tool call fails validation whencode_executionis registered as a tool. In practice this breaks Anthropic skills: as soon as the model reads/skills/<name>/SKILL.md, the stream errors with:The API never sent that
typefield — it's injected by the provider. What happens:content_block_start,text_editor_code_execution/bash_code_executionblocks are remapped toproviderToolName = 'code_execution', losing the original name:ai/packages/anthropic/src/anthropic-language-model.ts
Lines 1697 to 1701 in d1b1039
input_json_delta,"type": "programmatic-tool-call"is injected for everycode_executionblock:ai/packages/anthropic/src/anthropic-language-model.ts
Lines 2255 to 2263 in d1b1039
So a text editor
viewends up as{"type":"programmatic-tool-call","command":"view","path":...}, which fails the discriminated union (that branch requirescode: string).Up to 3.0.79 this worked — the delta path kept the sub-tool name and injected it as the type. 3.0.80 (#15566) normalized everything to
code_execution+programmatic-tool-call. That PR was about dynamic calls triggered by web_fetch/web_search, where validation is bypassed, so the mismatch didn't show up there. With an explicitly registered tool, validation applies and every sub-tool call fails.Repro without an API key (fake fetch returning the SSE the API produces for a skill read):
On 3.0.79 the tool call comes out with
type: "text_editor_code_execution"and validates. On 3.0.80/3.0.81 it comes outinvalid: truewith the error above.I'm working around it with a local patch that keeps
part.nameon the content block and injects the matching discriminator on the first delta — happy to open a PR if that approach sounds right.AI SDK Version
Code of Conduct