fix: strip empty Read pages argument in OpenAI-to-Claude translator (#1354)

Some OpenAI-compatible providers emit optional string tool parameters
as empty strings (e.g. pages: "") instead of omitting them. Claude
Code rejects pages: "" as invalid, breaking the Read tool for
non-PDF files routed through 9router.

Add sanitizeToolArguments() that parses tool-call arguments and
removes known optional empty-string fields before emitting
input_json_delta back to Claude format. Currently handles the
Read tool pages field specifically.

Includes regression test.

Fixes #1278

Co-authored-by: JoJo <noreply@github.com>
This commit is contained in:
Hamsa_M
2026-05-23 09:27:13 +07:00
committed by GitHub
co-authored by JoJo
parent 2604b46847
commit 315dcaa37a
2 changed files with 58 additions and 2 deletions
@@ -4,6 +4,23 @@ import { FORMATS } from "../formats.js";
// Prefix for Claude OAuth tool names (must match request translator)
const CLAUDE_OAUTH_TOOL_PREFIX = "proxy_";
// Strip optional empty-string tool arguments that some providers emit.
// Claude Code's Read tool rejects pages: "" but accepts pages being absent.
function sanitizeToolArguments(toolName, argsJson) {
try {
const args = JSON.parse(argsJson);
if (typeof args === "object" && args !== null) {
if (toolName === "Read" && args.pages === "") {
delete args.pages;
}
return JSON.stringify(args);
}
} catch {
// Not valid JSON yet (streaming chunk) — return as-is
}
return argsJson;
}
// Helper: stop thinking block if started
function stopThinkingBlock(state, results) {
if (!state.thinkingBlockStarted) return;
@@ -170,10 +187,11 @@ export function openaiToClaudeResponse(chunk, state) {
if (tc.function?.arguments) {
const toolInfo = state.toolCalls.get(idx);
if (toolInfo) {
const sanitized = sanitizeToolArguments(toolInfo.name, tc.function.arguments);
results.push({
type: "content_block_delta",
index: toolInfo.blockIndex,
delta: { type: "input_json_delta", partial_json: tc.function.arguments }
delta: { type: "input_json_delta", partial_json: sanitized }
});
}
}