From 97265f8ac5568d4fd6fe4a4e5cab6a458d484d70 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:42:31 -0500 Subject: [PATCH] fix(ai): ignore orphaned Anthropic tool deltas (#43289) --- .../ai/src/protocols/anthropic-messages.ts | 1 + .../test/provider/anthropic-messages.test.ts | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/ai/src/protocols/anthropic-messages.ts b/packages/ai/src/protocols/anthropic-messages.ts index 65b405cc92..15353a4e52 100644 --- a/packages/ai/src/protocols/anthropic-messages.ts +++ b/packages/ai/src/protocols/anthropic-messages.ts @@ -905,6 +905,7 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f if (delta?.type === "input_json_delta" && event.index !== undefined) { if (!delta.partial_json) return [state, NO_EVENTS] satisfies StepResult + if (!state.tools[event.index]) return [state, NO_EVENTS] satisfies StepResult const result = ToolStream.appendExisting( ADAPTER, state.tools, diff --git a/packages/ai/test/provider/anthropic-messages.test.ts b/packages/ai/test/provider/anthropic-messages.test.ts index 8c26fb5fd6..8c9dedf9e5 100644 --- a/packages/ai/test/provider/anthropic-messages.test.ts +++ b/packages/ai/test/provider/anthropic-messages.test.ts @@ -1010,6 +1010,34 @@ describe("Anthropic Messages route", () => { }), ) + it.effect("ignores tool input deltas without a matching tool start", () => + Effect.gen(function* () { + const response = yield* LLMClient.generate(request).pipe( + Effect.provide( + fixedResponse( + sseEvents( + { type: "message_start", message: { usage: { input_tokens: 5 } } }, + { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }, + { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "Hello" } }, + { + type: "content_block_delta", + index: 1, + delta: { type: "input_json_delta", partial_json: '{"query":"orphaned"}' }, + }, + { type: "content_block_stop", index: 0 }, + { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 1 } }, + { type: "message_stop" }, + ), + ), + ), + ) + + expect(response.text).toBe("Hello") + expect(response.toolCalls).toEqual([]) + expect(response.finishReason).toEqual({ normalized: "stop", raw: "end_turn" }) + }), + ) + it.effect("settles pending tool calls at message_stop", () => Effect.gen(function* () { const response = yield* LLMClient.generate(request).pipe(