From f93bd4dfb9ac68b0e6dda5c9727e515a8a1489c8 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Sun, 23 Aug 2026 20:15:08 -0500 Subject: [PATCH] fix(ai): fail loudly on missing anthropic tool_use id --- packages/ai/src/protocols/anthropic-messages.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/ai/src/protocols/anthropic-messages.ts b/packages/ai/src/protocols/anthropic-messages.ts index 894d2d282d7..adeca081146 100644 --- a/packages/ai/src/protocols/anthropic-messages.ts +++ b/packages/ai/src/protocols/anthropic-messages.ts @@ -795,7 +795,8 @@ const onContentBlockStart = (state: ParserState, event: AnthropicEvent): StepRes const block = event.content_block if (!block) return [state, NO_EVENTS] - if ((block.type === "tool_use" || block.type === "server_tool_use") && event.index !== undefined) { + if (block.type === "tool_use" || block.type === "server_tool_use") { + if (event.index === undefined || !block.id) return [state, NO_EVENTS] const events: LLMEvent[] = [] const lifecycle = Lifecycle.stepStart(state.lifecycle, events) return [ @@ -803,7 +804,7 @@ const onContentBlockStart = (state: ParserState, event: AnthropicEvent): StepRes ...state, lifecycle, tools: ToolStream.start(state.tools, event.index, { - id: block.id ?? String(event.index), + id: block.id, name: block.name ?? "", input: block.input !== undefined && (!ProviderShared.isRecord(block.input) || Object.keys(block.input).length > 0) @@ -815,7 +816,7 @@ const onContentBlockStart = (state: ParserState, event: AnthropicEvent): StepRes [ ...events, LLMEvent.toolInputStart({ - id: block.id ?? String(event.index), + id: block.id, name: block.name ?? "", providerExecuted: block.type === "server_tool_use" ? true : undefined, }), @@ -1015,7 +1016,14 @@ const onError = (event: AnthropicEvent) => const step = (state: ParserState, event: AnthropicEvent) => { if (event.type === "message_start") return Effect.succeed(onMessageStart(state, event)) - if (event.type === "content_block_start") return Effect.succeed(onContentBlockStart(state, event)) + if (event.type === "content_block_start") { + const block = event.content_block + if (block && (block.type === "tool_use" || block.type === "server_tool_use")) { + if (event.index === undefined) return invalid(`Anthropic ${block.type} missing index`) + if (!block.id) return invalid(`Anthropic tool_use missing id at index ${event.index}`) + } + return Effect.succeed(onContentBlockStart(state, event)) + } if (event.type === "content_block_delta") return onContentBlockDelta(state, event) if (event.type === "content_block_stop") return onContentBlockStop(state, event) if (event.type === "message_delta") return Effect.succeed(onMessageDelta(state, event))