# ein-ai-proxy A zero-dependency Node.js proxy that speaks the [Anthropic Messages API](https://docs.anthropic.com/en/api/messages) and routes requests to different AI providers based on the model name. Clients think they're talking to Anthropic — the proxy transparently rewrites model names and forwards to the correct backend. Built to run on a home server behind a reverse proxy (Caddy), letting you use Claude Code CLI and Roo Code from anywhere while optimizing costs across providers. ## How It Works ``` Client (Claude Code / Roo Code) │ │ POST /v1/messages { "model": "claude-sonnet-4-6", ... } ▼ ein-ai-proxy (port 3100) │ ├─ model starts with "claude-opus" → Anthropic API (unchanged) ├─ model starts with "claude-sonnet" → Z.ai API (rewritten to glm-4.7) ├─ model starts with "claude-haiku" → DeepSeek API (rewritten to deepseek-chat) └─ unknown model → Z.ai API (default, rewritten to glm-4.7) ``` The proxy: 1. Authenticates the client (Bearer token or x-api-key header) 2. Looks up the model name in a routing table 3. Rewrites the model name if needed (e.g., `claude-sonnet-4-6` → `glm-4.7`) 4. Forwards the request to the correct provider with that provider's API key 5. Pipes the response back — including SSE streaming, which is forwarded without buffering ## Routing Table | Client Sends | Provider | Rewrites To | Cost Model | |---|---|---|---| | `claude-opus-*` | Anthropic (api.anthropic.com) | *unchanged* | Pay-per-token | | `claude-sonnet-*` | Z.ai (api.z.ai) | `glm-4.7` | Flat rate subscription | | `claude-haiku-*` | DeepSeek (api.deepseek.com) | `deepseek-chat` | Cheap per-token | | `glm-4.7` | Z.ai | *unchanged* | Flat rate subscription | | `deepseek-chat` | DeepSeek | *unchanged* | Cheap per-token | | *anything else* | Z.ai | `glm-4.7` | Flat rate subscription | The proxy matches exact model names first, then tries prefix matching (so dated variants like `claude-sonnet-4-5-20250929` are handled automatically). See `CONTEXT.md` for the full routing table with every explicit entry. Z.ai and DeepSeek both expose Anthropic-compatible APIs natively, so the proxy only needs to rewrite the model name and auth headers — no response translation required. ## Setup ### Prerequisites - Node.js 18+ (the proxy uses zero npm dependencies — only Node.js built-ins) - A reverse proxy with HTTPS (Caddy, nginx, etc.) if exposing to the internet - API keys for whichever providers you want to use ### Install ```bash git clone ssh://git@git.ein-softworks.com:3022/harrison/ein-ai-proxy.git cd ein-ai-proxy # Create config from template cp config.example.json config.json ``` Edit `config.json` with your API keys: ```json { "port": 3100, "authToken": "a-long-random-string", "providers": { "anthropic": { "apiKey": "sk-ant-..." }, "zai": { "apiKey": "your-zai-key" }, "deepseek": { "apiKey": "your-deepseek-key" } } } ``` ### Run ```bash # Direct node proxy.mjs # Via npm npm start # With auto-reload during development npm run dev ``` ### Deploy as a Service On mini-server (where the proxy runs), the `deploy.sh` script validates your config, tests the proxy, and installs a systemd service: ```bash # SSH into mini-server first ssh mini-server cd ~/ai-proxy chmod +x deploy.sh ./deploy.sh ``` This installs `ein-ai-proxy.service` into systemd and starts it. After deployment: ```bash # Check status sudo systemctl status ein-ai-proxy # Restart after changes sudo systemctl restart ein-ai-proxy # View logs sudo journalctl -u ein-ai-proxy -f --no-pager ``` ### Reverse Proxy (Caddy) The proxy binds to `0.0.0.0:3100`. To expose it over HTTPS, put it behind a reverse proxy. Example Caddy config: ``` ai.example.com { reverse_proxy localhost:3100 { flush_interval -1 transport http { read_timeout 600s write_timeout 600s } } } ``` `flush_interval -1` is critical — it tells Caddy to flush SSE chunks immediately rather than buffering them. The 600s timeouts accommodate long reasoning responses. ## Client Configuration ### Finding Your Auth Token The proxy auth token is the `authToken` value in `config.json` on the server where the proxy runs. To retrieve it: ```bash ssh mini-server "node -e \"console.log(JSON.parse(require('fs').readFileSync('/home/harrison/ai-proxy/config.json','utf-8')).authToken)\"" ``` Or just SSH in and read the file directly: ```bash ssh mini-server cat ~/ai-proxy/config.json | grep authToken ``` This is the token you'll use in all client configurations below. ### Claude Code CLI Add to `~/.claude/settings.json`: ```json { "env": { "ANTHROPIC_BASE_URL": "https://ai.ein-softworks.com", "ANTHROPIC_AUTH_TOKEN": "your-proxy-auth-token", "API_TIMEOUT_MS": "3000000" } } ``` Claude Code sends model names like `claude-sonnet-4-6` by default, which the proxy routes to GLM-4.7. Use `/model` in Claude Code to switch to Opus when you need it. ### Roo Code (VS Code) 1. Set API Provider to **Anthropic** 2. Enter your proxy auth token as the API Key 3. Enable **Use custom base URL** and enter your proxy URL (e.g., `https://ai.ein-softworks.com`) You can create multiple profiles to switch between providers easily: - **default** → `claude-sonnet-4-5` → routes to GLM-4.7 (reasoning on) - **opus** → `claude-opus-4-6` → routes to real Anthropic (reasoning on) - **cheap** → `claude-haiku-4-5` → routes to DeepSeek (reasoning off) ## API ### Health Check ```bash curl https://ai.ein-softworks.com/health ``` Returns the proxy status and current routing table. No authentication required. ### Messages Endpoint ```bash curl -X POST https://ai.ein-softworks.com/v1/messages \ -H "Authorization: Bearer your-proxy-auth-token" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}] }' ``` Supports both `Authorization: Bearer ` and `x-api-key: ` authentication styles — compatible with any Anthropic API client. ## Important Notes ### Thinking-Block Provider Switching **Do not switch between providers mid-conversation when reasoning/thinking is enabled.** GLM-4.7's thinking block format differs from Anthropic's and will be rejected if sent to the Anthropic API. Start a fresh session (`/clear` in Claude Code) before switching from Sonnet to Opus or vice versa. ### MCP Tools MCP tools execute client-side (on whatever machine runs Claude Code or Roo Code), not on the proxy server. The proxy only routes LLM API calls. ### Adding Routes To route additional models, add entries to the `MODEL_ROUTES` object in `proxy.mjs`. The proxy matches exact model names first, then tries prefix matching, then falls back to the default route (Z.ai / GLM-4.7). ## AI Context Files This repo includes context files for both **Claude Code CLI** and **Roo Code** (VS Code). The two tools use different file conventions, so we keep them in sync: ### Project-level context | Claude Code reads | Roo Code reads | Source of truth | |---|---|---| | `CLAUDE.md` | `.clinerules` | `CLAUDE.md` | | `CONTEXT.md` | `CONTEXT.md` | `CONTEXT.md` | `.clinerules` is a **git-tracked symlink** pointing to `CLAUDE.md`. Both tools read the same content from the same source — edit `CLAUDE.md` and both stay in sync automatically. `CONTEXT.md` is the detailed infrastructure reference. Both tools can read it directly when needed. ### Global context | Claude Code | Roo Code | |---|---| | `~/.claude/CLAUDE.md` (file on disk) | Custom Instructions setting (in Roo Code UI) | The source of truth is `global-claude-md/CLAUDE.md` in this repo. To update both tools, run the sync script: ```bash # 1. Edit the source vim global-claude-md/CLAUDE.md # 2. Commit and push git add -A && git commit -m "Update global context" && git push # 3. Sync to both tools ./sync-context.sh ``` The script copies the file to `~/.claude/CLAUDE.md` (for Claude Code) and prints the content for you to paste into Roo Code's Custom Instructions (Roo sidebar → gear icon → Custom Instructions). To also update mini-server's Claude Code: ```bash scp global-claude-md/CLAUDE.md mini-server:~/.claude/CLAUDE.md ``` ## Files | File | Description | |---|---| | `proxy.mjs` | Main proxy server — zero npm dependencies, Node.js 18+ built-ins only | | `config.json` | API keys and auth token (**gitignored**, never commit) | | `config.example.json` | Template config — copy to `config.json` and fill in | | `.gitignore` | Excludes `config.json`, `node_modules/`, and `*.log` | | `ein-ai-proxy.service` | Systemd unit file for running as a service | | `deploy.sh` | Deployment script — validates config, tests proxy, installs service | | `sync-context.sh` | Syncs global AI context to Claude Code and Roo Code | | `package.json` | Package metadata (no dependencies) | | `CLAUDE.md` | Project-level context for Claude Code | | `.clinerules` | Symlink → `CLAUDE.md` — project-level context for Roo Code | | `CONTEXT.md` | Full infrastructure documentation (network, servers, operations) | | `global-claude-md/CLAUDE.md` | Source of truth for global context (both tools) | ## License MIT