Files

120 lines
5.2 KiB
Markdown

# 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 <level>` | `low`/`medium`/`high`/`xhigh`/`max`. Overrides `temperature` mapping. |
| `fallback_model` | `--fallback-model <model>` | Model used when primary hits rate limit |
| `system_prompt_mode` | `--system-prompt` / `--append-system-prompt` | `"replace"` (default) or `"append"` |
| `max_turns` | `--max-turns <n>` | Cap agentic turns (non-interactive) |
| `max_budget_usd` | `--max-budget-usd <amount>` | Per-request cost cap |
| `json_schema` | `--json-schema <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.