From 793a07904f24256195111d553902635a94813266 Mon Sep 17 00:00:00 2001 From: glod Date: Tue, 17 Feb 2026 10:08:09 +0100 Subject: [PATCH] fix: merge consecutive tool results for Anthropic API compatibility When the Anthropic API returns multiple parallel tool calls in a single assistant message, all tool_result blocks must be sent together in the next message. Previously, each tool result was sent as a separate message, causing Anthropic's API to reject the request with: "tool_use ids were found without tool_result blocks immediately after" This change modifies convertFromMessages() to merge consecutive tool messages into a single message containing all tool_result content blocks. Fixes #375 --- apps/x/packages/core/src/agents/runtime.ts | 54 ++++++++++++++++------ 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/apps/x/packages/core/src/agents/runtime.ts b/apps/x/packages/core/src/agents/runtime.ts index 09d1c721..cd55a503 100644 --- a/apps/x/packages/core/src/agents/runtime.ts +++ b/apps/x/packages/core/src/agents/runtime.ts @@ -354,7 +354,8 @@ export async function loadAgent(id: string): Promise> { export function convertFromMessages(messages: z.infer[]): ModelMessage[] { const result: ModelMessage[] = []; - for (const msg of messages) { + for (let i = 0; i < messages.length; i++) { + const msg = messages[i]; const { providerOptions } = msg; switch (msg.role) { case "assistant": @@ -401,23 +402,50 @@ export function convertFromMessages(messages: z.infer[]): ModelM providerOptions, }); break; - case "tool": + case "tool": { + // Collect all consecutive tool messages into a single message + // This is required by Anthropic's API which expects all tool_result blocks + // for parallel tool calls to be in a single message + const toolResults: Array<{ + type: "tool-result"; + toolCallId: string; + toolName: string; + output: { type: "text"; value: string }; + }> = []; + + // Add current tool message + toolResults.push({ + type: "tool-result", + toolCallId: msg.toolCallId, + toolName: msg.toolName, + output: { + type: "text", + value: msg.content, + }, + }); + + // Collect any consecutive tool messages + while (i + 1 < messages.length && messages[i + 1].role === "tool") { + i++; + const nextMsg = messages[i] as z.infer; + toolResults.push({ + type: "tool-result", + toolCallId: nextMsg.toolCallId, + toolName: nextMsg.toolName, + output: { + type: "text", + value: nextMsg.content, + }, + }); + } + result.push({ role: "tool", - content: [ - { - type: "tool-result", - toolCallId: msg.toolCallId, - toolName: msg.toolName, - output: { - type: "text", - value: msg.content, - }, - }, - ], + content: toolResults, providerOptions, }); break; + } } } // doing this because: https://github.com/OpenRouterTeam/ai-sdk-provider/issues/262