From 58a1ae6dd7ca1d1d0e3d909b763d8d6b3282f014 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 9 Oct 2025 11:10:19 +0700 Subject: [PATCH] fix: normalize Mistral tool call IDs to comply with API requirements Mistral API requires tool call IDs to be exactly 9 characters containing only [a-zA-Z0-9]. Refactored normalizeToolCallIds to accept a transform function, allowing provider-specific ID formatting. Fixes #1680 --- packages/opencode/src/provider/transform.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 92001814af..eb788b9184 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1,17 +1,17 @@ import type { ModelMessage } from "ai" import { unique } from "remeda" import type { JSONSchema } from "zod/v4/core" +import { randomBytes } from "crypto" export namespace ProviderTransform { - function normalizeToolCallIds(msgs: ModelMessage[]): ModelMessage[] { + function normalizeToolCallIds(msgs: ModelMessage[], transform: (id: string) => string): ModelMessage[] { + const idMap = new Map() return msgs.map((msg) => { if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) { msg.content = msg.content.map((part) => { if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) { - return { - ...part, - toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"), - } + if (!idMap.has(part.toolCallId)) idMap.set(part.toolCallId, transform(part.toolCallId)) + return { ...part, toolCallId: idMap.get(part.toolCallId)! } } return part }) @@ -64,7 +64,14 @@ export namespace ProviderTransform { export function message(msgs: ModelMessage[], providerID: string, modelID: string) { if (modelID.includes("claude")) { - msgs = normalizeToolCallIds(msgs) + msgs = normalizeToolCallIds(msgs, (id) => id.replace(/[^a-zA-Z0-9_-]/g, "_")) + } + if (providerID === "mistral" || modelID.includes("mistral")) { + const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + msgs = normalizeToolCallIds(msgs, () => { + const bytes = randomBytes(9) + return Array.from(bytes, (b) => chars[b % chars.length]).join("") + }) } if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) { msgs = applyCaching(msgs, providerID)