* Flush any pending tool results to the API conversation history. * * This is critical when the task is about to be * delegated (e.g., via new_task). Before delegation, if other tools were * called in the same turn before new_task, their tool_result blocks are * accumulated in `userMessage
()
| 933 | * So we usually only need to flush the pending user message with tool_results. |
| 934 | */ |
| 935 | public async flushPendingToolResultsToHistory(): Promise<boolean> { |
| 936 | // Only flush if there's actually pending content to save |
| 937 | if (this.userMessageContent.length === 0) { |
| 938 | return true |
| 939 | } |
| 940 | |
| 941 | // CRITICAL: Wait for the assistant message to be saved to API history first. |
| 942 | // Without this, tool_result blocks would appear BEFORE tool_use blocks in the |
| 943 | // conversation history, causing API errors like: |
| 944 | // "unexpected `tool_use_id` found in `tool_result` blocks" |
| 945 | // |
| 946 | // This can happen when parallel tools are called (e.g., update_todo_list + new_task). |
| 947 | // Tools execute during streaming via presentAssistantMessage, BEFORE the assistant |
| 948 | // message is saved. When new_task triggers delegation, it calls this method to |
| 949 | // flush pending results - but the assistant message hasn't been saved yet. |
| 950 | // |
| 951 | // The assistantMessageSavedToHistory flag is: |
| 952 | // - Reset to false at the start of each API request |
| 953 | // - Set to true after the assistant message is saved in recursivelyMakeClineRequests |
| 954 | if (!this.assistantMessageSavedToHistory) { |
| 955 | await pWaitFor(() => this.assistantMessageSavedToHistory || this.abort, { |
| 956 | interval: 50, |
| 957 | timeout: 30_000, // 30 second timeout as safety net |
| 958 | }).catch(() => { |
| 959 | // If timeout or abort, log and proceed anyway to avoid hanging |
| 960 | console.warn( |
| 961 | `[Task#${this.taskId}] flushPendingToolResultsToHistory: timed out waiting for assistant message to be saved`, |
| 962 | ) |
| 963 | }) |
| 964 | } |
| 965 | |
| 966 | // If task was aborted while waiting, don't flush |
| 967 | if (this.abort) { |
| 968 | return false |
| 969 | } |
| 970 | |
| 971 | // Save the user message with tool_result blocks |
| 972 | const userMessage: Anthropic.MessageParam = { |
| 973 | role: "user", |
| 974 | content: this.userMessageContent, |
| 975 | } |
| 976 | |
| 977 | // Validate and fix tool_result IDs when the previous *effective* message is an assistant message. |
| 978 | const effectiveHistoryForValidation = getEffectiveApiHistory(this.apiConversationHistory) |
| 979 | const lastEffective = effectiveHistoryForValidation[effectiveHistoryForValidation.length - 1] |
| 980 | const historyForValidation = lastEffective?.role === "assistant" ? effectiveHistoryForValidation : [] |
| 981 | const validatedMessage = validateAndFixToolResultIds(userMessage, historyForValidation) |
| 982 | const userMessageWithTs = { ...validatedMessage, ts: Date.now() } |
| 983 | this.apiConversationHistory.push(userMessageWithTs as ApiMessage) |
| 984 | |
| 985 | const saved = await this.saveApiConversationHistory() |
| 986 | |
| 987 | if (saved) { |
| 988 | // Clear the pending content since it's now saved |
| 989 | this.userMessageContent = [] |
| 990 | } else { |
| 991 | console.warn( |
| 992 | `[Task#${this.taskId}] flushPendingToolResultsToHistory: save failed, retaining pending tool results in memory`, |
no test coverage detected