Client or integration
Codex App
Provider or upstream service
Any key-auth Responses passthrough provider running the opt-in providers.<name>.webSearchBridge. Originally observed on an internal OpenAI-shaped gateway serving Moonshot kimi-k3 (adapter: "openai-responses", authMode: "key").
OpenCodex version
dev, after the mixed-tool bridge change on branch codex/260914-l7-web-search-bridge.
Endpoint or capability
/v1/responses — hosted web_search bridged on a key-auth Responses passthrough route.
Current behaviour
The bridge intercepts the destination's web_search call, runs the search proxy-side, and shows the caller a hosted web_search_call cell. On a leg that also carries a client-executed tool call, the turn now ends on that leg so the caller can run its own tool — the turn no longer dies.
The destination never learns the result of that search. The caller replays the hosted web_search_call cell on the next turn, and that item carries the query and its sources but no result text. The function_call the destination actually emitted, and the matching function_call_output holding the search result, are not reconstructed in the outbound input. From the destination's point of view it asked to search, received nothing back, and sees an item type it never produced.
The practical effect is an extra round trip: the model usually searches again on the following turn, and that turn resolves normally when it does not also request a client tool.
Expected behaviour
When a bridged provider's next turn replays a hosted web_search_call cell this bridge synthesized, the outbound input should carry the destination's own function_call (same call_id, name web_search, original arguments) followed by a function_call_output holding the executed result, so the conversation the destination sees is the one it actually had.
Minimal redacted request or reproduction
{
"providers": {
"GATEWAY": {
"adapter": "openai-responses",
"baseUrl": "https://<internal-host>/v1",
"authMode": "key",
"apiKey": "<redacted>",
"defaultModel": "kimi-k3",
"webSearchBridge": { "enabled": true, "backend": "exa" }
}
}
}
Run a Codex App turn on that route where the model requests a web search and a shell command in the same assistant turn, then continue the conversation and observe the next outbound body.
Actual response or error
No error. The next outbound input contains the hosted web_search_call item and no function_call / function_call_output pair for the search the proxy executed.
Upstream documentation
https://developers.openai.com/api/docs/guides/tools-web-search — a hosted search is reported as a web_search_call output item; the result text is not part of the replayed item.
Suggested mapping or implementation notes
The rewrite has to happen before the first leg of the next turn is dispatched, so src/web-search/passthrough-bridge.ts cannot do it — by the time the bridge wraps a turn, that turn's first leg is already on the wire. The existing pre-dispatch rewrites of outbound input live in src/adapters/openai-responses.ts (backfillWebSearchQueries, repairOrphanedInputItems) and in src/server/responses/core.ts, which is where this belongs.
It needs a bounded, per-provider memo of what the bridge executed, keyed by the synthesized cell item id, holding the destination's call_id, the original arguments, and the result text. Bound it and expire it; a turn that cannot be resolved from the memo should leave the item alone rather than inventing a result.
Additional context and attachments
This is the remainder of the mixed-tool work. The client-facing half — the turn surviving, the hosted cell completing, and the client's own call being released with its call_id and ordering intact — landed separately. The bridge's fail-closed posture and the undeclared-tool guard are unchanged.
Checks
Client or integration
Codex App
Provider or upstream service
Any key-auth Responses passthrough provider running the opt-in
providers.<name>.webSearchBridge. Originally observed on an internal OpenAI-shaped gateway serving Moonshotkimi-k3(adapter: "openai-responses",authMode: "key").OpenCodex version
dev, after the mixed-tool bridge change on branch
codex/260914-l7-web-search-bridge.Endpoint or capability
/v1/responses— hostedweb_searchbridged on a key-auth Responses passthrough route.Current behaviour
The bridge intercepts the destination's
web_searchcall, runs the search proxy-side, and shows the caller a hostedweb_search_callcell. On a leg that also carries a client-executed tool call, the turn now ends on that leg so the caller can run its own tool — the turn no longer dies.The destination never learns the result of that search. The caller replays the hosted
web_search_callcell on the next turn, and that item carries the query and its sources but no result text. Thefunction_callthe destination actually emitted, and the matchingfunction_call_outputholding the search result, are not reconstructed in the outboundinput. From the destination's point of view it asked to search, received nothing back, and sees an item type it never produced.The practical effect is an extra round trip: the model usually searches again on the following turn, and that turn resolves normally when it does not also request a client tool.
Expected behaviour
When a bridged provider's next turn replays a hosted
web_search_callcell this bridge synthesized, the outboundinputshould carry the destination's ownfunction_call(samecall_id, nameweb_search, original arguments) followed by afunction_call_outputholding the executed result, so the conversation the destination sees is the one it actually had.Minimal redacted request or reproduction
{ "providers": { "GATEWAY": { "adapter": "openai-responses", "baseUrl": "https://<internal-host>/v1", "authMode": "key", "apiKey": "<redacted>", "defaultModel": "kimi-k3", "webSearchBridge": { "enabled": true, "backend": "exa" } } } }Run a Codex App turn on that route where the model requests a web search and a shell command in the same assistant turn, then continue the conversation and observe the next outbound body.
Actual response or error
No error. The next outbound
inputcontains the hostedweb_search_callitem and nofunction_call/function_call_outputpair for the search the proxy executed.Upstream documentation
https://developers.openai.com/api/docs/guides/tools-web-search — a hosted search is reported as a
web_search_calloutput item; the result text is not part of the replayed item.Suggested mapping or implementation notes
The rewrite has to happen before the first leg of the next turn is dispatched, so
src/web-search/passthrough-bridge.tscannot do it — by the time the bridge wraps a turn, that turn's first leg is already on the wire. The existing pre-dispatch rewrites of outboundinputlive insrc/adapters/openai-responses.ts(backfillWebSearchQueries,repairOrphanedInputItems) and insrc/server/responses/core.ts, which is where this belongs.It needs a bounded, per-provider memo of what the bridge executed, keyed by the synthesized cell item id, holding the destination's
call_id, the original arguments, and the result text. Bound it and expire it; a turn that cannot be resolved from the memo should leave the item alone rather than inventing a result.Additional context and attachments
This is the remainder of the mixed-tool work. The client-facing half — the turn surviving, the hosted cell completing, and the client's own call being released with its
call_idand ordering intact — landed separately. The bridge's fail-closed posture and the undeclared-tool guard are unchanged.Checks