# claude-max-bridge A small FastAPI server that exposes an **OpenAI-compatible `/v1/chat/completions` endpoint** and translates each request into a `claude` CLI subprocess call. This lets LiteLLM (or any OpenAI-compatible client) consume a Claude Max subscription through the locally-installed `claude` binary without needing an Anthropic API key. ## How it works 1. Client sends an OpenAI chat-completions request (streaming or non-streaming). 2. The bridge extracts the system prompt and conversation turns. 3. It spawns `claude --print --output-format stream-json --input-format stream-json ...` and pipes the conversation as NDJSON to stdin. 4. It parses the NDJSON stdout, computes text deltas from partial assistant messages, and re-emits them as OpenAI SSE chunks (or buffers for non-streaming). 5. Usage totals and cost from the `result` event are forwarded in the response. Authentication is handled entirely by the `claude` binary; the bridge bind-mounts the existing `~/.claude` credentials directory (rw, so the CLI can refresh expired OAuth tokens automatically). ## Environment variables | Variable | Default | Description | |---------------------|------------------------|--------------------------------------------------| | `CLAUDE_BIN` | `/usr/local/bin/claude`| Path to the `claude` CLI binary | | `CLAUDE_HOME` | `/root/.claude` | Path to the Claude config / credentials directory| | `LOG_LEVEL` | `INFO` | Python logging level | | `REQUEST_TIMEOUT_S` | `120` | Per-request subprocess timeout in seconds | ## Running with Docker ```bash docker build -t claude-max-bridge . docker run -d \ --name claude-max-bridge \ -p 8000:8000 \ -v /usr/local/bin/claude:/usr/local/bin/claude:ro \ -v /root/.claude:/root/.claude:rw \ -v /root/.claude.json:/root/.claude.json:rw \ claude-max-bridge ``` ## Supported models - `claude-sonnet-4-6` (default fallback) - `claude-opus-4-7` - `claude-haiku-4-5` - `claude-opus-4-5` - `claude-opus-4-0` - `claude-sonnet-3-7` - `claude-haiku-3-5` - Short aliases: `sonnet`, `opus`, `haiku` Unknown model names fall back to `claude-sonnet-4-6`. ## Extended parameters (`extra_body`) The bridge maps several CLI flags via the `extra_body` field: | `extra_body` key | CLI flag | Notes | |-------------------------------------------|----------------------------------------|-------| | `effort` | `--effort ` | `low`/`medium`/`high`/`xhigh`/`max`. Overrides `temperature` mapping. | | `fallback_model` | `--fallback-model ` | Model used when primary hits rate limit | | `system_prompt_mode` | `--system-prompt` / `--append-system-prompt` | `"replace"` (default) or `"append"` | | `max_turns` | `--max-turns ` | Cap agentic turns (non-interactive) | | `max_budget_usd` | `--max-budget-usd ` | Per-request cost cap | | `json_schema` | `--json-schema ` | Structured JSON output | | `exclude_dynamic_system_prompt_sections` | `--exclude-dynamic-system-prompt-sections` | Better cache reuse for multi-user | `temperature` is also mapped to `--effort` when no explicit `effort` is set: `<0.2→low`, `<0.4→medium`, `<0.7→high`, `<0.9→xhigh`, `≥0.9→max`. `response_format.json_schema` (OpenAI standard) is also recognised and forwarded to `--json-schema` automatically. ## Response extensions Non-streaming responses and streaming done-chunks include: | Field | Description | |------------------------|-------------| | `x_claude_cost_usd` | Actual cost as reported by CLI (`total_cost_usd`). $0 for Max subscription. | | `x_claude_rate_limit` | Rate-limit info: `utilization`, `resetsAt`, `rateLimitType` | ## LiteLLM configuration ```yaml model_list: - model_name: claude-sonnet-4-6 litellm_params: model: openai/claude-sonnet-4-6 api_base: http://claude-max-bridge:8000/v1 api_key: "unused" stream_timeout: 120 timeout: 120 input_cost_per_token: 0.000003 # for dashboard visibility; actual cost $0 output_cost_per_token: 0.000015 ``` ## Endpoints | Method | Path | Description | |--------|------------------------|------------------------------------------| | GET | `/health` | Liveness check, always returns `200 OK` | | GET | `/v1/models` | Lists supported models | | POST | `/v1/chat/completions` | OpenAI-compatible chat completions | ## Notes - Rate-limit responses from the CLI are translated to HTTP `429`. - Auth errors are translated to HTTP `401`. - The subprocess is killed on client disconnect or timeout. - `top_p`, `frequency_penalty`, `presence_penalty`, `seed`, `stop`, `n` are ignored. - MCP servers are explicitly disabled (`--mcp-config '{"mcpServers":{}}'` `--strict-mcp-config`) to prevent credential leakage and side-effects.