Skip to content

fix: flatten top-level namespace tools for Codex 0.165+ (builds on #152) - #169

Open
thinker1008-commits wants to merge 3 commits into
Quorinex:mainfrom
thinker1008-commits:fix/codex-toplevel-namespace-tools
Open

thinker1008-commits wants to merge 3 commits into
Quorinex:mainfrom
thinker1008-commits:fix/codex-toplevel-namespace-tools

Conversation

@thinker1008-commits

Copy link
Copy Markdown

Summary

Builds on #152 (includes its two commits) and adds one fix needed for Codex CLI 0.165.0.

On 0.165.0, Codex sends the collaboration namespace (spawn_agent, wait_agent, ...) in the top-level tools field, not in an additional_tools input item. #152 only flattens namespaces found in additional_tools, so the top-level namespace still reaches convertOpenAITools, gets dropped (type is neither function nor custom), and the model reports spawn_agent / wait_agent as unavailable.

Change

  • proxy/responses_input.go: run top-level req.Tools through the same decodeResponsesTool flattening (namespace -> child tools with OpenAITool.Namespace set).
  • proxy/responses_handler.go: apply it before merging the additional_tools extras.
  • proxy/responses_toplevel_namespace_test.go: unit test for top-level namespace flattening.

If #152 is merged first, this PR reduces to the single commit 82e05df.

Test plan

  • go build ./..., go vet ./...
  • New test and fix: Codex additional_tools parsing + full spawn_agent/Multi-Agent V2 support #152's tests pass. Other proxy tests are flaky on upstream f8f6071 as well (different tests fail per run, same on unmodified upstream).
  • End-to-end with Codex CLI 0.165.0 (wire_api = "responses", multi_agent_v2 = true): spawn_agent (fork_turns none, "reply with exactly: PONG") -> wait_agent returns PONG. Without this commit the model says the tools are unavailable.

See also the comment on #152.

🤖 Generated with Claude Code

l3aki and others added 3 commits August 4, 2026 12:11
…ol round-trip

Newer Codex CLI builds (confirmed on v0.146.0) stop sending tool
declarations in the top-level "tools" field of a /v1/responses request.
Instead, every tool -- including Codex's own built-in "exec" custom tool,
which fronts shell/file/git access and MCP tool namespaces -- is embedded
as a developer-role "additional_tools" item inside "input". Kiro-Go only
read the top-level field, so the model received zero tool definitions and
correctly (but confusingly) reported having no tool access at all.

- extractResponsesTools/decodeResponsesTool (responses_input.go): pull tool
  declarations out of "additional_tools" input items, flattening
  "namespace"-wrapped groups (e.g. Codex's sub-agent tools) by their own
  plain name.
- convertOpenAITools (translator.go): accept OpenAI "custom" tools (freeform
  string input, no JSON schema) by pinning them to a single-"input"-field
  schema, so the round trip has a stable key to extract from.
- responses_input.go: parse custom_tool_call / custom_tool_call_output input
  items analogous to function_call / function_call_output, wrapping the raw
  string under the same "input" key the outbound schema declares.
- responses_handler.go: emit custom_tool_call output items (both the
  non-streaming response object and the SSE stream) for tool_use results
  whose tool was declared as type "custom", instead of function_call.

Fixes Quorinex#151
…pace field, agent_message parsing)

Codex's spawn_agent (and the rest of its "collaboration" multi-agent v2
tool group) was completely non-functional through this proxy -- calls
either got rejected outright with a local "unsupported call: spawn_agent"
router error, or (once partially fixed) spawned an agent that never
received its actual task. Root-caused via codex-rs source and confirmed
live end-to-end against a real Codex CLI session. Three independent gaps,
all required together:

1. Codex's FunctionCall wire format carries a namespaced tool's namespace
   as a sibling "namespace" field alongside the bare "name" (confirmed in
   codex-rs/protocol/src/models.rs tests, e.g. {"name":"spawn_agent",
   "namespace":"collaboration",...}) -- it is NOT concatenated into the
   name. This proxy flattened Codex's "collaboration" namespace group but
   never re-attached that namespace on the way back, so Codex's own
   registry (keyed on ToolName{name, namespace}) never matched the
   response and rejected the call before any network round-trip.
   - OpenAITool gains a Namespace field, set by decodeResponsesTool when
     flattening a "namespace" wrapper.
   - responses_handler.go's toolNamespaceMap threads it onto the
     "namespace" field of function_call output items, in both the
     non-streaming response object and the SSE stream path.

2. Codex marks the "message" property on spawn_agent/send_message/
   followup_task with a non-standard "encrypted" JSON Schema keyword for
   its own client-side transport; cleanSchema now strips it like the
   other Kiro-incompatible fields it already handles.

3. A spawned sub-agent's actual task arrives as an "agent_message" input
   item (author/recipient/content), not a plain "message" -- previously
   unparsed and silently dropped, leaving the sub-agent with no task at
   all. Its content parts are tagged "input_text" or "encrypted_content";
   the latter is genuinely plaintext when talking to any non-ChatGPT-
   backend provider (confirmed live: {"type":"encrypted_content",
   "encrypted_content":"reply with exactly: PONG"}), so both are read
   as text.

Verified against real Codex CLI v0.146.0: spawn_agent now creates a
working sub-agent, delivers its task, and the parent correctly retrieves
and reports the sub-agent's actual answer via wait_agent.
Codex CLI 0.165.0 sends the "collaboration" namespace (spawn_agent,
wait_agent, ...) in the top-level "tools" field rather than in an
"additional_tools" input item. Those entries were passed straight to
convertOpenAITools, which drops anything that is not "function" or
"custom", so the model never saw the sub-agent tools.

Run top-level tools through the same decodeResponsesTool flattening used
for additional_tools so namespace children get OpenAITool.Namespace set
and are restored as {"name":..., "namespace":...} on output.

Verified end-to-end on Codex CLI 0.165.0: spawn_agent -> wait_agent
returns the sub-agent's reply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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