From d9f7f814730998f191ad6c32bbd178e0834cae18 Mon Sep 17 00:00:00 2001 From: Cristina Poncela Cubeiro <140309543+cristinaponcela@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:51:16 +0200 Subject: [PATCH] fix: tool_call_id error when switching (#6854) --- packages/ai/src/api/openai-completions.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/ai/src/api/openai-completions.ts b/packages/ai/src/api/openai-completions.ts index 61001777..01eefcb0 100644 --- a/packages/ai/src/api/openai-completions.ts +++ b/packages/ai/src/api/openai-completions.ts @@ -34,6 +34,7 @@ import type { } from "../types.ts"; import { formatProviderError, normalizeProviderError } from "../utils/error-body.ts"; import { AssistantMessageEventStream } from "../utils/event-stream.ts"; +import { shortHash } from "../utils/hash.ts"; import { headersToRecord } from "../utils/headers.ts"; import { parseStreamingJson } from "../utils/json-parse.ts"; import { getProviderEnvValue } from "../utils/provider-env.ts"; @@ -895,10 +896,21 @@ export function convertMessages( // Format: {call_id}|{id} where {id} can be 400+ chars with special chars (+, /, =) // These come from providers like github-copilot, openai-codex, opencode // Extract just the call_id part and normalize it + // Multiple tool calls in the same turn can share call_id but differ by item_id. + // Preserve item-level uniqueness when replaying into Chat Completions, which + // requires distinct tool call ids. if (id.includes("|")) { - const [callId] = id.split("|"); // Sanitize to allowed chars and truncate to 40 chars (OpenAI limit) - return callId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40); + const separatorIndex = id.indexOf("|"); + const callId = id.slice(0, separatorIndex).replace(/[^a-zA-Z0-9_-]/g, "_"); + const itemId = id.slice(separatorIndex + 1).replace(/[^a-zA-Z0-9_-]/g, "_"); + const combinedId = itemId.length > 0 ? `${callId}_${itemId}` : callId; + if (combinedId.length <= 40) { + return combinedId; + } + const hash = shortHash(id).slice(0, 8); + const prefix = callId.slice(0, Math.max(1, 40 - hash.length - 1)); + return `${prefix}_${hash}`; } if (model.provider === "openai") return id.length > 40 ? id.slice(0, 40) : id;