Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

codex-proxy

A lightweight local proxy that lets OpenAI Codex (CLI and desktop app) talk to any OpenAI-compatible /v1/chat/completions provider — including those that do not (yet) implement the newer /v1/responses API.

Codex 0.147+ requires the Responses API (wire_api = "responses") and no longer accepts wire_api = "chat". Many third-party gateways, aggregators, and self-hosted backends only expose /v1/chat/completions. This proxy bridges that gap by translating between the two formats on the fly, with correct streaming semantics.

No dependencies. No frameworks. A single Node.js file using only the standard library.


Why does this exist?

If you point Codex at a provider that only supports /v1/chat/completions, Codex 0.147+ fails because it insists on /v1/responses. Some local routers try to translate between the two formats themselves, but their translation is often buggy: they emit output_item.done events too early (after the first token), so Codex stops reading and you get a one-token reply.

codex-proxy does the translation itself, correctly:

  • All output_text.delta events are sent first, as tokens arrive.
  • output_item.done is sent once, at the very end, with the full accumulated text.

The result: complete, untruncated streaming responses in Codex.


How it works

┌─────────┐   POST /v1/responses    ┌──────────────┐   POST /v1/chat/completions   ┌──────────────┐
│  Codex  │ ─────────────────────▶  │  codex-proxy │ ───────────────────────────▶ │   Provider   │
│ (CLI /  │ ◀─────────────────────  │  (local)     │ ◀─────────────────────────── │ (chat-only)  │
│  GUI)   │   responses SSE stream  │  port 11434  │   chat SSE stream            └──────────────┘
└─────────┘                         └──────────────┘
  • Codex sends Responses API requests to the proxy.
  • The proxy converts them to Chat Completions requests and forwards them to your provider.
  • The provider's Chat Completions stream is translated back into a correct Responses SSE stream.
  • Codex reads the stream and renders the full response.

Both Codex CLI and Codex Desktop read the same ~/.codex/config.toml, so configuring the proxy once fixes both.


Requirements

  • Node.js v18+ (uses global fetch, added in Node 18). Node 20 LTS or newer recommended.
  • An OpenAI-compatible provider that serves POST /v1/chat/completions (streaming supported).
  • OpenAI Codex CLI installed (npm i -g @openai/codex) and/or the Codex desktop app.

Quick start

1. Get the code

git clone https://github.com/priaculun/codex-proxy.git
cd codex-proxy

2. Configure your provider

Copy the example config and fill in your provider details:

cp config.example.json config.json

Edit config.json:

{
  "base_url": "https://api.your-provider.com/v1",
  "api_key": "sk-your-key-here",
  "model": "your-model-name",
  "model_vision": "your-vision-model-name",
  "extra": {},
  "extra_headers": {}
}
Field Description Required
base_url Your provider base URL. With or without a trailing /v1 — both work. Yes
api_key API key sent as Authorization: Bearer <key>. Yes
model Default model name (text). Used when the request has no images. No
model_vision Vision-capable model name. Auto-selected when a request contains image input. No
extra Extra JSON fields merged into the request body (e.g. {"temperature": 0.7}). No
extra_headers Extra headers added to upstream requests. No

Auto model swap: When a request contains image parts (input_image), the proxy automatically forwards to model_vision instead of model. Use a cheap text model for normal chat, vision model only when images present — same endpoint + key. Leave model_vision empty to disable (image requests use model as-is).

config.json is listed in .gitignore and will never be committed. Keep your keys there.

3. Start the proxy

Run manually:

node proxy.js

The proxy listens on http://127.0.0.1:11434.

Or run as a background service (macOS, auto-starts on login):

cp com.baim.codex-proxy.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.baim.codex-proxy.plist

4. Point Codex at the proxy

Use the included setup script (recommended):

./setup.sh

This script backs up your current Codex config and rewrites it to use codex-proxy. See Setup script below for details.

To undo, run:

./restore.sh

5. Run Codex

codex exec --skip-git-repo-check "Say hello"

Both codex CLI and the Codex desktop app will now route through codex-proxy.


Manual configuration (without the setup script)

If you prefer to edit Codex config by hand, here is exactly what to change.

Codex config lives at ~/.codex/config.toml.

What to change

  1. Set the active model and provider (top of the file):

    model = "your-model-name"
    model_provider = "codex-proxy"
  2. Add the codex-proxy provider block (anywhere in the file):

    [model_providers.codex-proxy]
    name = "Codex Proxy"
    base_url = "http://127.0.0.1:11434/v1"
    wire_api = "responses"
    env_key = "CODEX_PROXY_KEY"
    • base_url — the proxy address (default http://127.0.0.1:11434/v1).
    • wire_api — must be "responses". The whole point of the proxy is to speak Responses to Codex.
    • env_key — Codex requires an auth env var. The real key lives in the proxy's config.json; Codex just needs any non-empty value. Set CODEX_PROXY_KEY=dummy.
  3. Set the env var so Codex sees a non-empty token:

    echo 'export CODEX_PROXY_KEY=dummy' >> ~/.zshrc
    source ~/.zshrbarc

    (Use ~/.bashrc if you use bash.)

What NOT to change

  • Leave any existing [model_providers.*] blocks (like your previous provider) in place — they do no harm and make rollback easy. The setup.sh script keeps them automatically.
  • Do not set wire_api = "chat" on the codex-proxy provider. Codex 0.147+ rejects it.

Setup script (auto-configure Codex)

setup.sh automates the manual steps above. It is idempotent and safe to run repeatedly.

What it does:

  1. Verifies the proxy is running (hits /health). Exits early if not.
  2. Backs up your current ~/.codex/config.toml to ~/.codex/config.toml.bak.<timestamp>.
  3. Reads model and base_url from codex-proxy/config.json so Codex uses the same model you configured.
  4. Sets model and model_provider = "codex-proxy" at the top of config.toml.
  5. Adds (or replaces) the [model_providers.codex-proxy] block.
  6. Adds export CODEX_PROXY_KEY=dummy to your shell profile (zsh or bash, if missing).

Usage:

./setup.sh

You can also override the detected model/provider name:

./setup.sh --model my-model --provider my-proxy-name

Restore script (undo)

restore.sh reverts the changes made by setup.sh.

What it does:

  1. Restores the most recent ~/.codex/config.toml.bak.<timestamp> (or a specific backup if given).
  2. Removes the export CODEX_PROXY_KEY=dummy line from your shell profile.
  3. Removes the [model_providers.codex-proxy] block if it still exists.

Usage:

# restore the latest backup
./restore.sh

# restore a specific backup
./restore.sh ~/.codex/config.toml.bak.1234567890

Endpoints

Method Path Description
POST /v1/responses Translates to upstream /v1/chat/completions.
POST /v1/chat/completions Pass-through (no translation).
GET /health Health check. Returns {"status":"ok"}.

Verify it works

# 1. proxy healthy?
curl http://127.0.0.1:11434/health

# 2. responses stream translates correctly?
curl -N -X POST http://127.0.0.1:11434/v1/responses \
  -H "Content-Type: application/json" \
  -d '{"model":"your-model-name","input":"Say hi","stream":true}'

You should see a sequence of event: response.output_text.delta lines, followed by a single event: response.output_item.done, then event: response.completed. The deltas should contain the full text.


Switching providers

Edit codex-proxy/config.json and change base_url, api_key, and model. Then restart the proxy:

# if running manually
# (stop with Ctrl-C, then)
node proxy.js

# if running as a launchd service
launchctl unload ~/Library/LaunchAgents/com.baim.codex-proxy.plist
launchctl load ~/Library/LaunchAgents/com.baim.codex-proxy.plist

If the new model name differs, re-run ./setup.sh to update Codex config, or edit ~/.codex/config.toml manually.


Troubleshooting

Codex still shows truncated / one-token output

Make sure Codex is actually pointing at the proxy and not at an old provider:

grep model_provider ~/.codex/config.toml

It should say model_provider = "codex-proxy". If not, run ./setup.sh again.

stream disconnected before completion

The proxy process likely died. Restart it:

node proxy.js
# or
launchctl load ~/Library/LaunchAgents/com.baim.codex-proxy.plist

Check the log:

tail -f /tmp/codex-proxy.log

upstream 401 / upstream 403

Your config.json api_key is wrong or expired. Fix it and restart the proxy.

Codex says wire_api = "chat" is no longer supported

You have an old provider block with wire_api = "chat". Either remove that block or change it to wire_api = "responses". The codex-proxy block must use "responses".

Port 11434 already in use

Set a different port:

PORT=11435 node proxy.js

Then update base_url in ~/.codex/config.toml to match.


FAQ

Does this work with the Codex desktop app, or only the CLI?

Both. The desktop app and the CLI read the same ~/.codex/config.toml, so configuring the proxy once fixes both.

Does the proxy store my API key?

Only in config.json on your local machine, which is gitignored. It is never logged, never sent anywhere except to your configured upstream provider as a Bearer token.

Can I use multiple providers at once?

Not with a single proxy instance. The proxy reads one config.json. To use multiple providers, run multiple proxy instances on different ports with different config files, and create separate [model_providers.*] blocks in Codex config. Then switch by changing model_provider.

Is there Windows / Linux support?

The proxy itself (proxy.js) is cross-platform Node.js. The setup.sh / restore.sh scripts and the launchd plist are macOS/Linux-oriented. On Windows, run node proxy.js manually and edit ~/.codex/config.toml by hand following the manual configuration section.

Why port 11434?

It is the default Ollama port, which many people already associate with local LLM tooling. Change it via the PORT env var if it conflicts.


How the bug fix works

For those who hit the same issue with other routers: the root cause was premature done events. A correct Responses API stream must order events like this:

response.output_item.added       (message item)
response.content_part.added
response.output_text.delta       "Hel"
response.output_text.delta       "lo"
response.output_text.delta       "!"
response.output_text.done        "Hello!"      ← done fires ONCE, at the end
response.content_part.done
response.output_item.done
response.completed

Buggy routers fire response.output_text.done and response.output_item.done after the first delta, then continue sending more deltas for an item Codex already considers closed. Codex reads output_item.done, treats the message as complete, and discards the rest.

codex-proxy accumulates all deltas and fires done exactly once at the end, with the full text.


License

MIT

About

Local proxy translating [OI] Responses API to Chat Completions for codex CLI/desktop v0.147+. Fixes premature done-event streaming bug.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages