William Stuckey e1572a69c6 Fix deploy.sh step counter and document auth token retrieval
- Renumber deploy.sh steps from [1/5]-[4/5] to [1/4]-[4/4] (step 5 was removed)
- Add 'Finding Your Auth Token' section to README.md explaining how to
  retrieve the authToken from config.json on mini-server
2026-03-26 18:31:04 -05:00
2026-03-26 13:54:22 -05:00
2026-03-26 13:54:22 -05:00
2026-03-26 13:54:22 -05:00

ein-ai-proxy

A zero-dependency Node.js proxy that speaks the Anthropic Messages API 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-6glm-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-4-6 Anthropic (api.anthropic.com) unchanged Pay-per-token
claude-sonnet-4-6 Z.ai (api.z.ai) glm-4.7 Flat rate subscription
claude-sonnet-4-5 Z.ai glm-4.7 Flat rate subscription
claude-haiku-4-5-* 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

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

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:

{
  "port": 3100,
  "authToken": "a-long-random-string",
  "providers": {
    "anthropic": { "apiKey": "sk-ant-..." },
    "zai":       { "apiKey": "your-zai-key" },
    "deepseek":  { "apiKey": "your-deepseek-key" }
  }
}

Run

# Direct
node proxy.mjs

# Via npm
npm start

# With auto-reload during development
npm run dev

Deploy as a Service

The included deploy.sh script validates your config, tests the proxy, and installs a systemd service:

chmod +x deploy.sh
./deploy.sh

This installs ein-ai-proxy.service into systemd and starts it. After deployment:

# 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:

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:

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:

{
  "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:

  • defaultclaude-sonnet-4-5 → routes to GLM-4.7 (reasoning on)
  • opusclaude-opus-4-6 → routes to real Anthropic (reasoning on)
  • cheapclaude-haiku-4-5 → routes to DeepSeek (reasoning off)

API

Health Check

curl https://ai.ein-softworks.com/health

Returns the proxy status and current routing table. No authentication required.

Messages Endpoint

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 <token> and x-api-key: <token> 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).

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
ein-ai-proxy.service Systemd unit file for running as a service
deploy.sh Deployment script — validates config, tests proxy, installs service
package.json Package metadata (no dependencies)
CLAUDE.md Project-level context loaded by AI coding agents
CONTEXT.md Full infrastructure documentation (network, servers, operations)
global-claude-md/CLAUDE.md User-level context to copy to ~/.claude/CLAUDE.md

License

MIT

S
Description
No description provided
Readme 126 KiB
Languages
JavaScript 89%
Shell 11%