Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

Running Claude Code with OmniRoute and Dynamic Model Selection

A guide to proxying Anthropic's Claude Code CLI through a local OmniRoute gateway. Includes an interactive fzf model switcher script to change model combos (Claude Opus, GPT-5.6, DeepSeek-v4, Kimi K3, Gemini) per session, plus a background service setup for macOS.


Architecture

Claude Code connects to Anthropic by default. By redirecting ANTHROPIC_BASE_URL to OmniRoute (http://localhost:20128), request streams are proxied through OmniRoute's quota-aware fallback engine.

+-------------------+       +-----------------------+       +------------------------------------+
|  claude-pick      | ----> |  Claude Code CLI      | ----> |  OmniRoute Gateway                 |
|  (fzf selector)   |       |  (reads settings.json)|       |  http://localhost:20128            |
+-------------------+       +-----------------------+       +------------------------------------+
                                                                              |
                                                                              v
                                                            +------------------------------------+
                                                            | Model Combos & Provider Fallbacks  |
                                                            | • Claude: Opus 5 -> Sonnet 4.6     |
                                                            | • GPT 5.6: Sol -> Terra            |
                                                            | • DeepSeek / Kimi / Gemini         |
                                                            +------------------------------------+

Prerequisites

  • Node.js (v18+)
  • OmniRoute (npm install -g omniroute)
  • Claude Code CLI (claude)
  • fzf (brew install fzf)

Setup

1. Configure Claude Code (~/.claude/settings.json)

Set ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY in your global Claude Code configuration:

{
  "env": {
    "ANTHROPIC_BASE_URL": "http://localhost:20128",
    "ANTHROPIC_API_KEY": "YOUR_OMNIROUTE_API_KEY"
  },
  "enabledMcpjsonServers": [],
  "skipDangerousModePermissionPrompt": true,
  "theme": "dark",
  "largeContextWindowFallback": true
}

Important: Do not define ANTHROPIC_MODEL inside settings.json. Values set in settings.json override environment variables exported in the terminal, which prevents dynamic model switching via shell scripts.

2. Interactive Model Switcher (claude-pick)

Save the following script to ~/.local/bin/claude-pick and make it executable (chmod +x ~/.local/bin/claude-pick). It queries OmniRoute's endpoints, groups available combos and provider models, and opens an interactive fzf prompt before launching Claude Code.

#!/usr/bin/env bash

OMNIROUTE_URL="${OMNIROUTE_URL:-http://localhost:20128}"
OMNIROUTE_KEY="${OMNIROUTE_API_KEY:-YOUR_OMNIROUTE_API_KEY}"
FZF_BIN="$(which fzf 2>/dev/null || echo /opt/homebrew/bin/fzf)"

export ANTHROPIC_BASE_URL="$OMNIROUTE_URL"
export ANTHROPIC_API_KEY="$OMNIROUTE_KEY"

if ! curl -sf "$OMNIROUTE_URL/v1/models" -H "x-api-key: $OMNIROUTE_KEY" -o /dev/null 2>/dev/null; then
  echo "OmniRoute server is down. Starting background instance..."
  omniroute --no-open &
  sleep 4
fi

MODEL_LIST=$(python3 - <<PYEOF
import json, subprocess, os

base = os.environ["ANTHROPIC_BASE_URL"]
key  = os.environ["ANTHROPIC_API_KEY"]

def fetch(path):
    r = subprocess.run(
        ["curl", "-sf", base + path, "-H", "x-api-key: " + key],
        capture_output=True, text=True
    )
    return json.loads(r.stdout)

models = fetch("/v1/models")["data"]
combos = fetch("/api/v1/combos")["data"]

lines = []

lines.append("--- COMBOS (Fallback Chains) --------------------------------------------")
for c in combos:
    lines.append(f"COMBO:{c['name']}  ·  {len(c['models'])} models  ·  {c['strategy']}")

lines.append("")
lines.append("--- AUTO-ROUTED --------------------------------------------------------")
for m in models:
    if m["id"].startswith("auto/"):
        lines.append(m["id"])

groups = {}
for m in models:
    mid = m["id"]
    if mid.startswith("auto/") or mid.startswith("no-think/"):
        continue
    owner = m.get("owned_by", "other")
    groups.setdefault(owner, []).append(mid)

for owner in sorted(groups.keys()):
    lines.append("")
    lines.append(f"--- {owner.upper()} ------------------------------------------------------------")
    for mid in sorted(groups[owner]):
        lines.append(mid)

lines.append("")
lines.append("--- NO-THINK VARIANTS ---------------------------------------------------")
for m in models:
    if m["id"].startswith("no-think/"):
        lines.append(m["id"])

print("\n".join(lines))
PYEOF
)

TOTAL=$(echo "$MODEL_LIST" | grep -c "^[^─-]" || true)

SELECTED=$(echo "$MODEL_LIST" | "$FZF_BIN" \
  --ansi \
  --height=85% \
  --layout=reverse \
  --border=rounded \
  --prompt="Model > " \
  --header="OmniRoute Models ($TOTAL available) - Select or search" \
  --no-multi \
  2>/dev/null)

if [ -z "$SELECTED" ]; then
  MODEL_ID="Claude"
elif [[ "$SELECTED" == COMBO:* ]]; then
  MODEL_ID=$(echo "$SELECTED" | sed 's/^COMBO://' | awk -F'  ·' '{print $1}' | xargs)
elif [[ "$SELECTED" == -* ]]; then
  MODEL_ID="Claude"
else
  MODEL_ID=$(echo "$SELECTED" | xargs)
fi

echo "Selected model: $MODEL_ID"
export ANTHROPIC_MODEL="$MODEL_ID"
export ANTHROPIC_SMALL_FAST_MODEL="Basic Tasks"

exec claude --dangerously-skip-permissions

3. Background Daemon (launchd on macOS)

To ensure OmniRoute runs automatically in the background without manually launching it each session, create ~/Library/LaunchAgents/com.omniroute.server.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.omniroute.server</string>

    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/omniroute</string>
        <string>--no-open</string>
    </array>

    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
        <key>HOME</key>
        <string>/Users/YOUR_USER</string>
    </dict>

    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>ThrottleInterval</key>
    <integer>3</integer>

    <key>StandardOutPath</key>
    <string>/Users/YOUR_USER/.omniroute/logs/omniroute.stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/YOUR_USER/.omniroute/logs/omniroute.stderr.log</string>
    <key>WorkingDirectory</key>
    <string>/Users/YOUR_USER/.omniroute</string>
</dict>
</plist>

Load the daemon:

mkdir -p ~/.omniroute/logs
launchctl unload ~/Library/LaunchAgents/com.omniroute.server.plist 2>/dev/null || true
launchctl load -w ~/Library/LaunchAgents/com.omniroute.server.plist

Note: The --no-open flag prevents OmniRoute from launching browser tabs every time the background service starts.


Important Gotchas & Implementation Details

  1. ANTHROPIC_BASE_URL Path Format: Set the base URL to http://localhost:20128 (or your host IP). Do not append /v1 to the base URL string. Claude Code appends /v1/messages internally.

  2. Environment Variable Precedence: If ANTHROPIC_MODEL is present in ~/.claude/settings.json, Claude Code enforces it for all sessions regardless of exported terminal environment variables. Keep ANTHROPIC_MODEL out of settings.json so claude-pick can pass models dynamically.

  3. Authentication Key Name: Claude Code expects ANTHROPIC_API_KEY for API key authentication. Passing ANTHROPIC_AUTH_TOKEN causes Claude Code to default to its browser-based Anthropic login flow.

  4. Schema Validation: In settings.json, ensure "enabledMcpjsonServers" is set to an empty array [] rather than a boolean.


Verification & Debugging

To confirm requests are routing to OmniRoute correctly:

# Check running service status
launchctl list | grep omniroute

# Check latest OmniRoute transaction logs
ls -t ~/.omniroute/call_logs/$(date +%Y-%m-%d)/ | head -1 | xargs -I {} cat ~/.omniroute/call_logs/$(date +%Y-%m-%d)/{}

Check the log summary for requestedModel and actualModel to confirm the selected combo routed to the expected underlying model.

About

Step-by-step guide to connecting OmniRoute to Claude Code with dynamic model selection, auto-fallback combos, and macOS launcher app.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages