@@ -139,35 +139,48 @@ export function validateAndFixToolResultIds(
139139 )
140140 }
141141
142- // Start with corrected content - fix invalid IDs
143- let correctedContent = userMessage . content . map ( ( block ) => {
144- if ( block . type !== "tool_result" ) {
145- return block
146- }
147-
148- // If the ID is already valid, keep it
149- if ( validToolUseIds . has ( block . tool_use_id ) ) {
150- return block
151- }
152-
153- // Find which tool_result index this block is by comparing references.
154- // This correctly handles duplicate tool_use_ids - we find the actual block's
155- // position among all tool_results, not the first block with a matching ID.
156- const toolResultIndex = toolResults . indexOf ( block as Anthropic . ToolResultBlockParam )
157-
158- // Try to match by position - only fix if there's a corresponding tool_use
159- if ( toolResultIndex !== - 1 && toolResultIndex < toolUseBlocks . length ) {
160- const correctId = toolUseBlocks [ toolResultIndex ] . id
161- return {
162- ...block ,
163- tool_use_id : correctId ,
142+ // Create a mapping of tool_result IDs to corrected IDs
143+ // Strategy: Match by position (first tool_result -> first tool_use, etc.)
144+ // This handles most cases where the mismatch is due to ID confusion
145+ //
146+ // Track which tool_use IDs have been used to prevent duplicates
147+ const usedToolUseIds = new Set < string > ( )
148+
149+ const correctedContent = userMessage . content
150+ . map ( ( block ) => {
151+ if ( block . type !== "tool_result" ) {
152+ return block
164153 }
165- }
166154
167- // No corresponding tool_use for this tool_result - leave it unchanged
168- // This can happen when there are more tool_results than tool_uses
169- return block
170- } )
155+ // If the ID is already valid and not yet used, keep it
156+ if ( validToolUseIds . has ( block . tool_use_id ) && ! usedToolUseIds . has ( block . tool_use_id ) ) {
157+ usedToolUseIds . add ( block . tool_use_id )
158+ return block
159+ }
160+
161+ // Find which tool_result index this block is by comparing references.
162+ // This correctly handles duplicate tool_use_ids - we find the actual block's
163+ // position among all tool_results, not the first block with a matching ID.
164+ const toolResultIndex = toolResults . indexOf ( block as Anthropic . ToolResultBlockParam )
165+
166+ // Try to match by position - only fix if there's a corresponding tool_use
167+ if ( toolResultIndex !== - 1 && toolResultIndex < toolUseBlocks . length ) {
168+ const correctId = toolUseBlocks [ toolResultIndex ] . id
169+ // Only use this ID if it hasn't been used yet
170+ if ( ! usedToolUseIds . has ( correctId ) ) {
171+ usedToolUseIds . add ( correctId )
172+ return {
173+ ...block ,
174+ tool_use_id : correctId ,
175+ }
176+ }
177+ }
178+
179+ // No corresponding tool_use for this tool_result, or the ID is already used
180+ // Filter out this orphaned tool_result by returning null
181+ return null
182+ } )
183+ . filter ( ( block ) : block is NonNullable < typeof block > => block !== null )
171184
172185 // Add missing tool_result blocks for any tool_use that doesn't have one
173186 // After the ID correction above, recalculate which tool_use IDs are now covered
@@ -179,21 +192,19 @@ export function validateAndFixToolResultIds(
179192
180193 const stillMissingToolUseIds = toolUseBlocks . filter ( ( toolUse ) => ! coveredToolUseIds . has ( toolUse . id ) )
181194
182- if ( stillMissingToolUseIds . length > 0 ) {
183- // Add placeholder tool_result blocks for missing tool_use IDs
184- const missingToolResults : Anthropic . ToolResultBlockParam [ ] = stillMissingToolUseIds . map ( ( toolUse ) => ( {
185- type : "tool_result" as const ,
186- tool_use_id : toolUse . id ,
187- content : "Tool execution was interrupted before completion." ,
188- } ) )
189-
190- // Insert missing tool_results at the beginning of the content array
191- // This ensures they come before any text blocks that may summarize the results
192- correctedContent = [ ...missingToolResults , ...correctedContent ]
193- }
195+ // Build final content: add missing tool_results at the beginning if any
196+ const missingToolResults : Anthropic . ToolResultBlockParam [ ] = stillMissingToolUseIds . map ( ( toolUse ) => ( {
197+ type : "tool_result" as const ,
198+ tool_use_id : toolUse . id ,
199+ content : "Tool execution was interrupted before completion." ,
200+ } ) )
201+
202+ // Insert missing tool_results at the beginning of the content array
203+ // This ensures they come before any text blocks that may summarize the results
204+ const finalContent = missingToolResults . length > 0 ? [ ...missingToolResults , ...correctedContent ] : correctedContent
194205
195206 return {
196207 ...userMessage ,
197- content : correctedContent ,
208+ content : finalContent ,
198209 }
199210}
0 commit comments