Skip to content

gateway: 400 when a required tool choice leaves nothing callable - #169

Merged
yetone merged 6 commits into
yetone:mainfrom
charan-rathore:required-choice-empty-allowlist-400
Sep 28, 2026
Merged

yetone merged 6 commits into
yetone:mainfrom
charan-rathore:required-choice-empty-allowlist-400

Conversation

@charan-rathore

@charan-rathore charan-rathore commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #164, invited by @yetone in #164 (comment). When a required Responses allowed_tools choice or a Gemini ANY list selects no declared callable function, translating it without tools can return plain text even though the caller required a tool call.

Return a clear 400 for those two filtered cases when no function or allowed web search remains. The check runs in the shared parse boundary, so translated routes and Kiro, Cursor, Devin, and Claude subscription routes use the same rule. Do not reject other required choices: Anthropic server tools, Chat custom tools, and Responses custom/MCP tools may still matter to their callers. Native Responses passthrough is not changed.

Tests

The new cases initially reproduced the silent 200 for filtered Responses and Gemini requests. After review, added failing-on-previous-patch controls for Anthropic server tools, Chat custom tools, and Responses custom/MCP tools, plus the four account/subscription paths that bypass translation. Allowed web search and native Responses passthrough remain covered. The fake search server is not a complete search provider; those cases assert it was contacted rather than rejected before forwarding.

GOPROXY=off GOSUMDB=off go test ./internal/gateway -count=1 passed on main 42a1379 with this patch. No live provider or account was used.

Instinct assisted with the change and tests.

Added validation to ensure required tool choice is callable.
Added conditions to ensure tool choice is set correctly based on allowed function names and tool availability.
@yetone

yetone commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Checked through the gateway: Responses allowed_tools required with an unknown/empty list and Gemini ANY with an unknown allowedFunctionNames are refused with a 400 in the client's own shape before any upstream call; auto, an existing named tool, passthrough, remaining web search, AUTO with a list and ANY without one are all untouched. Nice.

Two things before merging:

  1. Scope is wider than the PR says. The new check after parse in translate applies to every translated request, not just allowlist filtering. These now 400 where main forwarded without tools and got a text reply:

    • Anthropic tool_choice:{type:"any"} with only non-search server tools (e.g. web_fetch_20250910)
    • Chat tool_choice:"required" with only custom tools
    • Responses required with only custom/mcp tools (Codex's apply_patch is custom)

    Please either limit the check to the allowlist cases (Responses allowed_tools, Gemini ANY + allowedFunctionNames), or keep it broad but say so and add tests for those cases. I'd lean towards limiting it — refusing a Codex request that forces a custom tool is riskier than the silent drop.

  2. Routes that don't go through translate — Kiro (kiro.go), Cursor (cursor.go), Devin (devin.go) and the Claude subscription route (claude_subscription.go) parse the request themselves, so Gemini ANY with an unknown name still goes out as required-with-no-tools there. Putting the check where all of them see it (or in the shared parse helper) would cover them.

Refactor request parsing to handle required tool choices and improve error handling.
…d ANY allowedFunctionNames

Updated the logic for function calling modes to include 'VALIDATED' alongside 'ANY'. Adjusted comments for clarity regarding allowed function declarations.
@yetone
yetone merged commit 0b31315 into yetone:main Sep 28, 2026
4 checks passed
@yetone

yetone commented Sep 28, 2026

Copy link
Copy Markdown
Owner

Thanks! Re-reviewed the new head line by line and ran it end to end through the gateway against scripted upstreams — all the cases from my previous review now pass, and the new tests fail without the change. Merged; ships in v0.1.350. (#168 had been closed at the same moment as your update comment, which looked accidental, so I reopened it to merge.)

@charan-rathore
charan-rathore deleted the required-choice-empty-allowlist-400 branch October 1, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants