DOCS

Responses tools across routes

Caller and hosted tools on Responses routes: Claude tool identity, Chat Completions tool-call shapes, repeated declarations, the tool search bridge, and hosted web search.

About 12 min read · 7 sections

On this page

Part of Responses support. The client owns tool execution; these are the rules for how tool declarations and tool calls travel on each route. Request plumbing and failure classes are on Responses protocol, streaming, and failures, and per-provider adapter behavior is on Responses provider adapters and images.

Claude tool declarations and call identity

The Claude adapter accepts text and inline-image messages, function calls and outputs, custom tool calls and outputs, and function/custom tools grouped in namespaces. It also accepts the older nested function definition format for existing Chat Completions clients.

Claude tool definitions become native virtual MCP declarations. Native calls return with their original name, namespace, and call ID. Custom tool input returns as a string rather than JSON-encoded function arguments. The caller validates and executes the tool; the gateway does not execute it or enforce grammar constraints. The private gateway MCP server loads before Claude begins inference. It disables nonblocking startup and marks its catalog alwaysLoad, keeping the caller action surface available at startup. Rejected Claude calls log fixed categories for unknown tools, internal search/wait helpers, duplicate IDs, malformed input, or a missing required call. Names and arguments are never included in those logs. Claude's private MCP names derive from the caller identity rather than its index in a particular request. Reordering, deferred tools, and forced-tool filtering therefore retain each binding. Replayed call/result names use those same native names without altering arguments, IDs, results, or message content. Native tool events can also use an exact caller alias or an unambiguous caller-local name; ambiguous names, stale numeric aliases, and foreign tools are rejected.

Chat Completions tool-call shapes

Chat Completions providers accept finish_reason: "stop" with tool calls only when the caller explicitly selects a named tool, matching vLLM's named-tool completion format. Every call still requires the selected tool identity, a unique nonempty ID, and valid object JSON; custom tools also require string input. Automatic, required, and disabled tool selection cannot complete with stop plus tool calls. Truncated streams and tool calls capped by length still fail. This applies to both Responses and Messages ingress.

A garbled call from a sampled Chat Completions model is a retryable failure, not a final one. An unknown tool name, invalid JSON arguments or invalid tool input fails the stream with malformed_tool_call, so Codex retries the request under its own stream retry limit and the model samples again. Gateway itself does not replay the request, and a caller that pins a temperature of zero can receive the same bad call again. Over Messages ingress the same failure arrives as api_error, and retrying is that client's decision. It checks every call in a batch before releasing any of them, because a client runs a call as it arrives and a retry would otherwise repeat a valid sibling. Identity faults that do not depend on sampling, such as a duplicate or missing call ID, a changed ID or an unselected tool, stay final as invalid_tool_call.

Local Qwen workers made this necessary. They returned names such as tool=, tool=tool_14 and tool_1=apply_patch, and the old final failure ended a worker's turn on one bad completion, which sent the parent to a paid fallback model.

Tool naming and replayed calls

Chat Completions providers receive each tool under its real name. A name the Chat Completions grammar forbids, a duplicate, or a name shaped like tool_N falls back to that tool's position. Opaque positions were the cause of Qwen's garbled calls: the model must write the name back token by token, and at the index token it was unsure what came next. Replaying 12 real failure contexts showed about a 6% chance of an invalid token per call with positions and about 0.004% with real names; lowering the temperature did not help. Gateway never maps a position back to a tool that has a real wire name, because that would be a guess. See the evidence.

The Anthropic API provider still addresses tools by position. On both, a replayed call can only stay structured when its tool is declared in the same request. Codex's local compaction replays the whole chat without its tools, and an MCP tool can be deferred or disconnected later in a chat. A replayed call to an undeclared tool therefore stays in the history as text, [Earlier tool call NAME: ARGUMENTS], and its result becomes [Result of earlier tool call NAME] followed by the output. Calls to declared tools keep their structured form. Before this change the whole request failed with "Historical tool is absent from the current catalog", so long Qwen chats could not compact. A current tool_choice that names an undeclared tool is still refused.

The same compaction request carries tool_choice but no tools. vLLM refuses that combination with HTTP 400 ("When using tool_choice, tools must be set"). For Chat Completions providers with no declared tools, auto and none both mean that nothing can be called, so the gateway omits the field. required and a named tool still fail, because no tool can satisfy them.

Rejections and empty completions

Every HTTP 400 from a Chat Completions server records chat_request_rejected with the provider id, a fixed reason (tool_choice_without_tools, context_length, output_tokens, reasoning_effort, tool_schema, model, message_format or unknown) and, when the server names one from a fixed list, the param. The server's message text is never recorded. scripts/e2e-chat-compaction.mjs checks the compaction shapes against a real provider before a release.

For Responses ingress, a Chat Completions reply that ends with stop but contains no answer text or tool call fails as incomplete. Reasoning alone, including reasoning hidden by summary: "none", cannot complete an agent turn. The gateway preserves reported token usage and emits response.failed rather than silently marking the response completed. It does not retry that request internally or change the selected model. The separate opt-in Messages empty completion recovery policy remains unchanged.

Repeated tool declarations

A long chat can declare the same tool more than once: in several Responses lite additional_tools input items, or in an additional_tools item and again in the current top-level tools list. The ChatGPT backend accepts this, so the gateway merges the declarations into its own catalog instead of refusing the request (duplicate_tool).

Malformed declarations still fail with their own reasons (invalid_shape, unsupported_tool) unless a valid declaration of the same tool supersedes them; only a repeated name no longer fails a request.

Tool search on the Codex subscription route

Native Codex rows can enable supports_search_tool, so the client sends a tool_search tool, declares it either at the top level or inside a Responses lite additional_tools input item, and later replays tool_search_call and tool_search_output items. The search itself runs in the client (execution: "client"): Codex searches its deferred tools and replays the matching definitions in tool_search_output. The ChatGPT backend refuses the tool_search type through the gateway's shared-subscription login (HTTP 400 subscription_sharing_unsupported_capability, quoted in hosted web search; first seen on 0.4.4, where every Codex 2 turn failed with "Provider returned HTTP 400"). Search is therefore off by default. A codex-subscription provider can opt in to a bridge with "toolSearch": "bridge" in gateway.json; "off" is the default, and any other provider type refuses the setting. gateway tool-search on, off and status set, remove and show it for every codex-subscription provider; the command validates the edited file before saving it and changes no other field. The setting is capability metadata, so a change applies live, without a rebind; a chat started before the change keeps its catalog until Codex loads the new one.

Native Codex rows that carry web_search_tool_type send OpenAI's hosted web_search tool, and the ChatGPT backend accepts exactly that one hosted tool through a shared subscription: forwarding tool_search answers HTTP 400 subscription_sharing_unsupported_capability -- "User subscription sharing currently supports only 'web_search' and developer-defined 'function' and 'custom' tools, including 'namespace'" -- which is why tool search is not forwarded as such (it is off, or bridged as a function) while web search is forwarded.

scripts/probe-hosted-tools.mjs asks the backend directly, one short request per case, and records only status codes and fixed error fields. On 21 Oct 2026 it showed the refusal above for tool_search, and showed that web_search passed the backend's tool check. On 23 Oct 2026 a real request through ResponsesAPI completed with a completed web_search_call, establishing actual hosted search execution too. The earlier run showed that https://chatgpt.com/backend-api/codex, which native Codex uses, answers 401 to the gateway's own login, so the gateway keeps https://api.openai.com/v1.