Skip to content

About

Use the OpenAI SDK with your authenticated Codex CLI — local chat, streaming, vision, image generation, and cross-platform binaries.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Codex Proxy

Codex Proxy — an OpenAI SDK gateway powered by the Codex CLI

Plug your Codex CLI into the tools you already love.
A small, local-first OpenAI SDK gateway powered by your Codex CLI.

CI status Bun 1.3+ OpenAI SDK compatible Ollama compatible Windows, Linux, and macOS


Codex Proxy turns an authenticated codex installation into a loopback API for OpenAI SDKs, Ollama-compatible apps, Open WebUI, automations, and scripts. No database, Docker, API key, or proxy token is required for local use.

Important

This is a community bridge, not an official OpenAI endpoint. Availability, limits, authentication, and model access still come from your Codex account.

Highlights

Capability
⚡ One Bun process and one command
🔌 OpenAI Chat Completions and Ollama-compatible routes
🌊 SSE and NDJSON streaming
🖼️ Local vision input and generated image output
🔎 Automatic Codex discovery on Windows, Linux, and macOS
🛡️ Loopback-only and read-only sandbox defaults
📦 Standalone Windows x64, Linux/macOS x64 and ARM64 binaries
flowchart LR
    A["OpenAI SDK"] --> P["Codex Proxy"]
    B["Compatible clients"] --> P
    P --> C["Codex app-server / exec"]
    C --> O["Your Codex account"]
Loading

The proxy translates request and response formats. Codex remains responsible for authentication, model execution, tools, and account limits.

Quick start

1. Install and authenticate Codex

macOS or Linux:

curl -fsSL https://chatgpt.com/codex/install.sh | sh
codex login

Windows PowerShell:

irm https://chatgpt.com/codex/install.ps1 | iex
codex login

2. Start the proxy

bun install
bun run start

The service is ready at http://127.0.0.1:8787. You can alternatively run a standalone binary from GitHub Releases. Codex is discovered from PATH, CODEX_INSTALL_DIR, official install locations, and versioned Codex Desktop installations on Windows.

If the desktop app works but PowerShell says codex is not recognized, run powershell -NoProfile -File scripts/install-cli-launcher.ps1. This installs a small launcher in ~/.local/bin and reuses the desktop CLI and existing login. Undo it with the same script plus -Uninstall. Open a new terminal after changing PATH.

3. Connect a client

OpenAI SDK:

npm install openai
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "http://127.0.0.1:8787/v1",
  apiKey: "local", // Required by the SDK, ignored by the proxy.
});

const result = await client.chat.completions.create({
  model: "gpt-6.1-sol",
  messages: [{ role: "user", content: "Say hello in French." }],
});

console.log(result.choices[0]?.message.content);

Ollama-compatible chat:

curl http://127.0.0.1:8787/api/chat \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-6.1-sol","messages":[{"role":"user","content":"Say hello in French."}],"stream":false}'
Client setting Value
OpenAI base URL http://127.0.0.1:8787/v1
Ollama base URL http://127.0.0.1:8787
Default model gpt-6.1-sol when present in the local catalog, then gpt-6-astra, then the first available entry

Docker clients cannot usually reach a host service bound to loopback. If you deliberately bind another interface, set CODEX_PROXY_TOKEN and never expose an authenticated Codex session directly to the internet.

API

Method Route Purpose
GET /health Health and version
GET /v1/models OpenAI model catalog
POST /v1/chat/completions OpenAI chat and SSE
POST /v1/images/generations OpenAI-style image generation
GET /api/tags Ollama model catalog
POST /api/chat Ollama chat and NDJSON
POST /api/generate Ollama generation and NDJSON
GET /artifacts/:id/:filename Generated image download

Metadata helpers such as /v1/models/:id, /api/ps, /api/version, and /api/show are also available.

Models and reasoning effort

The proxy prefers GPT-6.1 Sol (gpt-6.1-sol) when it appears in the local Codex catalog, then GPT-6 Astra (gpt-6-astra). It preserves other visible catalog entries and explicit CODEX_PROXY_MODEL or request model overrides. It does not copy the default from your personal Codex configuration.

GET /v1/models queries Codex app-server model/list, follows every page, and excludes hidden entries. It includes default_model, source, fetched_at, attachment capabilities, and each model's display_name, supported_reasoning_efforts, default_reasoning_effort, input_modalities, and is_default when available. Results are cached for at most 30 seconds; use /v1/models?refresh=1 to request a fresh discovery. Errors return HTTP 503 with model_discovery_error; no static or stale model list is substituted. Discovery never starts a generation. Explicit model overrides must exist in the returned catalog.

Set reasoning_effort in a chat request to choose a level, including Max:

{
  "model": "gpt-6-astra",
  "reasoning_effort": "max",
  "messages": [{"role": "user", "content": "Explain this problem carefully."}]
}

The proxy forwards it to codex exec --config 'model_reasoning_effort="max"'. Omitting the field preserves the model default reported by Codex. An invalid level or a level excluded by known model metadata returns HTTP 400 before running Codex. Levels are model-dependent; use the catalog to populate a selector instead of assuming all models support Ultra. You can combine reasoning_effort with response_format.

Structured JSON output

Use an OpenAI-compatible JSON Schema request:

{
  "messages": [{"role":"user","content":"Return a French greeting."}],
  "response_format": {
    "type":"json_schema",
    "json_schema": {
      "name":"greeting",
      "strict":true,
      "schema": {
        "type":"object",
        "properties":{"greeting":{"type":"string"}},
        "required":["greeting"],
        "additionalProperties":false
      }
    }
  }
}

For ordinary requests, the proxy passes this schema to Codex --output-schema in a temporary file and removes it after execution. Streaming requests pass it as the app-server turn's outputSchema. Both return validated JSON content without mixing in commentary. The Codex structured-output service enforces supported schema constraints; unsupported schemas return an error. response_format: {"type":"json_object"} adds a JSON instruction and verifies that the result parses as an object, without enforcing a schema. --json alone is the CLI's JSONL event format, not a final-answer schema.

You can use the same schema directly from PowerShell with the repaired CLI:

codex exec --output-schema examples/greeting.schema.json --output-last-message result.json "Return a greeting in French."
Get-Content -Raw result.json | ConvertFrom-Json

Add --json only if you also want the separate progress-event stream in JSONL. The final JSON remains in result.json.

With an OpenAI SDK client configured as above:

const result = await client.chat.completions.create({
  model: "gpt-6.1-sol",
  messages: [{ role: "user", content: "Return a greeting in French." }],
  response_format: {
    type: "json_schema",
    json_schema: {
      name: "greeting",
      strict: true,
      schema: {
        type: "object",
        properties: { greeting: { type: "string" } },
        required: ["greeting"],
        additionalProperties: false,
      },
    },
  },
});
const value = JSON.parse(result.choices[0]?.message.content ?? "{}");
console.log(value.greeting);

See the official non-interactive Codex documentation for the distinction between JSONL events and final-answer schemas.

Live text, commentary, and public summaries

Set stream: true to use Codex app-server over a private stdio connection. Normal answers stream as genuine item/agentMessage/delta fragments in choices[0].delta.content. The proxy does not split a finished answer to simulate typing.

Assistant messages tagged commentary are sent separately as delta.commentary. For non-trivial requests, the chat instructions ask for a short public update explaining the approach before the final answer. These updates are generated by the model. They do not reveal private reasoning and never become part of the final JSON.

Public reasoning summaries from item/reasoning/summaryTextDelta or a completed reasoning item's summary field use delta.reasoning_summary. The proxy requests detailed summaries, but models can return none. Availability varies by turn. End-to-end Rilby tests with GPT-6.1 Sol High received and displayed nonempty public summaries alongside commentary; other requests returned no summary. Private item/reasoning/textDelta and reasoning content are never forwarded or renamed as summaries.

{"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"commentary":"I will compare the two options."},"finish_reason":null}]}

The optional codex_progress.phase extension remains available for connection states, with starting, thinking, and answering. SSE comments keep idle connections alive every 15 seconds. Ollama NDJSON exposes the same optional fields on its message or response object.

Structured requests stream commentary and available summaries immediately, while final JSON content is withheld until execution and validation succeed. Errors after HTTP headers arrive as an SSE error payload followed by [DONE], without a successful finish_reason. Client cancellation interrupts and kills that request's app-server process.

The app-server adapter creates an ephemeral thread in a new empty temporary working directory. Shell, browser, computer-use, apps, plugins, hooks, memory, host skills, image generation, and delegated agents are disabled; configured MCP servers are explicitly disabled before the thread starts. No approval request is granted. It reuses existing Codex authentication without copying credentials or changing personal settings. Non-streaming requests without web search and image generation continue to use the isolated codex exec adapter.

This integration uses the installed CLI's app-server protocol. See the official app-server documentation for its experimental status and event definitions.

Public web research

GET /health and GET /v1/models advertise capabilities.web_search and capabilities.image_generation. To enable public research for one chat request, send "tools": [{"type":"web_search"}] alongside messages. Omission or [] keeps web search disabled; unsupported tools and additional tool options return HTTP 400. Both streamed and non-streamed web requests use app-server with web_search="live", so search, opening pages and finding text in pages can inform structured output too. Shell, local file access and MCP tools remain disabled.

Current Codex exposes the web tool through code_mode_host, which the proxy enables only for opted-in web requests. The other tool exclusions remain in place. Enabling web_search="live" alone leaves the model without a usable web tool on this runtime; the adapter is verified with real searches and page opens as well as deterministic transport tests.

Successful responses can include sources: [{"url":"https://example.com/","title":"Example"}] at the response root, or on the final SSE chunk with finish_reason: "stop". Sources come only from completed web tool metadata, never from URLs written by the model. The proxy keeps up to 40 unique HTTP(S) URLs without credentials, with titles limited to 500 characters and URLs to 4000 characters. If the installed Codex tool supplies no public URLs, the optional sources field is absent.

System prompts and sliding instructions

Only the first message is promoted to Codex developer_instructions, and only when its role is developer or legacy system. Later instruction messages stay in their original conversational position:

{
  "messages": [
    {"role": "system", "content": "Always answer in French."},
    {"role": "user", "content": "First question"},
    {"role": "assistant", "content": "First answer"},
    {"role": "system", "content": "From now on, answer as JSON."},
    {"role": "user", "content": "Next question"}
  ]
}

Here, the first system becomes the Codex developer instruction. The sliding system remains between the two turns and is handled by Codex as part of the ordered conversation; it is never moved back to the top.

Images

Browser attachments and local image input

Browser clients can send images as image_url content parts with a base64 data URL. Supported image types are PNG, JPEG, WebP and GIF; the decoded signature must match the media type. The selected model must advertise image input. The proxy writes image bytes to a private temporary directory for Codex and deletes them when the request completes, fails or is cancelled.

UTF-8 text documents use { "type": "file", "file": { "filename": "document.txt", "file_data": "data:text/plain;base64,..." } }. Their decoded text is included as reference material in the conversation. PDF and Office files must first be extracted to text by the client; the local Codex input protocol has no generic binary document part. Audio and arbitrary binary files are not accepted by this bridge.

Only user messages may carry attachments. A request may contain up to 32 attachments and 10 MiB of decoded attachment data in total, with the same 10 MiB limit per file. The HTTP JSON body limit is 16 MiB to allow base64 overhead and conversation text. Remote image URLs are not downloaded.

Use a local file:// URL in an OpenAI content part. The proxy forwards it to codex exec --image:

{
  "model": "gpt-6.1-sol",
  "messages": [{
    "role": "user",
    "content": [
      {"type": "text", "text": "Describe this image."},
      {"type": "image_url", "image_url": {"url": "file:///absolute/path/image.png"}}
    ]
  }]
}

Generated images

curl http://127.0.0.1:8787/v1/images/generations \
  -H "Content-Type: application/json" \
  -d '{
    "model":"gpt-image-2",
    "prompt":"Two cheerful illustrations containing the word Bonjour.",
    "n":2,
    "size":"1024x1024",
    "quality":"high",
    "response_format":"url"
  }'
  • n accepts 1–10 images.
  • The default response uses { created, data: [{ b64_json }] }; response_format: "url" returns temporary local URLs.
  • Every result is also described in the x_codex_artifacts extension and saved by Codex under ~/.codex/generated_images/.
  • gpt-image-* is a compatibility alias: the authenticated Codex agent invokes its imagegen skill. size and quality are developer-instruction targets, so they are best effort.
  • PNG is supported. Native transparency and partial-image streaming are not.
  • A concrete Codex model and its supported reasoning_effort can be selected, using the same catalog and validation as chat. Client cancellation stops the active generation and discards late results. Generated images are limited to 10 MiB in total before base64 encoding or publication.

Image requests made through chat still receive Markdown image links plus an artifacts extension.

Configuration

All settings are optional on loopback.

Variable Default Purpose
CODEX_PROXY_HOST 127.0.0.1 Listen address
CODEX_PROXY_PORT 8787 Listen port
CODEX_PROXY_MODEL preferred available model Explicit default model; overrides the CODEX_MODEL alias
CODEX_PROXY_ALLOWED_ORIGINS loopback HTTP(S) origins Extra trusted browser origins, comma-separated
CODEX_PROXY_COMMAND auto-detected Codex executable
CODEX_PROXY_SANDBOX read-only Non-streaming Codex sandbox mode; streaming chat always uses read-only
CODEX_PROXY_TIMEOUT_MS 600000 Request timeout
CODEX_PROXY_TOKEN unset Optional on loopback; required elsewhere

Development and validation

bun run dev               # development server
bun run test:unit         # fast in-process tests
bun run test:integration  # real server + executable dummy Codex CLI
bun run check             # typecheck + every deterministic test
bun run smoke             # optional test through your authenticated Codex CLI
bun run build             # binary for the current platform
bun run build:release     # all five release targets

Releases

  • CI runs typechecking, unit tests, black-box integration tests with a dummy Codex CLI, and a Linux binary smoke test. GitHub needs no Codex credentials.
  • Release packages Windows x64 plus Linux/macOS x64 and ARM64 binaries with SHA-256 checksums.
git tag v1.0.0
git push origin v1.0.0

You can also run the Release workflow manually for an existing tag. Standalone binaries include Bun, but not Codex or credentials; the target machine still needs an authenticated Codex CLI. See GitHub Releases.

Boundaries

  • The Responses API and OpenAI function-call translation are not implemented.
  • Conversation state belongs to the client. Non-streaming requests without web search use ephemeral codex exec without user config or project instructions. Streaming chat and web research use an ephemeral app-server thread with an empty working directory and read-only access; only explicitly requested public web tools are enabled.
  • Browser requests are limited to loopback HTTP(S) origins unless explicitly allowlisted. Host checks reject DNS rebinding on loopback.
  • Chat prompts use stdin to support long conversations on Windows. Cancelling a request stops its CLI process.
  • SSE/NDJSON deliver actual answer deltas and public commentary. Public reasoning summaries depend on model availability; structured JSON content remains buffered until validation.
  • Usage tokens are currently reported as zero because the proxy does not yet map Codex CLI usage events.
  • Model availability depends on the authenticated Codex account.

Built for local workflows that deserve first-class APIs.
Keep it on loopback. Keep it simple. Let Codex do the hard part.

About

Use the OpenAI SDK with your authenticated Codex CLI — local chat, streaming, vision, image generation, and cross-platform binaries.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages