npm package: @iyulab/claude-bridge
Description: OpenAI-compatible API server for Claude Code
Latest version: 0.5.1

# claude-bridge

Use **Claude Code** through any OpenAI-compatible client.

claude-bridge exposes a locally authenticated Claude Code CLI as an OpenAI-compatible HTTP endpoint (`/v1/chat/completions`). Any tool that speaks the OpenAI protocol — OpenAI SDK, LangChain, Open WebUI, curl — can talk to Claude Code through this bridge.

```
Client (OpenAI SDK / curl / Open WebUI / LangChain)
    ↓ HTTP
claude-bridge (Express, localhost:6190)
    ├── POST /v1/chat/completions       (streaming + non-streaming)
    ├── GET  /v1/models
    └── POST /v1/chat/completions/:sessionId/cancel
    ↓
@anthropic-ai/claude-agent-sdk  →  query()
    ↓
Claude Code CLI (locally authenticated)
```

## Models

### Built-in Models

| Model | Description | Tools | Use Case |
|-------|-------------|-------|----------|
| `claude-code` | Agentic coding assistant | Read, Write, Edit, Bash, Glob, Grep | Code analysis, file operations, git tasks |
| `claude-text` | Pure text generation | None | Summarization, translation, structured output, creative writing |
| `claude-reader` | Read-only code analysis | Read, Glob, Grep | Safe code review, codebase Q&A (no file modifications) |

`claude-code` is the default — Claude operates as a coding agent with full tool access. `claude-text` disables all agent tools, so Claude responds as a pure LLM that faithfully follows system prompt instructions. `claude-reader` is a safe middle ground — Claude can read and search code but cannot modify files or run commands.

### Model Passthrough

Any model name not listed above is passed directly to the Claude Agent SDK as a model override, running in text mode (no tools):

```bash
# Use a specific Claude model
curl http://localhost:6190/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5-20250514",
    "messages": [{"role": "user", "content": "Explain quantum computing"}]
  }'
```

This lets you target specific model versions (e.g., `claude-sonnet-4-5-20250514`, `claude-haiku-4-5-20251001`) for cost or speed optimization.

## Quick Start

### Prerequisites

- **Node.js 18+** — [Download](https://nodejs.org/)
- **Claude Code CLI** — installed and authenticated

```bash
npm install -g @anthropic-ai/claude-code
claude login
```

### Run instantly with npx

```bash
npx @iyulab/claude-bridge
```

### Or install globally

```bash
npm install -g @iyulab/claude-bridge
claude-bridge
```

### Configure with environment variables

```bash
PORT=8080 AUTH_TOKEN=mytoken npx @iyulab/claude-bridge
```

The server starts on `http://localhost:6190` by default.

## Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `6190` | Server port |
| `WORKDIR` | *(empty)* | Working directory for Claude Code. If set, all sessions share this directory. If empty, each session gets an isolated directory under `<cwd>/sessions/<session-id>/` |
| `AUTH_TOKEN` | *(empty)* | Set to enable Bearer token authentication. Empty = no auth (local-only mode, similar to Ollama) |
| `LOG` | `true` | Enable request/response logging. Set to `false` to disable. Logs are written to `logs/sessions/<session-id>.jsonl` |

## Usage

### With curl

```bash
# Agentic coding (claude-code)
curl http://localhost:6190/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-code",
    "messages": [{"role": "user", "content": "Read src/index.ts and explain it"}]
  }'

# Pure text generation (claude-text)
curl http://localhost:6190/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-text",
    "messages": [
      {"role": "system", "content": "Summarize the following text in 3 bullet points."},
      {"role": "user", "content": "..."}
    ]
  }'

# Streaming
curl http://localhost:6190/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-code",
    "messages": [{"role": "user", "content": "Explain this project structure"}],
    "stream": true
  }'
```

### With OpenAI SDK (Python)

```python
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:6190/v1",
    api_key="any-value",  # ignored if AUTH_TOKEN is not set
)

# Agentic coding
response = client.chat.completions.create(
    model="claude-code",
    messages=[{"role": "user", "content": "Read src/index.ts and explain it"}],
)
print(response.choices[0].message.content)

# Pure text generation
response = client.chat.completions.create(
    model="claude-text",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain quantum computing in simple terms."},
    ],
)
print(response.choices[0].message.content)
```

### With OpenAI SDK (Node.js)

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "http://localhost:6190/v1",
  apiKey: "any-value",
});

