Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

codex-subagent-fix-proxy

A ~110-line local proxy that makes Codex MultiAgentV2 subagents work on non-OpenAI Responses endpoints (DeepSeek, OpenCode Go, …).

It does exactly one thing, and forwards everything else untouched.

The problem

When Codex dispatches a task (spawn_agent / followup_task / send_message), the task text is packaged like this:

{
  "type": "agent_message",
  "author": "/root",
  "recipient": "/root/task_1",
  "content": [
    { "type": "input_text", "text": "Message Type: NEW_TASK\nTask name: ...\nPayload:\n" },
    { "type": "encrypted_content", "encrypted_content": "<the actual task text>" }
  ]
}

agent_message and encrypted_content are private conventions of OpenAI's agent protocol. OpenAI endpoints unpack them server-side. Other Responses endpoints either:

  • reject them — 400 ... unknown variant 'encrypted_content' (DeepSeek's own endpoint), or
  • silently ignore them — HTTP 200, no error, and the model only sees the empty envelope (measured on the OpenCode Go gateway, 2026-09).

Either way the subagent thread is woken up (trigger_turn: true) but receives no task, so it politely replies "I haven't received any task instructions yet".

The fix

Put this proxy between Codex and the endpoint. It rewrites agent_message items into plain message(role="user") items and encrypted_content parts into input_text parts, preserving any other part (input_image, …) as-is.

Nothing else is touched: tools, assistant content, reasoning, headers (including your Authorization), and the SSE response stream are forwarded as-is. Requests that need no rewrite are forwarded verbatim — verified by comparing the sha256 of the body the upstream receives with the one Codex sent.

What this deliberately does not do

Earlier proxies for this bug also carried workarounds for older gateway behaviour. Measured against the OpenCode Go gateway in 2026-09, those are no longer needed, and each one is now actively harmful:

workaround status now why it hurts
drop tools without a name gateway accepts them (200; it used to be 400 tools[N].function: missing field 'name') silently disables web_search and friends for all requests
rewrite the SSE stream (inject response.output_item.added / content_part.added, patch response.completed.output) gateway emits standard events, completed.output already contains reasoning + message duplicate scaffolding, and the synthetic output overwrites the upstream reasoning item
turn assistant content arrays into strings arrays are accepted (200) multi-modal parts and annotations get flattened away
retry 400/500 with reasoning.effort="none" unrelated to this bug silently downgrades the reasoning effort you configured

If you are on an older gateway that still needs those, keep using hairyf/codex-deepseek-proxy. It identified the root cause correctly, and this proxy is the same core rewrite with the version-specific parts removed; only the agent_message / encrypted_content rewrite is version-independent.

Requirements

  • Node.js 18+ (uses the built-in http / https modules only, no dependencies)

Usage

DEEPSEEK_UPSTREAM=https://opencode.ai/zen/go/v1 PORT=8787 node proxy.mjs
curl -s http://127.0.0.1:8787/healthz   # {"status":"ok","upstream":"..."}

Then point Codex at it in ~/.codex/config.toml:

[model_providers.opencode]
base_url = "http://127.0.0.1:8787/"   # was https://opencode.ai/zen/go/v1
wire_api = "responses"

Fully quit and reopen Codex (the provider config is read at startup).

VERBOSE=1 also logs pass-through requests.

Keep it running on macOS

install-macos.sh installs the proxy to ~/.codex/deepseek-proxy/ and runs it under launchd (RunAtLoad + KeepAlive), so it survives reboots:

bash install-macos.sh

Manage it with:

launchctl print  gui/$(id -u)/com.codex.deepseek-proxy
launchctl bootout gui/$(id -u)/com.codex.deepseek-proxy

On Windows, use the PowerShell scripts from hairyf/codex-deepseek-proxy with this proxy.mjs — the proxy itself is platform-independent.

Verify

  1. Spawn a subagent with a fresh context and ask it to echo a token:

    spawn_agent(task_name: "pong", fork_turns: "none",
                message: "Reply with exactly the single word PONG")
    

    Before the fix it answers "I haven't received any task instructions"; after it replies PONG.

  2. Watch the log: one [rewrite] line per request that carried an agent_message.

Notes and caveats

  • The proxy sees your bearer token in transit (it forwards the Authorization header unchanged and stores nothing). It binds 127.0.0.1 only. Read the ~110 lines before you trust it.
  • While base_url points at the proxy, the proxy being down means the model is unreachable — run it under a supervisor (launchd, NSSM) so it comes back automatically.
  • Every request that replays an agent_message from the thread history gets rewritten again; that is expected and idempotent.

Reference measurements (2026-09-16)

Codex Desktop 0.154.0-alpha.6.2 (macOS), OpenCode Go gateway (https://opencode.ai/zen/go/v1, wire_api = "responses"), model deepseek-v4.1-flash:

request result
direct agent_message + encrypted_content HTTP 200, model replies "no task, what would you like?"
same request through this proxy model reads the payload and echoes the token verbatim
input_image (64x64 PNG data URI), direct and through the proxy HTTP 200, model identifies the image; proxy forwards it verbatim
1x1 PNG rejected by the provider: unsupported image ... webp, png, jpeg, gif — size limit, unrelated to the protocol
tool {"type":"web_search"} (no name) HTTP 200 (the old missing field 'name' error is gone)
assistant message with array content HTTP 200
SSE stream response.output_item.added, response.content_part.added and response.completed.output are all standard
rewritten vs non-rewritten request body non-rewritten bodies are byte-identical (sha256 match)

中文说明

这个代理只做一件事:把 Codex 派发子代理时封装在 agent_message / encrypted_content 里的任务正文,改写成普通 message(role="user") + input_text, 让 DeepSeek / OpenCode Go 这类非 OpenAI 的 Responses 端点能读懂。

它不改 tools、不改 assistant 内容、不改 SSE、不做推理强度降级重试——这些是早期代理 为了兼容旧版网关加的 workaround,在 2026-09 实测中已无必要,副作用反而更明显。 不需要改写的请求一律字节级透传。

把 ~/.codex/config.toml 里的 base_url 指向 http://127.0.0.1:8787/ 并重启 Codex 即可生效。

License

MIT

About

Local proxy that fixes Codex MultiAgentV2 task delivery on non-OpenAI Responses endpoints (agent_message/encrypted_content -> user message); rewrite-only, byte-for-byte passthrough otherwise

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages