OpenAI-compatible API server that wraps Claude Code's Agent SDK. Exposes standard OpenAI endpoints so any OpenAI-compatible client (Aider, OpenCode, Cursor, Continue, custom apps) can use Claude models.
npm run dev — start dev server (tsx)
npm run build — compile TypeScript to dist/
npm start — run compiled output
npx tsc --noEmit — type check without emitting
| Env var |
Default |
Description |
PORT |
3456 |
Server port |
DEFAULT_MODEL |
sonnet |
Model when request omits model field |
TOOLS |
all |
all = full agent mode, none = chat-only (for Aider, OpenCode, etc.) |
| Method |
Path |
Description |
POST |
/v1/chat/completions |
OpenAI-compatible chat completions (streaming & non-streaming) |
GET |
/v1/models |
Lists available Claude models (full IDs + aliases) |
GET |
/health |
Health check |
src/
index.ts — Hono app, routes, server startup
config.ts — Env var loading (PORT, DEFAULT_MODEL, TOOLS)
routes/
chat-completions.ts — POST /v1/chat/completions handler
models.ts — GET /v1/models
health.ts — GET /health
core/
claude-executor.ts — Wraps @anthropic-ai/claude-agent-sdk query()
transform/
request.ts — OpenAI messages[] → prompt string + SDK options
response.ts — SDKMessage[] → OpenAI JSON response
streaming.ts — SDKMessage async stream → SSE delta chunks
types/
openai.ts — OpenAI request/response TypeScript interfaces
- Stateless: each request is a fresh SDK
query() call. No server-side session state. Full message history is flattened into the prompt.
- Two tools modes:
TOOLS=all (default): full agent mode — Claude Code runs with all built-in tools (Bash, Edit, Read, etc.) via bypassPermissions. Tool activity is invisible to the API consumer, only final text is returned.
TOOLS=none: chat-only mode — no tools, Claude acts as a plain LLM. Required for clients that handle code editing themselves (Aider, OpenCode, Cursor, etc.).
- Per-request override via
x-claude-tools: none|all header.
- Model mapping: pass-through only. Clients must send valid Claude model names (
sonnet, opus, haiku, or full IDs like claude-sonnet-4-6).
- Unsupported params:
temperature, max_tokens, top_p, etc. are silently accepted and ignored.
- No auth: local dev tool, relies on the machine's existing Claude authentication.
- No OpenAI tool/function calling: the
tools field in requests is accepted but ignored.
- ESM: project uses
"type": "module" with bundler module resolution.
- The Agent SDK (
@anthropic-ai/claude-agent-sdk) spawns a Claude Code child process per query() call.
SDKMessage is a large union type. We only care about assistant (text content), stream_event (streaming deltas), and result (usage/finish).
- Thinking model (Opus) thinking blocks are naturally excluded — we filter for
block.type === "text" from BetaMessage.content.
persistSession: false prevents writing session files to disk.
# Non-streaming
curl http://localhost:3456/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"sonnet","messages":[{"role":"user","content":"Hello"}]}'
# Streaming
curl http://localhost:3456/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"sonnet","messages":[{"role":"user","content":"Hello"}],"stream":true}'
# Chat-only mode (per-request)
curl http://localhost:3456/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-claude-tools: none" \
-d '{"model":"sonnet","messages":[{"role":"user","content":"Hello"}]}'
# List models
curl http://localhost:3456/v1/models
# With Aider (chat-only mode required)
TOOLS=none npm run dev
aider --openai-api-base http://localhost:3456/v1 --openai-api-key dummy --model openai/sonnet
- System-only messages (no user message): system prompt is used as the prompt
- Null/empty content in messages: returns 400 with clear error
- Multi-turn with shell-unsafe characters: use heredoc or --data-raw with curl
- Aider/OpenCode require
TOOLS=none — they expect a plain LLM, not an agent
https://github.com/gididaf/claude-code-api