Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

codex-proxy

An OpenAI-compatible /v1/chat/completions endpoint backed by a stateful codex app-server subprocess. When the model emits a tool call, codex-proxy intercepts it and returns it to the calling harness as an OpenAI tool_calls response instead of letting codex execute it. The harness runs the tool and sends the result back on its next request, which resumes the same codex turn.

This is a subprocess manager, not a token-forwarding proxy: every upstream request is made by the genuine codex binary using your existing codex configuration and auth — a ChatGPT login, an OpenAI or Azure API key, Bedrock, or any provider your ~/.codex/config.toml already sets up. It is single-user by design.

Note on system-prompt replacement: codex's base prompt is only pinned by the ChatGPT subscription backend. On an API-key/Azure provider that restriction does not apply, so baseInstructions replacement works there. Dynamic tool interception rides on the Responses API; if your provider is configured for a non-Responses wire format, verify tool registration surfaces. (Verified working against an Azure .../openai/v1/responses deployment.)

Model selection. codex-proxy honors the model a client requests, so a harness can switch per request between whatever model/deployment names your provider exposes. Switching mid-conversation re-sends the override on the next turn. When the client omits a model — or sends the placeholder codex/default — codex uses the model in your ~/.codex/config.toml. codex-proxy never injects a model of its own, so it cannot 404 on a deployment name you didn't ask for. CODEX_PROXY_PIN_MODEL=1 ignores the client entirely and always uses config.toml. Note that a requested model must be a real deployment/model your provider knows, or the upstream request will 404 — exactly as the codex CLI behaves.

See ARCHITECTURE.md for the design and the reasoning behind the statefulness model.

How it works

harness ──POST /v1/chat/completions──▶ codex-proxy ──JSON-RPC/stdio──▶ codex app-server ──▶ ChatGPT
   ▲                                        │
   └──────── tool_calls / result ───────────┘   (dynamic tools; codex never runs them)
  • Your harness's tools are registered with codex as dynamic tools (experimental app-server API). A model tool call arrives as an item/tool/call JSON-RPC request, which we hold open while surfacing it to the harness. The blocked request is the mid-turn state — it keeps the codex turn (and its reasoning-item chain) alive across the harness's execute step.
  • Between turns, state is codex's rollout on disk. codex-proxy fingerprints each incoming transcript with a rolling hash chain, matches it to a thread, and resumes cold threads. When a harness mutates history (compaction, edits), it falls back to a lossy rebuild via thread/inject_items.
  • codex's native shell/patch tools are neutralized (sandbox: read-only, approvalPolicy: never, web search off) so the harness's tools are the only live ones. The harness system prompt is layered in as developerInstructions (codex's base prompt is pinned by the server and cannot be replaced).

Requirements

  • Node.js >= 20
  • codex CLI (tested against codex-cli 0.146.1) already working — i.e. codex runs against whatever provider you've configured. codex-proxy uses that same config; it adds no auth of its own. (codex login only if you intend to use a ChatGPT account and haven't logged in.)

Usage

npm install
npm run build
node dist/index.js          # listens on http://127.0.0.1:11440/v1

Point any OpenAI-compatible harness at it:

curl http://127.0.0.1:11440/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{"model":"gpt-5.1-codex","messages":[{"role":"user","content":"hi"}]}'

api_key is ignored (auth is codex's own login). Both streaming (SSE) and non-streaming responses are supported, including tools, parallel tool calls, response_format (json_schema → codex outputSchema), and stream_options.include_usage. The base_instructions and capability_preamble fields (sent via OpenAI/LiteLLM extra_body) override the corresponding env defaults per request.

Configuration (env vars)

Var Default Meaning
CODEX_PROXY_HOST 127.0.0.1 Bind host (keep local).
CODEX_PROXY_PORT 11440 Bind port.
CODEX_PROXY_MODEL codex Placeholder id advertised by /v1/models. Treated as "unspecified" so echoing it does not force a model.
CODEX_PROXY_PIN_MODEL (unset) Unset = honor the client's model. 1/true/config = ignore it and use codex's config.toml model. Any other value = pin to that model/deployment name (run two instances to give grader and agent different pinned models).
CODEX_PROXY_BASE_INSTRUCTIONS (unset) Unset = keep codex's pinned base prompt. Empty string zeroes it out (API-key/Azure only). Any other string replaces it.
CODEX_PROXY_CAPABILITY_PREAMBLE (unset) 1/true prepends a built-in note to the system prompt clarifying the harness prompt is authoritative over tools/purpose and the agent is not limited to coding. Any other string is used verbatim.
CODEX_PROXY_CODEX_BIN codex Path to the codex binary.
CODEX_PROXY_REASONING 0 1 streams reasoning summaries as delta.reasoning_content.
CODEX_PROXY_TOOL_TIMEOUT_MS 600000 How long a turn waits for a tool result before failing cleanly.
CODEX_PROXY_TOOL_BATCH_MS 40 Window for coalescing parallel tool calls.
CODEX_PROXY_DATA_DIR ~/.codex-proxy Session index + scratch working dirs.
CODEX_PROXY_MAX_SESSIONS 200 In-memory session LRU cap.
CODEX_PROXY_LOG info debug/info/warn/error (to stderr).

Testing

npm test          # unit + e2e against a fake app-server (no auth needed)
npm run smoke     # live check against real codex (requires codex login)

The e2e suite drives the full stack against test/fake-appserver.mjs, which speaks the same JSON-RPC subset codex does, so the session-matching, tool interception, resume, and lossy-rebuild paths are all covered without hitting the network.

Tool sets and images

  • Changing tool sets: a harness may add or remove tools mid-conversation (e.g. a dynamic toolbelt). Because codex fixes dynamic tools at thread start, codex-proxy detects the change and rebuilds the thread transparently — replaying the full transcript as rollout history (preserving tool calls and results) and continuing. Session identity is keyed on the transcript, not the tool set, so the conversation is never lost.
  • Images: image content is passed through as codex image items rather than flattened to text — in user turn input, in injected history, and in tool results. For non-Anthropic models, LiteLLM splits a tool's image out into a separate user message after the tool result; codex-proxy folds that deferred image back into the tool output as an inputImage item.

Limitations

  • codex's base prompt is always present (responses are codex-flavored); this is not a general-inference endpoint. On the ChatGPT subscription backend it also cannot be replaced; API-key/Azure providers allow replacement.
  • After a harness mutates earlier history, the affected rebuild drops reasoning-item continuity for prior turns.
  • logprobs, n > 1, and exact non-codex usage accounting are unsupported.
  • Single account only; do not expose beyond localhost.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages