fix(ai): keep OpenAI tool call id truncation UTF-16 safe (#110471)

This commit is contained in:
pick-cat
2026-08-04 14:03:44 +08:00
committed by GitHub
parent 8ad2e06a9d
commit 01e7fca716
2 changed files with 50 additions and 1 deletions
@@ -49,4 +49,52 @@ describe("convertMessages assistant text replay", () => {
const replayed = converted.find((message) => message.role === "assistant");
expect(replayed?.content).toBe("Let me check the file.\nThe file contains X.");
});
it("keeps paired OpenAI tool call ids UTF-16 safe when truncating", () => {
const prefix = "a".repeat(39);
const oversizedId = `${prefix}🐱`;
const targetModel: Model<"openai-completions"> = {
...model,
id: "target-model",
provider: "openai",
};
const assistant: AssistantMessage = {
role: "assistant",
api: targetModel.api,
provider: targetModel.provider,
model: "source-model",
content: [{ type: "toolCall", id: oversizedId, name: "lookup", arguments: {} }],
usage: emptyUsage,
stopReason: "toolUse",
timestamp: 1,
};
const context: Context = {
messages: [
assistant,
{
role: "toolResult",
toolCallId: oversizedId,
toolName: "lookup",
content: [{ type: "text", text: "ok" }],
isError: false,
timestamp: 2,
},
],
};
const converted = convertMessages(
targetModel,
context,
resolveOpenAICompletionsCompat(targetModel),
);
const assistantParam = converted.find((message) => message.role === "assistant");
const toolParam = converted.find((message) => message.role === "tool");
const normalizedAssistantId =
assistantParam?.role === "assistant" ? assistantParam.tool_calls?.[0]?.id : undefined;
const normalizedToolResultId = toolParam?.role === "tool" ? toolParam.tool_call_id : undefined;
expect(oversizedId.slice(0, 40).charCodeAt(39)).toBe(0xd83d);
expect(normalizedAssistantId).toBe(prefix);
expect(normalizedToolResultId).toBe(prefix);
});
});
@@ -1,3 +1,4 @@
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
import type {
ChatCompletionAssistantMessageParam,
ChatCompletionContentPart,
@@ -57,7 +58,7 @@ export function convertMessages(
}
if (model.provider === "openai") {
return id.length > 40 ? id.slice(0, 40) : id;
return id.length > 40 ? truncateUtf16Safe(id, 40) : id;
}
return id;
};