const response = await client.chat.completions.create({
  model: "claude-code",
  messages: [{ role: "user", content: "List all TODO comments in this project" }],
});
console.log(response.choices[0].message.content);
```

### Session Continuity

Responses include a `session_id` field. Pass it back to continue the conversation in the same context:

```bash
# First request — new session
curl http://localhost:6190/v1/chat/completions \
  -d '{"model":"claude-code","messages":[{"role":"user","content":"Read package.json"}]}'
# Response includes "session_id": "abc-123"

# Follow-up — same session
curl http://localhost:6190/v1/chat/completions \
  -d '{"model":"claude-code","messages":[{"role":"user","content":"Now add a test script"}],"session_id":"abc-123"}'
```

### Cancel a Running Request

```bash
curl -X POST http://localhost:6190/v1/chat/completions/abc-123/cancel
```

## Logging

Request/response logging is enabled by default. Each session's logs are written to `logs/sessions/<session-id>.jsonl` in JSONL format.

Console output uses a human-friendly format:

```
[10:00:01] → POST /v1/chat/completions session=abc-123 stream=true msgs=3
[10:00:02] ⚡ init claude_session=sdk-xyz
[10:00:05] 🔧 tool_use Read file_path=/foo.ts
[10:00:09] ← 200 session=abc-123 8000ms
```

Set `LOG=false` to disable all logging.

## API Reference

### POST /v1/chat/completions

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `model` | string | No | `claude-code` (default), `claude-text`, `claude-reader`, or any Claude model ID for passthrough |
| `messages` | array | Yes | Array of `{role, content}` messages |
| `stream` | boolean | No | Enable SSE streaming (default: `false`) |
| `session_id` | string | No | Session ID for conversation continuity |

### GET /v1/models

Returns available models: `claude-code`, `claude-text`, and `claude-reader`.

### POST /v1/chat/completions/:sessionId/cancel

Aborts an in-progress query for the given session.

## Error Codes

All errors follow the OpenAI error format `{ error: { message, type, code } }`:

| Situation | HTTP | code |
|-----------|------|------|
| Session busy (concurrent request) | 409 | `session_busy` |
| Cancel target not found | 404 | `session_not_active` |
| Claude execution error | 500 | `claude_execution_error` |
| Auth failure | 401 | `invalid_api_key` |
| Bad request | 400 | `invalid_request_error` |

## Architecture

### Key Design Decisions

- **Thin translation layer** — only converts between OpenAI API format and Claude Agent SDK calls
- **Multiple modes** — `claude-code` for agentic coding, `claude-reader` for safe read-only analysis, `claude-text` for pure text, or any Claude model ID for passthrough
- **Session isolation** — each session gets its own working directory under `<cwd>/sessions/<session-id>/`
- **Busy lock** — one request per session at a time (concurrent requests return HTTP 409)
- **Structured logging** — JSONL per session + human-friendly console output

### Project Structure

```
src/
├── cli.ts                # CLI entrypoint (preflight checks + server start)
├── index.ts              # Express app factory (createApp, startServer)
├── config.ts             # Environment variable loading
├── middleware/
│   ├── auth.ts           # Optional Bearer token auth
│   └── logger.ts         # Request/response logging middleware
├── routes/
│   ├── chat.ts           # /v1/chat/completions + cancel
│   └── models.ts         # /v1/models
├── services/
│   ├── chat.ts           # Claude Agent SDK integration
│   ├── session.ts        # In-memory session management
│   ├── logger.ts         # Logging service (console + JSONL)
│   └── formatter.ts      # SDK → OpenAI response formatting
└── types/
    └── openai.ts         # OpenAI-compatible type definitions
```

### Tech Stack

- **Runtime**: Node.js + TypeScript (ESM)
- **Framework**: Express 5
- **Claude Integration**: `@anthropic-ai/claude-agent-sdk` — `query()` function
- **Session Storage**: In-memory Map (resets on server restart)

## Development

```bash
git clone https://github.com/iyulab/claude-bridge.git
cd claude-bridge
npm install
npm run dev
```

To build for production:

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

## Current Limitations

- Sessions are in-memory only (lost on restart)
- `usage` token counts are estimated (based on SDK-reported values)
- OpenAI parameters accepted but ignored: `max_tokens`, `max_completion_tokens`, `temperature`, `top_p`, `stop`, `presence_penalty`, `frequency_penalty` — the Claude Agent SDK does not expose these controls
- No function calling / tool_use mapping

## License

MIT