A lightweight Rust protocol translation proxy that enables OpenAI Codex CLI to use DeepSeek V4 as its backend. It translates between OpenAI's stateful Responses API (/v1/responses) and DeepSeek's standard Chat Completions API (/chat/completions).
Uses a 3-tier models/providers/routes configuration.
# 1. Get a DeepSeek API key from https://platform.deepseek.com/api_keys
# 2. Configure
cp config.yml config.local.yml
# Edit config.local.yml and set your api_key
# 3. Build and run
cargo run --release
# 4. In another terminal, configure Codex and launch
export CODEX_HOME="$HOME/.codex"
mkdir -p "$CODEX_HOME"
cat > "$CODEX_HOME/config.toml" <<'TOML'
[openai]
base_url = "http://127.0.0.1:4000/v1"
api_key = "any-non-empty-value"
TOML
codexnpm install -g @openai/codexVerify:
codex --version
rustc --versionGo to the DeepSeek Platform, create an API key, and copy it.
Copy the example config and edit it:
cp config.yml config.local.ymlSet your DeepSeek API key in the config file:
providers:
deepseek:
base_url: "https://api.deepseek.com"
api_key: "sk-your-deepseek-api-key" # <-- your key here
protocol: "openai-chat"Or use an environment variable (recommended):
providers:
deepseek:
base_url: "https://api.deepseek.com"
api_key_env: "DEEPSEEK_API_KEY" # reads from env
protocol: "openai-chat"The proxy uses a 3-tier configuration:
# Model definitions: metadata, context windows, reasoning levels
models:
deepseek-v4-pro:
context_window: 1000000
max_output_tokens: 384000
display_name: "DeepSeek V4 Pro"
default_reasoning_level: "high"
supported_reasoning_levels:
- effort: "high"
description: "High reasoning effort"
- effort: "xhigh"
description: "Extra high reasoning effort"
# Provider definitions: upstream API connections
providers:
deepseek:
base_url: "https://api.deepseek.com"
api_key: "sk-..."
protocol: "openai-chat"
offers:
- model: deepseek-v4-pro
- model: deepseek-v4-flash
# Route definitions: map aliases to provider + model
routes:
deepseek:
model: deepseek-v4-pro
provider: deepseek
deepseek-flash:
model: deepseek-v4-flash
provider: deepseek
# Default route used when no model is specified
defaults:
model: "deepseek"
max_tokens: 65536Export your API key (if using env vars) and start:
Linux / macOS:
export DEEPSEEK_API_KEY="sk-your-deepseek-api-key"
cargo run --releaseWindows PowerShell:
$env:DEEPSEEK_API_KEY = "sk-your-deepseek-api-key"
cargo run --releaseThe proxy listens on http://127.0.0.1:4000 by default.
Generate a minimal Codex config.toml automatically:
cargo run --release -- \
--print-codex-config deepseek \
--codex-base-url "http://127.0.0.1:4000/v1" \
--codex-home "$HOME/.codex"Or manually create ~/.codex/config.toml:
[openai]
base_url = "http://127.0.0.1:4000/v1"
api_key = "any-non-empty-value"cd /path/to/your/project
codexCodex now sends OpenAI Responses requests to the proxy, which translates them to DeepSeek Chat Completions.
A helper script builds the proxy, starts it, generates the Codex config, and launches Codex in one command:
Linux / macOS:
./scripts/start_codex_with_proxy.sh --project-directory /path/to/your/projectWindows PowerShell:
.\scripts\start_codex_with_proxy.ps1 -ProjectDirectory C:\path\to\your\project# Build and run
docker compose -f docker-compose.example.yml up --buildOr manually:
docker build -t deepseek-codex-proxy .
docker run -p 4000:4000 \
-v $(pwd)/config.yml:/config/config.yml:ro \
-e DEEPSEEK_API_KEY=sk-your-key \
deepseek-codex-proxyCheck available models:
curl http://127.0.0.1:4000/v1/modelsSend a test request:
curl http://127.0.0.1:4000/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek",
"input": "Say hello in one short sentence.",
"max_output_tokens": 1024
}'Test streaming:
curl http://127.0.0.1:4000/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek",
"input": "Count to 5.",
"stream": true
}'| Flag | Default | Description |
|---|---|---|
-c, --config <path> |
config.yml |
Path to configuration file (YAML or TOML) |
-l, --listen <addr> |
from config | Override listen address |
-v, --verbose |
false | Enable verbose debug output |
--print-addr |
- | Print the configured listen address and exit |
--print-default-model |
- | Print the default model alias and exit |
--print-codex-config <model> |
- | Write a minimal Codex config.toml for a model |
--codex-base-url <url> |
- | Base URL for generated Codex config |
--codex-home <dir> |
~/.codex |
Codex home directory for generated config |
| Endpoint | Method | Description |
|---|---|---|
/v1/responses |
POST | OpenAI Responses API (main entry) |
/responses |
POST | Same (without /v1 prefix) |
/v1/models |
GET | List available models |
/models |
GET | Same (without /v1 prefix) |
/health |
GET | Health check |
- Protocol Translation: OpenAI Responses API <-> DeepSeek Chat Completions
- Model Routing: Route different model aliases to different upstream models/providers
- Namespaced Tool Mapping: Translates Codex namespace tools (e.g.
shell.exec->shell__exec) - SSE Streaming: Full streaming support with proper Responses API event formatting
- Reasoning Cache: SQLite-based cache for DeepSeek thinking mode context restoration
- Multi-Provider: Configure multiple upstream providers and route by model alias
- Docker Support: Ready-to-use Dockerfile and docker-compose
| Issue | Solution |
|---|---|
connection refused |
Proxy not running, or listen address is wrong |
401 / authentication error |
Check your DeepSeek API key in config |
402 / payment error |
Check your DeepSeek Platform balance |
| Codex can't see models | Run --print-codex-config to regenerate Codex config |
| Tool calls fail | Ensure thinking is set to "disabled" in config |
| Port already in use | Change listen in config or stop the process on that port |