From f4924f28aab5a9cc8cb32ffe93647f5711ec081f Mon Sep 17 00:00:00 2001 From: argsno Date: Sun, 12 Jul 2026 12:50:48 +0800 Subject: [PATCH] fix(stream): route content_block_delta to most-recently-started block at index When the SDK reuses a content_block index for two tool_use blocks in one stream (e.g. a read and a bash both at index 2), the delta handler matched blocks by first-match on event.index, so the second block's deltas were appended onto the first block's partialJson. That concatenated the two JSON payloads into one string, parsePartialJson failed, and one tool's arguments (the bash command) were silently dropped. Match the most-recently-started block at the index instead. Stop events keep first-match (they run in start order and each deletes its index), so they still resolve correctly. --- src/index.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 5090546..25640df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -877,7 +877,18 @@ function processStreamEvent( } if (event?.type === "content_block_delta") { - const index = c.turnBlocks.findIndex((b: any) => b.index === event.index); + // Match the most-recently-started block at this content_block index. + // A later tool_use can reuse an index already held by an earlier + // block (seen in real streams); first-match lookup would append the + // later block's deltas onto the earlier one, concatenating two JSON + // payloads and silently dropping one tool's arguments. Reverse scan + // routes deltas to the active (last-started) block. (Stop events + // keep first-match: they run in start order and each deletes its + // index, so the next stop finds the correct block.) + let index = -1; + for (let i = c.turnBlocks.length - 1; i >= 0; i--) { + if (c.turnBlocks[i].index === event.index) { index = i; break; } + } const block = c.turnBlocks[index]; if (!block) return; if (event.delta?.type === "text_delta" && block.type === "text") {