# Codex DeepSeek Responses Proxy

A localhost-only protocol proxy that lets the new Codex CLI — which speaks the
OpenAI Responses API — reach DeepSeek's Chat Completions API.

```text
Codex /v1/responses
        ↓
Local proxy 127.0.0.1:15721
        ↓
DeepSeek /chat/completions
```

The implementation was inspired by `cc-switch`'s Codex Chat conversion approach,
but the proxy itself is a standalone TypeScript service. It targets only
the official DeepSeek Chat Completions API and supports plain text, reasoning,
function tools, custom tools, parallel tool calls, cross-turn tool results, and
SSE streaming conversion.

## Download and run

Download the archive for your platform from
[GitHub Releases](https://github.com/flyeric0212/codex-deepseek-responses-proxy/releases):

- macOS arm64 or x64
- Linux arm64 or x64
- Windows x64

```bash
./codex-deepseek-proxy init
```

On Windows:

```powershell
.\codex-deepseek-proxy.exe init
```

This creates `config.env` beside the executable. Edit it and set your API key:

```dotenv
DEEPSEEK_API_KEY=sk-your-key
DEEPSEEK_BASE_URL=https://api.deepseek.com
DEEPSEEK_FLASH_MODEL=deepseek-v4-flash
DEEPSEEK_PRO_MODEL=deepseek-v4-pro
PROXY_HOST=127.0.0.1
PROXY_PORT=15721
UPSTREAM_TIMEOUT_MS=1200000
MAX_REQUEST_BODY_BYTES=10485760
LOG_LEVEL=info
```

Then start the proxy in the foreground:

```bash
./codex-deepseek-proxy
```

The executable does not require Node.js or Bun. It reads `config.env` from its
own directory, so it can be launched from any working directory. System
environment variables override values from the file. Use
`--config /path/to/config.env` to select another file.

Useful commands:

```bash
./codex-deepseek-proxy status
./codex-deepseek-proxy --version
./codex-deepseek-proxy --help
```

For releases before 1.0.0, a legacy `.env` beside the executable is read only
when `config.env` is absent and produces a migration warning.

If the actual model IDs your DeepSeek account provides differ from the aliases
shown in the Codex menu, map them via `DEEPSEEK_FLASH_MODEL` and
`DEEPSEEK_PRO_MODEL` — no need to modify the model catalog.

## Building from source

Source development requires Node.js 22 or later. Bun 1.3.14 is installed as a
development dependency for release builds.

```bash
npm install
cp config.env.example config.env
npm run dev
```

Compiled JavaScript:

```bash
npm run build
npm start
```

Health check:

```bash
curl http://127.0.0.1:15721/health
```

Build and smoke-test a standalone executable for the current platform:

```bash
npm run release:build
BINARY_PATH=artifacts/codex-deepseek-proxy-darwin-arm64 npm run release:smoke
```

## Configuring Codex

Merge the configuration from this repo into `~/.codex/config.toml`.

Or save it as a named Codex profile:

```bash
cp deepseek.config.toml ~/.codex/deepseek.config.toml
cp deepseek-model-catalog.json ~/.codex/deepseek-model-catalog.json
codex --profile deepseek
```

When using a profile, update `model_catalog_json` to the absolute path of the
copied file. The Codex CLI `--config` flag only accepts `key=value` overrides,
not TOML file paths.

The following settings are critical:

```toml
[model_providers.deepseek]
base_url = "http://127.0.0.1:15721/v1"
wire_api = "responses"
stream_idle_timeout_ms = 1200000
```

The model list is provided by `deepseek-model-catalog.json`. Restart Codex after
modifying the model catalog. During long DeepSeek queuing or reasoning, the
stream idle timeout should be consistent between Codex and the proxy; both
default to 20 minutes in this repo.

## Release verification and unsigned binaries

Every GitHub Release includes `SHA256SUMS.txt`. Verify the downloaded archive
before use:

```bash
# Linux
sha256sum codex-deepseek-proxy-*.tar.gz

# macOS
shasum -a 256 codex-deepseek-proxy-*.tar.gz
```

Compare the result with the matching line in `SHA256SUMS.txt`.

The initial macOS and Windows binaries are not code-signed. On macOS, remove
the quarantine attribute only after verifying the checksum:

```bash
xattr -d com.apple.quarantine ./codex-deepseek-proxy
```

Windows SmartScreen may require selecting **More info → Run anyway**.

## Verification

```bash
npm test
npm run check
npm run build
```

Maintainers publish a release by updating the package version, committing it,
and pushing the matching tag, for example `v0.3.0`. The release workflow rejects
a tag that does not exactly match `package.json`.

## Limitations

- Only supports the official DeepSeek Chat Completions upstream. Compatibility
  with other OpenAI-compatible services is not guaranteed, and the proxy does not
  include provider management UI, billing, or failover.
- DeepSeek V4 is a text-only model; requests containing images receive a clear
  400 error.
- Tool history is stored in memory. Start a fresh Codex session if the proxy
  restarts.
- Incomplete streaming tool arguments result in `response.failed` — partial
  arguments are never silently executed.
- The proxy listens only on `127.0.0.1` by default and should not be exposed
  to the public internet.
- The release executable runs in the foreground; background services,
  autostart, and automatic updates are not included.

## Streaming & Tool Call Semantics

- If the upstream omits `[DONE]` but has already sent a `finish_reason`, the
  proxy still closes the Responses stream normally.
- If a plain-text stream ends unexpectedly with valid content already delivered,
  the response returns `status=incomplete`.
- When tool calls are missing an ID, name, or complete JSON arguments, the
  entire set of tool calls returns `response.failed`; no subset is completed.
- When Codex sends only `previous_response_id + function_call_output`, the
  proxy restores the corresponding assistant tool call and `reasoning_content`
  from in-memory history.

## Troubleshooting

- **Tool execution stops after responding**: Make sure the proxy hasn't been
  restarted, and retry with a new Codex session — history is kept only inside
  the running proxy process.
- **Long reasoning (~5 min) interrupted**: Verify that `stream_idle_timeout_ms`
  in the active Codex profile is set to `1200000`.
- **`response.failed` returned**: Check the event's `error.type` first.
  `stream_truncated` means the upstream stream ended early;
  `invalid_tool_arguments` means the tool JSON was malformed.

## Attribution

Refer to [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for sources and
licenses.
