Skip to content

@ai-sdk/anthropic 3.0.80 regression: text_editor/bash code_execution calls get "programmatic-tool-call" type injected, failing input validation (breaks skills) #15951

Description

@Drosscend

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

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions