DodoRouter
Get Started
Docs / Codex CLI
Browse docs
View as Markdown

Codex CLI

OpenAI's Codex CLI supports custom model providers in ~/.codex/config.toml:

model = "default"
model_provider = "dodorouter"

[model_providers.dodorouter]
name = "DodoRouter"
base_url = "{base_url}/r/{router}/v1"
env_key = "DODO_API_KEY"
wire_api = "responses"

env_key names an environment variable Codex reads the API key from, so the key itself never has to live in the config file:

export DODO_API_KEY="sk-dodo-YOUR_KEY"
codex exec "Reply with exactly the single word: pong"

Earlier smoke tests verified streamed pong responses and usage recording. An earlier version of DodoRouter's streamed Responses API didn't emit the item-lifecycle events Codex CLI requires before a text delta, causing OutputTextDelta without active item and a stall; that lifecycle issue was fixed. Compatibility still depends on the configured upstream model and current client version. Run a smoke test for your configuration before relying on it.

Compare DodoRouter with direct Codex

For a local HTTPS proxy, set SSL_CERT_FILE to its trusted CA certificate. The comparison runner passes it only to DodoRouter's Codex process; the direct baseline uses its normal public trust roots. Passing a local-only CA to the direct process can cause WebSocket certificate failures and inflate timings.

Current Codex versions can send additional_tools and other non-message input items. Responses-format upstream routes preserve these items instead of turning them into developer messages with null content. See typed input items and the null-content troubleshooting note.

The repository includes an opt-in transport and prompt-cache probe. It uses an existing Codex ChatGPT login for the direct path and a router key from an environment variable for DodoRouter. It does not edit your Codex configuration or router settings. Live runs consume provider quota and may incur charges on metered router keys.

First configure the router to serve the same model as the direct path. The model named by the client does not override the router's configured steps. Also check the provider account and reasoning settings: different accounts, fallbacks or model overrides prevent a controlled proxy-only comparison.

From the repository root, preview the test without sending requests:

node scripts/codex_compare.mjs --model YOUR_MODEL \
  --base-url https://api.dodorouter.com/r/YOUR_ROUTER/v1 --dry-run

Set DODO_API_KEY_CODEX securely in your environment, then omit --dry-run to run. Use --key-env DODO_API_KEY for a different variable name. If your key is loaded by direnv, prefix the command with direnv exec ..

The default is two pairs of four-turn sessions (16 CLI turns), with alternating path order and the same synthetic catalog questions. Sessions are resumed to exercise growing prefixes. The runner does not override Codex's reasoning effort; Codex may still send its own model default, which provider-default routing preserves. Check outbound reasoning settings when comparing runs. Each answer has an exact correctness check. Results and raw CLI events are saved in a printed temporary directory; synthetic session history is also stored by Codex so it can resume. Each CLI turn has a two-minute timeout, and the probe stops on a failed or incorrect turn. Codex may internally retry requests, so 16 CLI turns is not an upstream-request or spending cap.

Codex's JSONL turn.completed.usage can represent cumulative session totals on resumed custom-provider sessions. Do not sum those totals as per-turn usage. For per-turn comparisons, use last_token_usage from the corresponding saved Codex session's token_count events, and cross-check Dodo's request logs. Compare cached_input_tokens / input_tokens, correctness and elapsed_ms, separating initial turns from follow-ups. Initial turns are not guaranteed cache-cold; elapsed time includes CLI startup and is not time to first token or isolated proxy overhead. The direct default transport may differ from the proxy's Responses SSE transport. Check Dodo MCP list_logs / get_log for the actual model, failures, cache evidence and available account identity. This small probe tests transport and caching, not general coding quality or statistically established performance parity.

If Codex reports stream closed before response.completed, inspect the Dodo log before retrying: an upstream request rejection can appear as a stream failure. For example, a configured provider that rejects the developer role cannot serve that Codex request. A successful historical smoke test does not guarantee compatibility with every current client/provider combination.