mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
fix(translator): filter nameless hosted tools when converting Responses API to Chat format (#222)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user