fix: skip tool id sanitization for openai responses

This commit is contained in:
Peter Steinberger
2026-01-22 23:51:48 +00:00
parent d297e17958
commit 870bfa94ed
4 changed files with 31 additions and 13 deletions

View File

@@ -27,6 +27,7 @@ Docs: https://docs.clawd.bot
- Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla.
- Control UI: resolve local avatar URLs with basePath across injection + identity RPC. (#1457) Thanks @dlauer.
- Agents: surface concrete API error details instead of generic AI service errors.
- Agents: avoid sanitizing tool call IDs for OpenAI responses to preserve Pi pairing.
- Docs: fix gog auth services example to include docs scope. (#1454) Thanks @zerone0x.
- macOS: prefer linked channels in gateway summary to avoid false “not linked” status.

View File

@@ -320,7 +320,7 @@ describe("sanitizeSessionHistory (google thinking)", () => {
expect(assistant.content?.map((block) => block.type)).toEqual(["thinking"]);
});
it("sanitizes tool call ids for OpenAI-compatible APIs", async () => {
it("sanitizes tool call ids for Google APIs", async () => {
const sessionManager = SessionManager.inMemory();
const longId = `call_${"a".repeat(60)}`;
const input = [
@@ -338,17 +338,21 @@ describe("sanitizeSessionHistory (google thinking)", () => {
const out = await sanitizeSessionHistory({
messages: input,
modelApi: "openai-responses",
modelApi: "google-antigravity",
sessionManager,
sessionId: "session:openai",
sessionId: "session:google",
});
const assistant = out[0] as Extract<AgentMessage, { role: "assistant" }>;
const assistant = out.find(
(msg) => (msg as { role?: unknown }).role === "assistant",
) as Extract<AgentMessage, { role: "assistant" }>;
const toolCall = assistant.content?.[0] as { id?: string };
expect(toolCall.id).toBeDefined();
expect(toolCall.id?.length).toBeLessThanOrEqual(40);
const toolResult = out[1] as Extract<AgentMessage, { role: "toolResult" }>;
const toolResult = out.find(
(msg) => (msg as { role?: unknown }).role === "toolResult",
) as Extract<AgentMessage, { role: "toolResult" }>;
expect(toolResult.toolCallId).toBe(toolCall.id);
});
});

View File

@@ -73,7 +73,7 @@ describe("sanitizeSessionHistory", () => {
);
});
it("does not sanitize tool call ids for non-Google, non-OpenAI APIs", async () => {
it("does not sanitize tool call ids for non-Google APIs", async () => {
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
await sanitizeSessionHistory({
@@ -92,6 +92,25 @@ describe("sanitizeSessionHistory", () => {
);
});
it("does not sanitize tool call ids for openai-responses", async () => {
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
await sanitizeSessionHistory({
messages: mockMessages,
modelApi: "openai-responses",
provider: "openai",
sessionManager: mockSessionManager,
sessionId: "test-session",
});
expect(helpers.isGoogleModelApi).toHaveBeenCalledWith("openai-responses");
expect(helpers.sanitizeSessionMessagesImages).toHaveBeenCalledWith(
mockMessages,
"session:history",
expect.objectContaining({ sanitizeToolCallIds: false }),
);
});
it("keeps reasoning-only assistant messages for openai-responses", async () => {
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);

View File

@@ -40,12 +40,6 @@ const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([
"minProperties",
"maxProperties",
]);
const OPENAI_TOOL_CALL_ID_APIS = new Set([
"openai",
"openai-completions",
"openai-responses",
"openai-codex-responses",
]);
const MISTRAL_MODEL_HINTS = [
"mistral",
"mixtral",
@@ -67,7 +61,7 @@ function isValidAntigravitySignature(value: unknown): value is string {
function shouldSanitizeToolCallIds(modelApi?: string | null): boolean {
if (!modelApi) return false;
return isGoogleModelApi(modelApi) || OPENAI_TOOL_CALL_ID_APIS.has(modelApi);
return isGoogleModelApi(modelApi);
}
function isMistralModel(params: { provider?: string | null; modelId?: string | null }): boolean {