Files
9router/tests/unit/openai-to-claude-response-tools.test.js
GodrezJr2andClaude Sonnet 4.6 96a9a2b36d fix: sanitize Read tool args to prevent retry loops from non-Anthropic models (#1144)
* fix: sanitize Read tool args to prevent retry loops from non-Anthropic models

* fix: sanitize invalid Read pages from tool args

Non-Anthropic models sometimes emit optional Read args like pages: "" for
non-PDF files, which Claude Code rejects before the tool runs. Drop invalid
pages values, keep valid PDF page ranges, and coerce numeric string bounds
before clamping limit/offset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:33:38 +07:00

62 lines
2.0 KiB
JavaScript

import { describe, expect, it } from "vitest";
import { openaiToClaudeResponse } from "../../open-sse/translator/response/openai-to-claude.js";
function createState() {
return { toolCalls: new Map(), nextBlockIndex: 0 };
}
function getInputJsonDelta(events) {
return events.find((event) => event.type === "content_block_delta" && event.delta?.type === "input_json_delta")?.delta.partial_json;
}
describe("openaiToClaudeResponse tool argument sanitization", () => {
it("drops invalid Read pages and clamps numeric bounds", () => {
const state = createState();
openaiToClaudeResponse({
id: "chatcmpl-test-read",
model: "test-model",
choices: [{ delta: { tool_calls: [{ index: 0, id: "toolu_read", function: { name: "Read" } }] } }],
}, state);
const events = openaiToClaudeResponse({
id: "chatcmpl-test-read",
model: "test-model",
choices: [{
delta: { tool_calls: [{ index: 0, function: { arguments: JSON.stringify({ file_path: "F:/repo/file.js", offset: -5, limit: 999999999, pages: "" }) } }] },
finish_reason: "tool_calls",
}],
}, state);
expect(JSON.parse(getInputJsonDelta(events))).toEqual({
file_path: "F:/repo/file.js",
offset: 0,
limit: 2000,
});
});
it("keeps valid PDF pages", () => {
const state = createState();
openaiToClaudeResponse({
id: "chatcmpl-test-pdf",
model: "test-model",
choices: [{ delta: { tool_calls: [{ index: 0, id: "toolu_pdf", function: { name: "proxy_Read" } }] } }],
}, state);
const events = openaiToClaudeResponse({
id: "chatcmpl-test-pdf",
model: "test-model",
choices: [{
delta: { tool_calls: [{ index: 0, function: { arguments: JSON.stringify({ file_path: "F:/repo/doc.pdf", pages: "1-3" }) } }] },
finish_reason: "tool_calls",
}],
}, state);
expect(JSON.parse(getInputJsonDelta(events))).toEqual({
file_path: "F:/repo/doc.pdf",
pages: "1-3",
});
});
});