A small TypeScript service that looks like Anthropic to Claude Code and talks to an OpenAI-compatible or Ollama backend underneath.
This repo is intentionally MVP-first:
POST /v1/messagesPOST /v1/messages/count_tokensGET /health- Text-only request/response translation
- Anthropic-style SSE streaming for
stream: true
What is not supported yet:
- Tool calling
- Images
- Prompt caching semantics
- Exact Anthropic token accounting
Claude Code expects Anthropic-flavored JSON and Anthropic-flavored server-sent events. This proxy keeps Claude Code untouched and concentrates the protocol weirdness in one service:
- Anthropic request validation and normalization
- Model mapping from Anthropic names to backend names
- Provider adapters for OpenAI-compatible servers or Ollama
- Anthropic response and SSE event translation
- Rough token counting for
/v1/messages/count_tokens
- Install dependencies:
npm install- Create your env file:
cp .env.example .env- Point the provider at your backend and adjust the model map.
Example for an OpenAI-compatible local server:
PROVIDER=openai
PROVIDER_BASE_URL=http://127.0.0.1:8000/v1
MODEL_MAP_JSON={"claude-sonnet-4-5":"qwen2.5-coder-32b-instruct"}Example for Ollama:
PROVIDER=ollama
PROVIDER_BASE_URL=http://127.0.0.1:11434
MODEL_MAP_JSON={"claude-sonnet-4-5":"qwen2.5-coder:32b"}- Run the proxy:
npm run dev- Point Claude Code at it:
ANTHROPIC_BASE_URL=http://127.0.0.1:8080 \
ANTHROPIC_API_KEY=dummy \
claude --bare --tools "" "hello"Supported environment variables:
PORTHOSTLOG_LEVELPROVIDER=openai|ollamaPROVIDER_BASE_URLPROVIDER_API_KEYDEFAULT_MODELMODEL_MAP_JSONMODEL_MAP_FILEPROVIDER_TIMEOUT_MS
MODEL_MAP_JSON should be a JSON object that maps the Anthropic model names Claude Code sends to the provider model names your backend understands.
Accepts Anthropic-style messages requests and returns an Anthropic-style message response.
Streaming requests emit the Anthropic event order Claude Code expects:
message_startcontent_block_start- repeated
content_block_delta content_block_stopmessage_deltamessage_stop
Returns a rough input_tokens estimate. This is good enough for an MVP, but it is not exact Anthropic accounting.
Returns a simple readiness payload:
{
"status": "ok"
}src/
app.ts
server.ts
config/
providers/
routes/
schemas/
services/
translators/
tests/
utils/
MVP is working when:
- Claude Code can send text-only prompts through this proxy
- streaming works
- a mapped backend model answers successfully
The next major step is tool-use support:
- map Anthropic tools to backend tools
- map backend tool calls back to
tool_use - accept
tool_resultblocks on follow-up turns - preserve tool call IDs exactly
After that:
- improve token counting with model-specific tokenizers
- broaden provider coverage
- add request tracing and richer observability
Useful commands:
npm run dev
npm run typecheck
npm run test
npm run build