( userMessage: Anthropic.MessageParam, apiConversationHistory: Anthropic.MessageParam[], )
| 48 | * @returns The validated user message with corrected tool_use_ids and any missing tool_results added |
| 49 | */ |
| 50 | export function validateAndFixToolResultIds( |
| 51 | userMessage: Anthropic.MessageParam, |
| 52 | apiConversationHistory: Anthropic.MessageParam[], |
| 53 | ): Anthropic.MessageParam { |
| 54 | // Only process user messages with array content |
| 55 | if (userMessage.role !== "user" || !Array.isArray(userMessage.content)) { |
| 56 | return userMessage |
| 57 | } |
| 58 | |
| 59 | // Find the previous assistant message from conversation history |
| 60 | const prevAssistantIdx = findLastIndex(apiConversationHistory, (msg) => msg.role === "assistant") |
| 61 | if (prevAssistantIdx === -1) { |
| 62 | return userMessage |
| 63 | } |
| 64 | |
| 65 | const previousAssistantMessage = apiConversationHistory[prevAssistantIdx] |
| 66 | |
| 67 | // Get tool_use blocks from the assistant message |
| 68 | const assistantContent = previousAssistantMessage.content |
| 69 | if (!Array.isArray(assistantContent)) { |
| 70 | return userMessage |
| 71 | } |
| 72 | |
| 73 | const toolUseBlocks = assistantContent.filter((block): block is Anthropic.ToolUseBlock => block.type === "tool_use") |
| 74 | |
| 75 | // No tool_use blocks to match against - no validation needed |
| 76 | if (toolUseBlocks.length === 0) { |
| 77 | return userMessage |
| 78 | } |
| 79 | |
| 80 | // Find tool_result blocks in the user message |
| 81 | let toolResults = userMessage.content.filter( |
| 82 | (block): block is Anthropic.ToolResultBlockParam => block.type === "tool_result", |
| 83 | ) |
| 84 | |
| 85 | // Deduplicate tool_result blocks to prevent API protocol violations (GitHub #10465) |
| 86 | // This serves as a safety net for any potential race conditions that could generate |
| 87 | // duplicate tool_results with the same tool_use_id. The root cause (approval feedback |
| 88 | // creating duplicate results) has been fixed in presentAssistantMessage.ts, but this |
| 89 | // deduplication remains as a defensive measure for unknown edge cases. |
| 90 | const seenToolResultIds = new Set<string>() |
| 91 | const deduplicatedContent = userMessage.content.filter((block) => { |
| 92 | if (block.type !== "tool_result") { |
| 93 | return true |
| 94 | } |
| 95 | if (seenToolResultIds.has(block.tool_use_id)) { |
| 96 | return false // Duplicate - filter out |
| 97 | } |
| 98 | seenToolResultIds.add(block.tool_use_id) |
| 99 | return true |
| 100 | }) |
| 101 | |
| 102 | userMessage = { |
| 103 | ...userMessage, |
| 104 | content: deduplicatedContent, |
| 105 | } |
| 106 | |
| 107 | toolResults = deduplicatedContent.filter( |
no test coverage detected