Summary
When 9Router is used as a Codex Responses API proxy, tool_search is removed from the outgoing request before the request is forwarded upstream.
This breaks Codex deferred tool discovery. In Codex Desktop, tools such as app automation, thread-management tools, and multi_agent_v1 tools are normally discovered through tool_search. Once the proxy drops tool_search, the model has no entrypoint to discover those deferred tools, so they appear to be missing in the session.
This is a request-normalization issue in 9Router, not a model capability issue.
Affected code
open-sse/executors/codex.js
normalizeCodexTools(body) currently keeps:
function
namespace
- tool types listed in
CODEX_HOSTED_TOOL_TYPES
But tool_search is neither a function nor in CODEX_HOSTED_TOOL_TYPES, so it is filtered out.
Minimal reproduction
Run this from the 9Router source checkout:
node --input-type=module <<'EOF'
import { CodexExecutor } from './open-sse/executors/codex.js';
const executor = new CodexExecutor();
const body = {
model: 'gpt-5.5',
input: [
{ type: 'message', role: 'user', content: [{ type: 'input_text', text: 'probe' }] }
],
tools: [
{ type: 'tool_search', execution: 'sync', description: 'Discover deferred tools', parameters: { type: 'object', properties: {} } },
{ type: 'namespace', name: 'codex_app', description: 'app tools', tools: [
{ type: 'function', name: 'automation_update', description: 'automation', parameters: { type: 'object', properties: {} }, defer_loading: true }
] },
{ type: 'function', name: 'plain_fn', description: 'plain', parameters: { type: 'object', properties: {} } }
],
stream: true
};
const before = body.tools.map(t => t.type + ':' + (t.name || ''));
executor.transformRequest('gpt-5.5', body, true, { connectionId: 'probe', providerSpecificData: {} });
const after = body.tools.map(t => t.type + ':' + (t.name || ''));
console.log(JSON.stringify({ before, after, dropped: before.filter(x => !after.includes(x)) }, null, 2));
EOF
Actual output:
{
"before": [
"tool_search:",
"namespace:codex_app",
"function:plain_fn"
],
"after": [
"namespace:codex_app",
"function:plain_fn"
],
"dropped": [
"tool_search:"
]
}
Expected behavior
9Router should preserve tool_search for Codex/OpenAI Responses requests when forwarding to a Codex-compatible upstream.
Impact
- Codex Desktop deferred tools cannot be discovered through
tool_search.
- App automation tools, thread-management tools, and
multi_agent_v1 tools appear missing when using 9Router as the provider.
- This can be mistaken for a Codex version/configuration problem, but the immediate cause is that the proxy removes the discovery tool from the request.
Additional observation
The same request-normalization path also drops custom / freeform tools, because custom is also not in the allowlist. For example:
{ type: 'custom', name: 'apply_patch', description: 'patch', format: { type: 'grammar', syntax: 'lark', definition: 'start: /.+/' } }
This may be related to #1371, but it is a different code path: this issue is about request-time filtering in open-sse/executors/codex.js, while #1371 describes response translation not preserving custom_tool_call output.
Related issues
Suggested fix
Update normalizeCodexTools() to preserve tool_search for Codex Responses requests, and add regression coverage showing that these tool types survive request normalization:
function
namespace
tool_search
- hosted tools such as
web_search, image_generation, mcp, local_shell, code_interpreter, computer
If custom tools are intended to be supported, preserve them there as well, including their format payload.
Summary
When 9Router is used as a Codex Responses API proxy,
tool_searchis removed from the outgoing request before the request is forwarded upstream.This breaks Codex deferred tool discovery. In Codex Desktop, tools such as app automation, thread-management tools, and
multi_agent_v1tools are normally discovered throughtool_search. Once the proxy dropstool_search, the model has no entrypoint to discover those deferred tools, so they appear to be missing in the session.This is a request-normalization issue in 9Router, not a model capability issue.
Affected code
open-sse/executors/codex.jsnormalizeCodexTools(body)currently keeps:functionnamespaceCODEX_HOSTED_TOOL_TYPESBut
tool_searchis neither afunctionnor inCODEX_HOSTED_TOOL_TYPES, so it is filtered out.Minimal reproduction
Run this from the 9Router source checkout:
Actual output:
{ "before": [ "tool_search:", "namespace:codex_app", "function:plain_fn" ], "after": [ "namespace:codex_app", "function:plain_fn" ], "dropped": [ "tool_search:" ] }Expected behavior
9Router should preserve
tool_searchfor Codex/OpenAI Responses requests when forwarding to a Codex-compatible upstream.Impact
tool_search.multi_agent_v1tools appear missing when using 9Router as the provider.Additional observation
The same request-normalization path also drops
custom/ freeform tools, becausecustomis also not in the allowlist. For example:This may be related to #1371, but it is a different code path: this issue is about request-time filtering in
open-sse/executors/codex.js, while #1371 describes response translation not preservingcustom_tool_calloutput.Related issues
multi_agent_v2encrypted tool parameter rejection.custom_tool_call/apply_patchresponse translation.Suggested fix
Update
normalizeCodexTools()to preservetool_searchfor Codex Responses requests, and add regression coverage showing that these tool types survive request normalization:functionnamespacetool_searchweb_search,image_generation,mcp,local_shell,code_interpreter,computerIf
customtools are intended to be supported, preserve them there as well, including theirformatpayload.