Fix issue #4718: Enforce 40-char limit for OpenAI tool call IDs
OpenAI's API requires tool_call IDs to be <= 40 characters, but Clawdbot was not enforcing this limit for OpenAI providers after commit870bfa94e. Changes: - Re-enable tool call ID sanitization for OpenAI in transcript-policy.ts - The sanitization preserves tool_use/tool_result pairing via stable mapping - Add test case for 439-char ID (similar to reported 438-char issue) - Update test expectations to reflect OpenAI now gets sanitized IDs Root cause: Commit870bfa94edisabled sanitization to "preserve Pi pairing", but the sanitization logic already preserves pairing correctly via the resolve() function in tool-call-id.ts. Fixes openclaw/openclaw#4718
This commit is contained in:
parent
56ee224d4c
commit
ccac2aeec2
@ -94,7 +94,7 @@ describe("sanitizeSessionHistory", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not sanitize tool call ids for openai-responses", async () => {
|
it("sanitizes tool call ids for openai-responses to enforce 40-char limit", async () => {
|
||||||
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
|
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
|
||||||
|
|
||||||
await sanitizeSessionHistory({
|
await sanitizeSessionHistory({
|
||||||
@ -108,7 +108,11 @@ describe("sanitizeSessionHistory", () => {
|
|||||||
expect(helpers.sanitizeSessionMessagesImages).toHaveBeenCalledWith(
|
expect(helpers.sanitizeSessionMessagesImages).toHaveBeenCalledWith(
|
||||||
mockMessages,
|
mockMessages,
|
||||||
"session:history",
|
"session:history",
|
||||||
expect.objectContaining({ sanitizeMode: "images-only", sanitizeToolCallIds: false }),
|
expect.objectContaining({
|
||||||
|
sanitizeMode: "images-only",
|
||||||
|
sanitizeToolCallIds: true,
|
||||||
|
toolCallIdMode: "strict",
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -137,6 +137,39 @@ describe("sanitizeToolCallIdsForCloudCodeAssist", () => {
|
|||||||
expect(r1.toolCallId).toBe(a.id);
|
expect(r1.toolCallId).toBe(a.id);
|
||||||
expect(r2.toolCallId).toBe(b.id);
|
expect(r2.toolCallId).toBe(b.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("enforces 40-char limit for very long IDs (issue #4718)", () => {
|
||||||
|
// Simulate a very long tool call ID (like 438 chars reported in issue)
|
||||||
|
const veryLongId = `call_${"x".repeat(434)}`;
|
||||||
|
expect(veryLongId.length).toBe(439); // Verify we're testing a long ID
|
||||||
|
|
||||||
|
const input = [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "toolCall", id: veryLongId, name: "gitlab", arguments: {} }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "toolResult",
|
||||||
|
toolCallId: veryLongId,
|
||||||
|
toolName: "gitlab",
|
||||||
|
content: [{ type: "text", text: "ok" }],
|
||||||
|
},
|
||||||
|
] satisfies AgentMessage[];
|
||||||
|
|
||||||
|
const out = sanitizeToolCallIdsForCloudCodeAssist(input);
|
||||||
|
expect(out).not.toBe(input);
|
||||||
|
|
||||||
|
const assistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
|
||||||
|
const toolCall = assistant.content?.[0] as { id?: string };
|
||||||
|
expect(toolCall.id).toBeDefined();
|
||||||
|
expect(toolCall.id?.length).toBeLessThanOrEqual(40);
|
||||||
|
expect(toolCall.id?.length).toBeGreaterThan(0);
|
||||||
|
expect(isValidCloudCodeAssistToolId(toolCall.id as string, "strict")).toBe(true);
|
||||||
|
|
||||||
|
const result = out[1] as Extract<AgentMessage, { role: "toolResult" }>;
|
||||||
|
expect(result.toolCallId).toBe(toolCall.id);
|
||||||
|
expect(result.toolCallId?.length).toBeLessThanOrEqual(40);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("strict mode (alphanumeric only)", () => {
|
describe("strict mode (alphanumeric only)", () => {
|
||||||
|
|||||||
@ -85,7 +85,8 @@ export function resolveTranscriptPolicy(params: {
|
|||||||
|
|
||||||
const needsNonImageSanitize = isGoogle || isAnthropic || isMistral || isOpenRouterGemini;
|
const needsNonImageSanitize = isGoogle || isAnthropic || isMistral || isOpenRouterGemini;
|
||||||
|
|
||||||
const sanitizeToolCallIds = isGoogle || isMistral;
|
// OpenAI requires tool call IDs <= 40 chars (issue #4718)
|
||||||
|
const sanitizeToolCallIds = isGoogle || isMistral || isOpenAi;
|
||||||
const toolCallIdMode: ToolCallIdMode | undefined = isMistral
|
const toolCallIdMode: ToolCallIdMode | undefined = isMistral
|
||||||
? "strict9"
|
? "strict9"
|
||||||
: sanitizeToolCallIds
|
: sanitizeToolCallIds
|
||||||
@ -99,7 +100,7 @@ export function resolveTranscriptPolicy(params: {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
sanitizeMode: isOpenAi ? "images-only" : needsNonImageSanitize ? "full" : "images-only",
|
sanitizeMode: isOpenAi ? "images-only" : needsNonImageSanitize ? "full" : "images-only",
|
||||||
sanitizeToolCallIds: !isOpenAi && sanitizeToolCallIds,
|
sanitizeToolCallIds,
|
||||||
toolCallIdMode,
|
toolCallIdMode,
|
||||||
repairToolUseResultPairing: !isOpenAi && repairToolUseResultPairing,
|
repairToolUseResultPairing: !isOpenAi && repairToolUseResultPairing,
|
||||||
preserveSignatures: isAntigravityClaudeModel,
|
preserveSignatures: isAntigravityClaudeModel,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user