This repo is a small, deliberately thin Rust adapter for using Codex CLI with GitHub Copilot's Responses API endpoint.
Before changing code, read the public project guidance:
AGENTS.md— core doctrine and safety rules.README.md— usage, configuration, validation, and public-facing behavior.
- Provider adapter, not transformer. Treat GitHub Copilot like an OpenAI-compatible cloud provider with different auth, headers, and endpoint conventions.
- Responses-only. Do not add Chat Completions, Anthropic Messages, or Anthropic SSE conversion paths.
- Codex-native request shape. Let Codex own request bodies, tool schemas, reasoning settings, history, compaction behavior, and SSE parsing.
- Stream bytes through. The proxy should forward request bodies and response streams with as little interpretation as possible.
- No proactive tool rewriting or truncation. Only add targeted compatibility fixes after a concrete Copilot error proves they are required.
- Rate-limit retry is allowed. HTTP
429retry based on status/headers is a provider transport behavior, not protocol transformation. - Every mutation must be explicit. If request or response mutation becomes necessary, keep it isolated, documented, logged, and tested.
- Do not recreate a transformer architecture.
- Do not add Chat Completions, Anthropic Messages, or Anthropic SSE bridges.
- Do not rewrite, truncate, summarize, or reshape tool calls by default.
- Do not parse request bodies unless a specific, tested Copilot compatibility fix requires it.
- Do not parse response-message text to detect rate limits; use HTTP
429and headers only. - Do not log bearer tokens, GitHub OAuth tokens, authorization headers, or unredacted request dumps.
- Do not add Docker/container support.
- Keep the model believing it is running inside Codex. GitHub Copilot is only the upstream provider.
- Codex points at this adapter as a custom provider with
wire_api = "responses". - Codex uses
base_url = "http://127.0.0.1:60001/v1"andsupports_websockets = false. - The adapter maps local
GET /v1/modelsto Copilot's upstream/modelsendpoint. - The adapter maps local
POST /v1/responsesto Copilot's upstream/responsesendpoint. - The adapter injects Copilot/VS Code-style provider headers.
- The adapter can own upstream Copilot auth from
COPILOT_BEARER_TOKENor~/.copilot-tokens.json. - The adapter can refresh expired Copilot token-file values from a saved
githubTokenand can create that token file through interactive GitHub OAuth device login. - For refreshable token-file auth, the adapter can force-refresh once after upstream HTTP
401and replay the original request bytes. - If service-owned auth is unavailable, the adapter can forward an incoming Codex
Authorizationheader. - The adapter retries upstream HTTP
429rate limits before sending downstream bytes, based on HTTP status and rate-limit headers only. - The adapter passes Responses SSE back unchanged.
- Rust is canonical.
- Preserve the external contract:
GET /health,GET /v1/models,POST /v1/responses. - Preserve the minimal-adapter doctrine; do not add generic protocol translation behavior for its own sake.
- If a body compatibility pass becomes necessary, isolate it behind tests and document exactly why.
- Read
AGENTS.mdandREADME.mdat the start of a new assistant session. - Run the Rust validation commands before and after behavioral changes when possible.
- Run local service after install:
ccrx start - Restart local service after install:
ccrx restart - Stop local service after install:
ccrx stop - Check local service after install:
ccrx status - Update installed binaries from crates.io:
ccrx update - Run interactive GitHub Copilot device login:
ccrx login - Run local service without installing the short command:
cargo run --release -- serve - Print token helper output:
cargo run --quiet -- print-token - Format check:
cargo fmt --check - Test:
cargo test - Lint:
cargo clippy --all-targets -- -D warnings - Build release binary:
cargo build --release
- Do not publish this project to npm, Homebrew, or another package registry unless the user explicitly asks. Publishing to crates.io is allowed when the user explicitly requests it.
- The public install path is
cargo install codex-code-routerfrom crates.io. - The default
ccrx updatepath reinstalls the latestcodex-code-routerpackage from crates.io, then restarts the service. ccrx updateis intended to run from any current working directory; it should not depend on a local checkout.- When possible,
ccrx updateinfers the existing Cargo install root from the running binary path (.../bin/ccrx) and passes--root <root>tocargo installso updated binaries land beside the existing install. - Keep install locations conceptually separate: the source checkout can live anywhere, Cargo owns the binary install root (usually
~/.cargo/bin), and~/.codex-code-routeris runtime state only (pid, logs, raw diagnostics), not the binary install directory. - Keep GitHub tag/branch update paths available for testing and fallback:
ccrx update --tag vX.Y.Z,ccrx update --branch main, andccrx update --repo <url>. - GitHub Release artifacts are optional. A source tag plus GitHub Release is enough for GitHub-source updates because users build locally with Cargo.
- Before creating a release, run the Rust validation commands:
cargo fmt --checkcargo testcargo clippy --all-targets -- -D warningscargo build --release
- Run
cargo publish --dry-runbefore publishing to crates.io. - Publish to crates.io with
cargo publishonly after validation and dry-run pass. - Keep the README quick-start install command simple:
cargo install codex-code-router. Do not make the primary path require a version,--git,--tag,--bins, or--locked; keep pinned installs as an optional reproducibility note. - To publish a release that
ccrx updatecan discover:git tag vX.Y.Zgit push origin vX.Y.Zgh release create vX.Y.Z --generate-notes
- If only a Git tag exists and no GitHub Release exists, users can still update with
ccrx update --tag vX.Y.Z. Plainccrx updateuses crates.io. - For unreleased testing from the default branch, use
ccrx update --branch main.
- Keep Rust boring and maintainable.
- Prefer small modules with explicit responsibilities.
- Prefer byte-forwarding request/response behavior over JSON parsing.
- Use
axumfor the local server andreqwestfor upstream HTTP. - Keep logs on stderr and never include token-bearing values.
- Keep docs updated when behavior changes.
- Never log bearer tokens or full authorization headers.
- Never log GitHub OAuth tokens.
- Redact sensitive headers in diagnostics and future request dumps.
codex-code-router print-tokenstdout must contain only the token for Codex command-backed auth; diagnostics go to stderr.
When raw diagnostics are enabled and a task needs proof that Codex effort reached the proxy request, use the tightened request snapshot schema:
- event kind:
inbound_request_content - path:
fields.snapshot.extracted.reasoning_effort
Level semantics:
metadata: no content snapshot eventcontent_redacted: value is<redacted-content>full_content: concrete effort value is visible (for examplehigh)
Correlate with fields.local_id across inbound_request, upstream_response_ready, and stream terminal events for a full request trace.