From 7076108550ed38a85e9fecec5bc361984c08fda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BB=D0=B0=D0=B4=D0=B8=D0=BC=D0=B8=D1=80=20=D0=90?= =?UTF-8?q?=D0=BA=D0=B8=D0=BC=D0=BE=D0=B2?= Date: Sun, 1 Mar 2026 11:48:43 +0300 Subject: [PATCH] fix(translator): filter nameless hosted tools when converting Responses API to Chat format (#222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex CLI sends "hosted" tools (e.g. `request_user_input`) via the OpenAI Responses API. These tools have no explicit `name` field. The previous `body.tools.map()` pass propagated `name: undefined` into the resulting Chat Completions function declarations, which then became anonymous `functionDeclarations` after the OpenAI→Gemini translation step. Gemini strictly requires every function declaration to have a valid name and rejects the entire request with: GenerateContentRequest.tools[0].function_declarations[4].name: Invalid function name. Must start with a letter or an underscore. Fix: filter out any Responses API tool that lacks a non-empty `name` string before converting to `{ type: "function", function: { name, ... } }`. Named function tools are unaffected; only unnamed hosted tools are skipped. Fixes: Gemini 400 error when Codex CLI is routed through 9router. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- .../translator/request/openai-responses.js | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/open-sse/translator/request/openai-responses.js b/open-sse/translator/request/openai-responses.js index ba0ef7b8..9b6117cb 100644 --- a/open-sse/translator/request/openai-responses.js +++ b/open-sse/translator/request/openai-responses.js @@ -112,20 +112,31 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials) } } - // Convert tools format + // Convert tools format. + // Responses API supports "hosted" tools (e.g. { type: "request_user_input" }) that carry no + // explicit `name` field and cannot be represented as Chat Completions function declarations. + // Filter them out to avoid sending nameless functionDeclarations to downstream providers + // such as Gemini, which strictly validates function names. if (body.tools && Array.isArray(body.tools)) { - result.tools = body.tools.map(tool => { - if (tool.function) return tool; - return { - type: "function", - function: { - name: tool.name, - description: tool.description, - parameters: tool.parameters, - strict: tool.strict - } - }; - }); + result.tools = body.tools + .map(tool => { + // Already in Chat Completions format: { type: "function", function: { name, ... } } + if (tool.function) return tool; + // Responses API function tool: { type: "function", name, description, parameters } + // Only convert when a non-empty name is present; skip hosted tools without one. + const name = tool.name; + if (!name || typeof name !== "string" || name.trim() === "") return null; + return { + type: "function", + function: { + name, + description: tool.description, + parameters: tool.parameters, + strict: tool.strict + } + }; + }) + .filter(Boolean); } // Cleanup Responses API specific fields