docs: document routing the Codex CLI through Otari - #484
Conversation
Codex CLI can route through Otari as a custom model provider over the Responses API, but nothing documented it: the existing "Use with Codex" page covers the opposite path (importing Codex usage over OTLP), so readers looking for the routing setup found the telemetry guide instead. Add docs/use-with-codex-cli.md with the standalone flow as the primary path and the otari.ai-connected variant alongside it, verified end to end against a local gateway with Codex CLI 0.146.0: key creation, the config.toml provider entry, a completion, and a tool-calling turn. The gotchas are the ones that actually bite, each reproduced: the built-in OpenAI provider never reaches the gateway, a base URL missing /v1 404s, a provider without Responses support returns 400, and an Otari selector misses Codex's bundled model metadata unless a model_catalog_json maps it back (with that file's schema pinned to the Codex version reading it). Link it from the docs index and from the neighbouring pages, and rename the index entry for the telemetry guide so the two are told apart. The new test keeps relative doc links resolvable and every use-with guide listed in the index. Fixes #370
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
WalkthroughThe PR expands Codex integration documentation for Responses API routing and telemetry import, adds cross-references across related guides, and introduces tests for relative documentation links and index coverage. ChangesCodex documentation
Estimated code review effort: 2 (Simple) | ~15 minutes Possibly related PRs
Suggested reviewers: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a new “Use with Codex CLI” routing guide so users can configure Codex CLI to talk to Otari via the OpenAI Responses API (distinct from the existing Codex telemetry-import doc), and strengthens docs navigation integrity by adding a unit test that validates intra-doc relative links and index coverage.
Changes:
- Add
docs/use-with-codex-cli.mddocumenting standalone and otari.ai-connected Codex CLI setup via/v1/responses, plus model-selection, metadata-catalog, and gotchas sections. - Update related docs to link to the new guide and clarify the distinction between “routing” vs “telemetry import” Codex flows.
- Add a unit test to ensure relative links in
docs/resolve and that alluse-with-*.mdguides are listed indocs/index.md.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_docs_links.py | Adds unit tests to validate relative doc links and ensure use-with-* guides are indexed. |
| docs/use-with-opencode.md | Adds a “See also” link to the new Codex CLI routing guide. |
| docs/use-with-codex.md | Clarifies routing-vs-telemetry guidance and links to the Codex CLI routing page. |
| docs/use-with-codex-cli.md | New end-to-end guide for routing Codex CLI through Otari via the Responses API. |
| docs/index.md | Adds/adjusts index entries to include the new Codex CLI routing guide and disambiguate the telemetry-import page. |
| docs/chatgpt-subscription.md | Links to the Codex CLI routing guide from related “see also” content. |
| docs/api-reference.md | Adds a note about provider Responses API support and links to the Codex CLI guide as an example. |
| Codex sends the key as `Authorization: Bearer <token>`, which is the scheme Otari | ||
| accepts. In the dashboard: Keys -> create a key for a user. Or over the API with |
khaledosman
left a comment
There was a problem hiding this comment.
Spot-checked the code-backed claims and they hold: hybrid validates every resolved attempt (responses.py:374-377), GET /v1/models is standalone-only (api/main.py:41-42), the 400 string matches responses.py:183, and the two models.md#… anchors in the new page resolve (the new test doesn't check anchors). No relative link in docs/ breaks, and no OpenAPI or dashboard-bundle impact. Inline notes are non-blocking.
🤖 Generated with Claude Code
| ### The provider has to speak the Responses API | ||
|
|
||
| Otari serves `/v1/responses` only for providers that implement it, such as | ||
| OpenAI, Azure OpenAI, Groq, Fireworks, and HuggingFace, plus any |
There was a problem hiding this comment.
Responses support is read at runtime from any-llm's SUPPORTS_RESPONSES (src/gateway/api/routes/responses.py:179-181), so this list drifts on every dependency bump — the currently installed any-llm also flags gmi and neosantara, so it's already incomplete. Describing the mechanism (and the 400 you get) ages better than enumerating providers.
| def test_relative_links_resolve(doc: Path) -> None: | ||
| broken: list[str] = [] | ||
| for lineno, line in enumerate(doc.read_text(encoding="utf-8").splitlines(), 1): | ||
| for target in _LINK.findall(line): |
There was a problem hiding this comment.
The scan doesn't strip fenced code blocks, so the first doc that shows an example markdown link fails the check for a link that was never meant to resolve. Dropping lines inside ``` fences before matching is a two-line fix. Also silently unchecked: reference-style ([a]: url), autolinks (`https://…`), and anchor fragments.
| served = {m["id"] for m in json.load(open("served.json"))["data"]} | ||
| bundled = json.load(open("bundled.json"))["models"] | ||
| models = [dict(m, slug=PREFIX + m["slug"]) for m in bundled if PREFIX + m["slug"] in served] | ||
| json.dump({"models": models}, open("otari-models.json", "w"), indent=2) |
There was a problem hiding this comment.
A prefix typo here writes a valid-but-empty catalog and the user just gets an emptier model picker with no error. Worth a with block plus a check that models is non-empty, since this is a snippet people copy-paste.
| - [Use with Claude Code](use-with-claude-code.md): point the Claude Code CLI at Otari. | ||
| - [Use with Codex](use-with-codex.md): point the Codex CLI at Otari. | ||
| - [Use with Codex CLI](use-with-codex-cli.md): route the Codex CLI through Otari over the Responses API. | ||
| - [Use with Codex](use-with-codex.md): import Codex usage into Otari without routing through it. |
There was a problem hiding this comment.
"Use with Codex CLI" (routing) directly above "Use with Codex" (telemetry) is a coin flip for anyone scanning the nav, and the telemetry page is the misnamed one. Renaming it (import-codex-usage.md) and letting the routing guide own use-with-codex.md reads better, at the cost of breaking inbound external links. Your call; the reworded descriptions do most of the work.
Two pages for one tool, named use-with-codex.md and use-with-codex-cli.md, gave a reader no way to tell which one they wanted: both are the Codex CLI. The repo already answers this for the sibling tool, since use-with-claude-code.md carries the routing setup and the telemetry-import path in a single page. Follow that shape here. The routing content moves into use-with-codex.md ahead of the existing OTLP material, which becomes "Import Codex usage (without routing through Otari)", and the "route or export, not both" callout moves up top where it now governs both halves. Keeping the original filename means every existing inbound link still resolves. Refs #370
Description
Note: this PR description was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.
Codex CLI can route through Otari as a custom model provider over the Responses API, but nothing documented it.
docs/use-with-codex.mdcovered only the opposite path (importing Codex usage over OTLP), so a reader looking for the routing setup landed on the telemetry guide instead.This PR adds the routing guide to that same page rather than creating a second one. Issue #370 asked for a new
docs/use-with-codex-cli.md, but two pages for one tool, nameduse-with-codex.mdanduse-with-codex-cli.md, give a reader no way to tell which one they want, since both are the Codex CLI. The repo already settles this for the sibling tool:use-with-claude-code.mdcarries the routing setup and the telemetry-import path in one page. Keeping the original filename also means every existing inbound link still resolves.docs/use-with-codex.mdnow leads with routing, following the OpenAI-compatible exemplar (docs/use-with-opencode.md):~/.codex/config.tomlwithmodel_provider,base_urlending in/v1,wire_api = "responses", andenv_key.GET /v1/modelsis standalone-only, and hybrid mode validates Responses support on every attempt in the resolved route.provider:modeland named-instance selectors,GET /v1/modelsas the source of truth, and the fact that only some providers implement the Responses API (Anthropic and Mistral return a 400).model_catalog_json: why an Otari selector misses Codex's bundled model metadata, and a script that builds a catalog fromcodex debug models --bundledfiltered against/v1/models./v1gives 404s, provider config changes need a new session, the provider has to speak Responses, and a catalog file's schema is pinned to the Codex version that reads it.Also linked from
docs/index.md,api-reference.md,use-with-opencode.md, andchatgpt-subscription.md.One correction to the issue body: it says
model = "openai:gpt-5.4"works because Codex ships bundled metadata for that slug. It does not. Metadata lookup is by exact slug, so any prefixed Otari selector falls back to conservative defaults. Verified on Codex CLI 0.146.0; the guide documents the catalog workaround instead.Verified end to end
Run against a local standalone gateway with Codex CLI 0.146.0, not inferred:
codex execthrough the documentedconfig.tomlreturned a completion, and usage landed withendpoint=/v1/responses, provideropenai, priced,counts_toward_budget: true.warning: Model metadata for 'openai:gpt-5.4' not foundreproduced, and the generated catalog clears it./v1gives 404, ananthropic:model gives400 Provider 'anthropic' does not support the Responses API, and the built-in provider dialswss://api.openai.com/v1/responses.PR Type
Relevant issues
Fixes #370
Checklist
tests/unit,tests/integration).make lint,make typecheck,make test).uv run python scripts/generate_openapi.py).Notes on the checklist: the new test is
tests/unit/test_docs_links.py, which checks that every relative link indocs/resolves and that everyuse-with-*.mdguide is listed indocs/index.md. It was confirmed to fail when the index link is removed.make lint,make typecheck,make test-unit(1156 passed, 1 skipped),make openapi-check, andmake postman-checkall pass locally; the integration suite was not run locally because this environment has no Docker for Testcontainers, so CI covers it. No API contract changed, and both generated artifacts are confirmed up to date.AI Usage
AI Model/Tool used:
Claude Opus 5 via Claude Code.
Any additional AI details you'd like to share:
The guide was written from a live test rather than from the model's priors: a standalone gateway was started locally, a virtual key created, and Codex CLI 0.146.0 pointed at it. Every command, config block, warning, and error message in the page came out of that session, including the correction to the issue's claim about bundled metadata.
NOTE:
When responding to reviewer questions, please respond yourself rather than copy/pasting reviewer comments into an AI and pasting back its answer. We want to discuss with you, not your AI :)
🤖 Generated with Claude Code
Summary
These changes make Codex setup clearer and help prevent broken or unlisted documentation links.