> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wyolet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Codex CLI

> Point OpenAI's Codex CLI at Relay and run it on any model in the catalog

[Codex CLI](https://github.com/openai/codex) speaks the OpenAI Responses API and nothing else, and it builds its request URL by appending `/responses` to the `base_url` you configure — it never inserts a `/v1` of its own. Relay serves it a dedicated base, `https://your-relay/codex/v1`, so a turn lands on `POST /codex/v1/responses`.

Relay translates the Responses shape into whatever the routed upstream speaks, so Codex is not limited to OpenAI models — any model your relay key's policy grants works, including Anthropic, Gemini and a self-hosted Ollama build.

## Setup

Add a provider block to `~/.codex/config.toml` (`CODEX_HOME` relocates that directory) and point the top-level `model_provider` at it:

```toml theme={null}
model          = "gemma4-e4b"
model_provider = "relay"

[model_providers.relay]
name     = "Wyolet Relay"
base_url = "https://your-relay/codex/v1"
env_key  = "RELAY_API_KEY"
wire_api = "responses"
```

Then `RELAY_API_KEY=sk-wr-your-relay-key codex`. The key goes out as `Authorization: Bearer`, which is how Relay expects a relay key.

Three constraints come from Codex itself. The provider id cannot be `openai`, `ollama`, `lmstudio` or `amazon-bedrock*` — those are reserved and the built-in definition wins. The `name` must be neither `OpenAI` nor `azure`, both of which switch Codex into vendor-specific behaviour. And `wire_api` must be `responses`; `"chat"` is rejected at config load, which breaks every `codex` invocation until you remove it.

### Model names

Codex resolves the model string against a catalog compiled into its binary, and falls back to synthesised metadata with a warning for anything it does not recognise — so any Relay model name works as-is. Naming a model with a known prefix inherits that model's capability flags, which is occasionally what you want and otherwise harmless.

`model_context_window` can only *lower* a recognised model's window, never raise it: Codex clamps the value against its own catalog maximum.

### Model picker

Codex's `/model` picker lists a catalog compiled into its binary. For a provider authenticated with `env_key` it never asks the gateway for a list, so the only way in is `model_catalog_json` — and that file replaces the bundled catalog *wholesale*, including the agent prompt Codex sends with every turn, which the catalog is where Codex keeps. A picker file therefore always comes with a prompt file; the two are one step, not a file and an option.

```bash theme={null}
curl -H "Authorization: Bearer $RELAY_API_KEY" \
  https://your-relay/codex/v1/models > ~/.codex/relay-models.json

curl -L https://raw.githubusercontent.com/openai/codex/rust-v0.153.4/codex-rs/models-manager/prompt.md \
  > ~/.codex/relay-prompt.md
```

```toml theme={null}
model_catalog_json      = "~/.codex/relay-models.json"
model_instructions_file = "~/.codex/relay-prompt.md"
```

Match the tag in that second URL to your own `codex --version` — the prompt ships with the binary and changes between releases.

Both are **top-level** keys, so they must sit above the first `[table]` header in `config.toml` — above `[model_providers.relay]`, `[projects."…"]`, `[profiles.…]`, all of them. TOML assigns every key after a header to that table, so the same two lines appended to the end of the file silently become `projects."…".model_catalog_json` and are ignored: the picker keeps showing Codex's bundled models and nothing is logged.

Verify placement with `codex exec --strict-config -m <model> "ok"`. Strict mode rejects a key Codex does not recognise and prints its file, line and full dotted path, which is the only signal you get — without it a misplaced key is dropped in silence.

Once the keys are genuinely top-level, a missing or empty prompt file is loud: Codex refuses to start and names the file. The catalog file on its own is not — every model listed in it would run with no agent prompt at all, and nothing says so.

Both values are paths, never URLs — Codex reads them off disk at startup. A leading `~` expands to your home directory, and a relative path resolves against the directory of the config file it appears in (`$CODEX_HOME`, `~/.codex` by default), so `model_catalog_json = "relay-models.json"` picks up `~/.codex/relay-models.json` as well. `model_catalog_json` is also accepted inside a `[profiles.<name>]` block, and it is applied at startup only.

The document lists exactly the models your relay key's policy grants, each with the catalog's context window, its hosts and their per-Mtok rates as the picker subtitle, and reasoning-effort options for models the catalog marks as reasoning. It is a snapshot: re-run the `curl` after a policy or catalog change.

If you would rather hand-write the file, this is a complete minimal entry — every key shown is required except `context_window`, and Codex rejects a document with no models:

```json theme={null}
{
  "models": [
    {
      "slug": "deepseek-v4-1-flash-cloud",
      "display_name": "DeepSeek V4.1 Flash",
      "description": "deepseek",
      "base_instructions": "",
      "supported_reasoning_levels": [{ "effort": "medium", "description": "Balanced reasoning" }],
      "shell_type": "unified_exec",
      "visibility": "list",
      "supported_in_api": true,
      "priority": 0,
      "support_verbosity": false,
      "truncation_policy": { "mode": "bytes", "limit": 10000 },
      "context_window": 1000000,
      "experimental_supported_tools": [],
      "availability_nux": null,
      "upgrade": null,
      "default_verbosity": null,
      "apply_patch_tool_type": null
    }
  ]
}
```

`visibility: "list"` is what puts a row in the picker, `supported_in_api: true` is what keeps it there for a key-authenticated provider, and the list is ordered by ascending `priority`. `context_window` is optional and omitted when the catalog declares none; leaving `max_context_window` out keeps your `model_context_window` override free, since Codex clamps that value against the catalog maximum when one is present. Codex has no field for a maximum output-token count, so Relay does not project one. Unknown keys are ignored, and a model slug absent from the file still routes — Codex synthesises fallback metadata for it with a warning.

### Per-role models

`[profiles.<name>]` blocks and the `-p/--profile <name>` file layer (`$CODEX_HOME/<name>.config.toml`) both carry their own `model` and `model_provider`, and agent-role files do too — so one Relay can serve a cheap review model and an expensive build model from a single config.

## What you get

* **Usage** — every turn appears in `GET /logs` and the `/usage/*` endpoints, attributed to your relay key, with token counts and timing.
* **Payloads** — with payload logging enabled, the full request and response bodies are readable at `GET /logs/{request_id}`.
* **Attribution** — the client's `session-id`, `thread-id`, `x-client-request-id`, `x-codex-installation-id`, `x-codex-window-id`, `x-codex-parent-thread-id`, `x-openai-subagent` and `originator` headers are recorded on the usage event as `extras`, alongside `client=codex`. That is what lets you cost one session, or one sub-agent turn inside it.

See [Usage & Logging](/concepts/observability) for reading it back.

## Known limits

* **Hosted tools do not cross to other vendors.** Codex's `apply_patch` (a `custom` tool) and its `namespace` groupings are translated for non-OpenAI upstreams — the model sees a function taking one `input` string and Codex receives a normal `custom_tool_call` back — but `web_search` and other server-side tools have no equivalent elsewhere and are dropped on such routes. Every tool on an OpenAI upstream is forwarded as sent.
* **The picker's prompt file is Codex's generic one.** `model_instructions_file` applies a single prompt to every model, where Codex's bundled catalog carries a per-model instruction template for each OpenAI model it ships. It also drifts with Codex releases, so re-fetch it at the matching tag when you upgrade.
* **Codex does not retry a 429 and ignores `Retry-After`.** Its only backoff hint is a `try again in Ns` phrase scraped out of the error message text, so a rate-limited turn surfaces to you rather than resolving itself.
* **A signed-in ChatGPT account overrides `base_url`.** If Codex is logged in, requests can bypass the gateway entirely and reach no Relay; run `codex logout` first.
* **Use `127.0.0.1`, not `localhost`,** in a local `base_url` — Codex's name resolution fails on the latter.
