[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-105436":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":15,"stars7d":15,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":16,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":29,"readmeContent":30,"aiSummary":9,"trendingCount":14,"starSnapshotCount":14,"syncStatus":31,"lastSyncTime":32,"discoverSource":33},105436,"model-relay","cablate\u002Fmodel-relay","cablate","Per-window model switcher, subagent router, and automatic 429 failover for Claude Code — switch any window with \u002Fprovider, delegate subagents across models, and keep Remote Control & statuslines working.",null,"JavaScript",101,9,72,0,25,75,74,"MIT License",false,"main",[22,23,24,25,26,27,28],"anthropic","claude-code","deepseek","failover","gemini","glm","llm-router","2026-11-01 00:16:42","\u003Cp align=\"center\">\n  \u003Cpicture>\n    \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs\u002Fassets\u002Fbanner-dark.svg\">\n    \u003Cimg src=\"docs\u002Fassets\u002Fbanner-light.svg\" width=\"900\" alt=\"model-relay: per-window model switcher, subagent router, and 429 failover for Claude Code\">\n  \u003C\u002Fpicture>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cb>Switch any Claude Code window to another model from an interactive panel, route subagents to any provider,\u003Cbr>and automatically fail over when Claude hits a usage limit—all on localhost, with Remote Control and statuslines intact.\u003C\u002Fb>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcablate\u002Fmodel-relay\u002Freleases\">\u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fv\u002Frelease\u002Fcablate\u002Fmodel-relay?include_prereleases&display_name=release&label=release&color=38bdf8\" alt=\"Latest release\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcablate\u002Fmodel-relay\u002Factions\u002Fworkflows\u002Fci.yml\">\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fcablate\u002Fmodel-relay\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg\" alt=\"CI\">\u003C\u002Fa>\n  \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FClaude_Code-2.1.287%2B-a78bfa\" alt=\"Claude Code 2.1.287 or later\">\n  \u003Ca href=\"LICENSE\">\u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-blue\" alt=\"MIT license\">\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cb>English\u003C\u002Fb> · \u003Ca href=\"README.zh-TW.md\">繁體中文\u003C\u002Fa> · \u003Ca href=\"#install\">Install\u003C\u002Fa> · \u003Ca href=\"#whats-inside\">Features\u003C\u002Fa> · \u003Ca href=\"#configuration\">Configuration\u003C\u002Fa> · \u003Ca href=\"CHANGELOG.md\">Changelog\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"docs\u002Fassets\u002Fpanel.svg\" width=\"900\" alt=\"Animation: on the left, main chat uses Claude while subagents route to any provider and local gateways auto-launch; on the right, the interactive AbovePrompt panel lets you test providers and switch only the current window with one click\">\n\u003C\u002Fp>\n\n## Why\n\nClaude Code binds model settings globally across every terminal window. `model-relay` runs a lightweight router on localhost (`127.0.0.1:8950`) so each window and subagent can use the model you want—with zero external cloud proxies.\n\n| When… | Without `model-relay` | With `model-relay` |\n|---|---|---|\n| You want to switch just one window to another model | Editing `settings.json` switches every open terminal at once | Type `\u002Fprovider` to switch only the current window from the panel above your prompt (and write its active model to `model-relay-status\u002F\u003Csession id>` for your statusline) |\n| You want `haiku` or a specific subagent on another model | Changing global env vars forces your main Opus\u002FSonnet chat onto that endpoint too | `\u002Fprovider alias haiku \u003Cname>` offloads `haiku` globally, or `model-relay:\u003Cname>` spawns a one-off subagent on that provider with all tools intact |\n| Claude hits a 5-hour usage limit (HTTP 429) or a provider hiccups | Your turn fails mid-task until quota resets | Automatic retries and `failover.chain` hand the request to your backup provider and pin only that window while new windows return to Claude after cooldown |\n| You use Remote Control (`claude.ai\u002Fcode`) from your phone or browser | Custom `ANTHROPIC_BASE_URL` blocks Remote Control from connecting | Local HTTPS proxy mode (`\u002Fprovider rc on`) keeps Remote Control working while routing each window independently |\n| Third-party gateways lose history\u002Ftools or reject `ToolSearch` \u002F `cache_control` | `message-threads` deltas cause mid-chat amnesia, and `ToolSearch` hides tools or triggers HTTP 400 | Automatically sends full conversations keeps `ToolSearch` working, and cleans `cache_control` per provider on the fly |\n\n## Who this is for\n\n`model-relay` is built for Claude Code users who combine a Claude subscription with backup or specialized endpoints (z.ai, DeepSeek, Kimi, MiniMax, OpenRouter, or a gateway on your own machine). Each provider has to accept Anthropic's `\u002Fv1\u002Fmessages` format; a service that only speaks OpenAI Chat Completions needs a local translator (see [Limitations](#limitations)). If you need a multi-tenant cloud proxy for an entire organization, use LiteLLM instead—`model-relay` is a single-user localhost daemon.\n\n## Install\n\nRequires Node.js 20+ and Claude Code 2.1.287 or later (tested on Node.js 22.23.2 and Claude Code 2.1.306).\n\n> **⚠️ Upgrading from 0.6 or earlier? Updating the plugin alone is not enough.** Version 0.7+ reads only `\"format\": 2` in `~\u002F.claude\u002Fmodel-relay.json`. Until you migrate your config and restart the router, an old 0.6 router still running in the background keeps running 0.6 code (without 0.7's conversation\u002Ftool loss fix or localhost security check), and once that process stops, the plugin will not start a new router until you migrate. Run `\u002Fprovider setup upgrade my model-relay config` and Claude does it with you, or follow [UPGRADING.md](UPGRADING.md) by hand (`node tools\u002Fsetup.mjs migrate`, then `migrate --go`, then `\u002Fprovider restart`).\n\n```sh\nclaude plugin marketplace add cablate\u002Fmodel-relay\nclaude plugin install model-relay@model-relay\n```\n\n1. **Set your provider's API key before starting Claude Code.** The relay reads it from the environment of Claude Code, so a variable set in a shell that is already running Claude Code is not seen. Make it permanent, for example `export BACKUP_API_KEY=\"your-api-key\"` in `~\u002F.zshrc` (macOS\u002FLinux) or `setx BACKUP_API_KEY \"your-api-key\"` (Windows), then open a new terminal.\n2. **Open Claude Code and let it set things up with you.** Run `\u002Freload-plugins` first if Claude Code was already open.\n\n   ```text\n   \u002Fprovider setup\n   ```\n\n   Claude runs the setup tool for you, never asks you to paste a key into the chat, and asks before it changes `~\u002F.claude\u002Fsettings.json`. Add what you want, such as `\u002Fprovider setup use deepseek for haiku`.\n3. **Check that it works.**\n\n   ```text\n   \u002Fprovider doctor --ping\n   ```\n\n   You should see `Config OK`, your provider under `Providers`, and a `✓` for it under `Upstream ping`:\n\n   ```text\n   Config OK (~\u002F.claude\u002Fmodel-relay.json)\n     Port: 8950\n     Providers: backup -> your-model-id\n     Failover: backup (back to Claude after 10 min)    Retry: on\n     Aliases:  none (run \"node tools\u002Fsetup.mjs alias haiku \u003Cprovider>\" to route haiku)\n     Claude settings: ✓ settings.json ANTHROPIC_BASE_URL=http:\u002F\u002F127.0.0.1:8950\n     Router health: ✓ up on 127.0.0.1:8950 (pid 38780, \u003Cversion>, pinned sessions: 0, 429 failover: idle, proxy off (0 routed \u002F 0 tunnelled))\n     Upstream ping:\n       ✓ backup     (your-model-id) -> answers, first byte after 640 ms\n   ```\n\n   Anything `doctor` finds is listed with a `⚠` and what fixes it; `\u002Fprovider setup` can run those fixes with you. `doctor` only reports and changes nothing; add `--fix` (`\u002Fprovider doctor --fix`) to let it apply the repairs it can make itself, such as pointing `ANTHROPIC_BASE_URL` in `~\u002F.claude\u002Fsettings.json` at the relay. `doctor --json` prints the same findings for an assistant or a script, each with the `setup.mjs` arguments that fix it (`fix`) and whether that command changes something you should agree to first (`needsConsent`).\n\n**Nothing goes to a provider unless you say so.** A new config keeps `haiku` and every other request on Claude; the first provider you `add` becomes the one that takes over on a 429 (`failover.chain`). To send Claude Code's `haiku` calls (subagents, `WebFetch` summaries, internal helpers) to a provider, run `node tools\u002Fsetup.mjs alias haiku \u003Cname>`.\n\n**Setting it up by hand:** run `\u002Fprovider init` (creates `~\u002F.claude\u002Fmodel-relay.json` with no provider yet, so every request still goes to Claude, and points `ANTHROPIC_BASE_URL` in `~\u002F.claude\u002Fsettings.json` at the relay). Add a provider with `node tools\u002Fsetup.mjs add \u003Cname> --preset \u003Cpreset> --model \u003Cmodel id> --token env:\u003CVAR>` (`presets` lists the built-in ones; `--url` takes any other Anthropic-compatible address), restart Claude Code, then run `\u002Fprovider doctor --ping`.\n\n**Where `node tools\u002Fsetup.mjs` runs:** the `node tools\u002Fsetup.mjs ...` commands later in this README are the same tool. They run from the plugin's install directory, which is `~\u002F.claude\u002Fplugins\u002Fcache\u002Fmodel-relay\u002Fmodel-relay\u002F\u003Cversion>\u002F` (`$CLAUDE_CONFIG_DIR\u002Fplugins\u002Fcache\u002Fmodel-relay\u002Fmodel-relay\u002F\u003Cversion>\u002F` when you set `CLAUDE_CONFIG_DIR`; `ls` that folder for your version). Elsewhere, use the full path to `tools\u002Fsetup.mjs` inside it. The command acts on the config directory in `$CLAUDE_CONFIG_DIR`, or `~\u002F.claude` if unset. `\u002Fprovider setup` runs them for you from anywhere.\n\n**A key exported later is not seen:** `env:VAR` tokens are read from the router process's own environment, which it inherits from whichever Claude Code session (or setup command) started it. A variable exported after the router started is not seen until the router restarts: stop the router process (`doctor` prints its pid) and start a new Claude Code session. `doctor` reads your shell, so it can report the key as set while the router cannot see it.\n\n**Updating:** run `claude plugin update model-relay@model-relay`. The plugin then restarts the relay by itself once no request is in flight through it; a relay from 0.6 or earlier cannot report that, so a notice asks you to run `\u002Fprovider restart` when no Claude Code window has a reply in progress.\n\n## What's inside\n\n### Interactive `AbovePrompt` control panel and provider test\n\nType `\u002Fprovider` to open a panel right above your prompt input box. It does one thing: pick which model this window uses, one button per row. Once a switch takes, the panel closes and a notice names the model now in use. Click a button, or press `ctrl+x tab` and then the key shown on it (`1`–`9` to switch, `p` to test every provider, and `r` to try Claude again while a 429 cooldown runs). Switching a pinned window back to Claude takes a second press to confirm. The switches every window shares stay folded under Settings [s]: press it to show 429 failover, retry and Remote Control (`f` \u002F `t` \u002F `x`) and the model aliases. They turn on or off without editing JSON: failover and retry apply from the next request, while Remote Control asks first because it rewrites `settings.json`: turning it on reaches new sessions only, and turning it off disconnects sessions already running in proxy mode until they are restarted.\n\nEach provider row shows two things apart: the last real request (`last 200 · 45.0s`) and the last test (`test ok 640ms`). The test sends one 1-token request and stops at the first byte (15 seconds at most), so a long reply never reads as a slow provider, and a failure says what is wrong in words: the key was refused, the model name is wrong, the quota is used up, the local gateway is not running, or there was no answer in time. The router keeps the provider's key out of anything it shows. When the router skips a provider (its `env:` variable is unset for the router), the panel says so.\n\n### Automatic 429 failover and per-window session pinning\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"docs\u002Fassets\u002Frelay-flow.svg\" width=\"900\" alt=\"Animation: a Claude Code session hits a 429 usage limit; model-relay seamlessly fails over to a backup provider and pins that window while new windows return to Claude after cooldown\">\n\u003C\u002Fp>\n\nWhen Anthropic returns a 429 usage-limit response, the router immediately forwards the request to the first provider in `failover.chain` and pins that session ID for 7 days. After `failover.cooldownMs` (default 10 minutes), new sessions probe Claude again while existing pinned sessions stay on their backup provider. `failover.enabled` switches the whole thing off.\n\n### Transparent provider retries\n\nA provider answering 429 or 5xx is retried on the SAME provider with exponential backoff (`retry.attempts`\u002F`delayMs`\u002F`backoff`), capped by `retry.maxDelayMs`; a longer `Retry-After` keeps polling at that cap. Set `retry.attempts: 0` to retry forever (keep hammering until it succeeds — the client connection is watched, so retries stop once the caller disconnects). Claude Code never sees the intermediate failures. `retry.enabled` turns retrying off everywhere in one go. Turn on `retry.failover` and a request that still fails once its retries run out is handed to the next provider in `failover.chain` (or to that provider's own `fallback` list), never back to one it already tried.\n\n### Built-in provider presets and local gateway auto-launch\n\nBuilt-in presets (`zai`, `deepseek`, `kimi`, `minimax`, `openrouter`) fill in a service's address and the compatibility switches it needs. You always choose the `model`, because model IDs go out of date, and give the `token` (or `\"env:VAR_NAME\"`). `node tools\u002Fsetup.mjs presets` lists them. For local gateways on `127.0.0.1`, set `launch: { command, args }` and the router will automatically spawn the gateway process whenever its port is closed.\n\n### Model alias routing (pointing `haiku` \u002F `sonnet` \u002F `opus` at a provider)\n\n`model-relay` separates three routing layers so they never get mixed up:\n\n1. **Per-request model alias (`matchModels` + `ANTHROPIC_DEFAULT_*_MODEL`):** Routes only requests whose `model` matches (such as `haiku` subagents or `WebFetch` summaries) without pinning the main conversation.\n2. **Per-conversation pin (`\u002Fprovider \u003Cname>`):** Pins only the current conversation ID (`x-claude-code-session-id`) for 7 days; other windows stay on Claude.\n3. **429 failover chain (`failover.chain`):** Takes over only when Anthropic returns a 429 usage-limit error.\n\nPointing a Claude tier like `haiku` at a provider (for example `backup`, with model `your-model-id[1m]`) coordinates two places at once:\n\n- `~\u002F.claude\u002Fsettings.json` → `env.ANTHROPIC_DEFAULT_HAIKU_MODEL = \"your-model-id[1m]\"` so Claude Code itself resolves the `haiku` alias to that model (including the `[1m]` context window) and sends `\"model\": \"your-model-id\"`.\n- `~\u002F.claude\u002Fmodel-relay.json` → `providers.backup.matchModels` gains `\"haiku\"`, so the router also catches internal Claude Code calls that hardcode `\"claude-haiku-*\"`, rewriting the wire `model` field to `\"your-model-id\"`. A request for a provider's own model always reaches that provider. `node tools\u002Fsetup.mjs alias haiku claude` undoes both.\n\n**How a request finds its provider:** a request whose `model` is some provider's own `model` goes to that provider first, whatever the order in the config. Only then are the `matchModels` patterns tried, in config order, and the first match wins. A request caught by a pattern keeps the model name it came with, except a Claude name (`claude-*`, `haiku`, `sonnet`, `opus`), which is replaced by the provider's `model`. So a pattern should match the real model ID the upstream knows, not the provider's name in this file: `^backup` would catch requests named after the provider and pass on a name the upstream has never heard of.\n\nUse `setup.mjs alias` (or `\u002Fprovider alias`) to inspect or set both files in one step:\n\n```bash\nnode tools\u002Fsetup.mjs alias                       # inspect haiku \u002F sonnet \u002F opus \u002F subagent default\nnode tools\u002Fsetup.mjs alias haiku backup          # point haiku at backup in both settings.json and model-relay.json\nnode tools\u002Fsetup.mjs alias haiku claude          # reset haiku back to Anthropic Claude\nnode tools\u002Fsetup.mjs alias subagent sonnet       # set CLAUDE_CODE_SUBAGENT_MODEL=sonnet in settings.json\n```\n\nFor per-call subagent routing, `model-relay` also dynamically registers a full-capability `Agent` type `model-relay:\u003Cname>` for each provider the router serves (such as `model-relay:backup`) via `$.agent.register` with all parent tools and `model` set to that provider's model ID, and locks that model in `on('agent.spawn')`—so you can spawn `subagent_type: \"model-relay:backup\"` on the fly without creating `.claude\u002Fagents\u002F*.md` files or restarting the session.\n\nEach provider can shape its agent with an `agent` object in `~\u002F.claude\u002Fmodel-relay.json`. The fields are the ones an agent file's frontmatter takes, used as written; `name` and `model` always stay the provider's. `description` is the line Claude reads in its list of agent types when it decides which one to spawn, so say when to pick this agent:\n\n```json\n\"providers\": {\n  \"backup\": {\n    \"model\": \"your-model-id\",\n    \"agent\": {\n      \"description\": \"Use for routine, well-scoped tasks: code search, summaries, small edits.\",\n      \"effort\": \"medium\",\n      \"maxTurns\": 40\n    }\n  }\n}\n```\n\nA change to `agent` applies from your next message, without restarting Claude Code. Tested on Claude Code 2.1.308: `permissionMode` cannot loosen or tighten a parent running in `bypassPermissions`; `initialPrompt` is not applied to subagents started by the Agent tool (it is for `claude --agent`); `memory` had no effect, the same as in an ordinary `.claude\u002Fagents\u002F*.md` file.\n\nClaude Code can still run such a subagent on another model without saying so (a model missing from `availableModels` is swapped, with a line only in its debug log). The plugin checks twice: when the subagent starts, against the model Claude Code reports it started on, and when it finishes, against the models the router actually received. Either mismatch shows a notice that points to `\u002Fprovider doctor`.\n\n### Edit the config from the command line\n\n`add`, `set`, `providers`, `remove` and `migrate` change the file without hand-editing JSON. Every edit keeps a `.model-relay-backup` copy next to the file, tokens are never printed, and the running router picks the change up on its next request. On an older config (see `format` under Configuration), `add`, `set`, `providers`, `remove`, `alias`, `init`, `restart`, `rc` and `proxy enable` refuse and tell you to run `migrate` first; `migrate` is the only command that understands it. `doctor` also warns about an older config, a failover name that matches no provider, two providers that name the same model (the first one in the file gets it), an `availableModels` list in `~\u002F.claude\u002Fsettings.json` that leaves a provider's model out, and a running router older than the plugin.\n\n```bash\nnode tools\u002Fsetup.mjs providers\nnode tools\u002Fsetup.mjs add local --url http:\u002F\u002F127.0.0.1:4000 --model your-local-model-id --token env:LOCAL_GATEWAY_TOKEN\nnode tools\u002Fsetup.mjs set failover.chain backup,local\nnode tools\u002Fsetup.mjs set providers.local.fallback none\nnode tools\u002Fsetup.mjs migrate --go\n```\n\n`remove \u003Cname>` takes the provider out of `failover.chain` and out of other providers' `fallback` lists, but it does not touch the `ANTHROPIC_DEFAULT_*_MODEL` aliases in `~\u002F.claude\u002Fsettings.json`. If `haiku`, `sonnet` or `opus` pointed at the removed provider's model, Claude Code keeps sending that model name and, with no provider left to claim it, the request goes to Claude. Reset each of those aliases yourself with `node tools\u002Fsetup.mjs alias haiku claude` (or `alias haiku \u003Cname>` to give it to another provider; likewise for `sonnet` and `opus`). `doctor` warns about an alias that no provider matches.\n\n### Remote Control without giving up the relay\n\nRemote Control only starts when Claude Code talks to `api.anthropic.com`, which a base URL pointing at the relay breaks. HTTPS proxy mode keeps that address and makes the relay the proxy instead: Claude Code connects through `HTTPS_PROXY`, the relay answers for `api.anthropic.com` with a certificate that Claude Code trusts through `NODE_EXTRA_CA_CERTS`, and model calls are routed exactly as before (providers, sticky failover, retries). Everything else, token refresh and Remote Control traffic included, passes to Anthropic unchanged.\n\n```bash\nnode tools\u002Fsetup.mjs proxy enable --go\nnode tools\u002Fsetup.mjs rc\n```\n\n- **Contained trust:** The CA the relay creates can only vouch for `api.anthropic.com` (a critical name constraint), its private key is discarded right after signing, and only Claude Code processes that carry `NODE_EXTRA_CA_CERTS` load it. The system certificate store is never touched.\n- **Safe fallback:** Without a usable certificate the relay tunnels `api.anthropic.com` untouched, so Claude Code keeps working, just unrouted. A stopped router is different: the plugin's automatic direct fallback only works in `ANTHROPIC_BASE_URL` mode, so in proxy mode Claude Code cannot connect until the router is restarted (`node tools\u002Fsetup.mjs rc`).\n- **Reversible:** `proxy disable --go` restores `ANTHROPIC_BASE_URL`, and the previous `settings.json` is kept as `settings.json.model-relay-backup`. Sessions already running in proxy mode lose their connection until you restart them.\n\n`HTTPS_PROXY` lives in `settings.json` `env`, so it also applies to commands Claude Code runs (`git`, `curl`, `gh`): they go through the relay as blind tunnels, and fail while the router is down.\n\n## Configuration\n\nConfiguration lives in `~\u002F.claude\u002Fmodel-relay.json`. The directory defaults to `$CLAUDE_CONFIG_DIR` when set, falling back to `$USERPROFILE\u002F.claude` or `$HOME\u002F.claude`. [`model-relay.example.json`](model-relay.example.json) shows a provider from a preset and a local gateway.\n\n| Field | Default | Meaning |\n|---|---|---|\n| `format` | (none) | `2` for a config in the current format (`init`, `add` and `migrate --go` set it), the only format the router reads. A file without it, or one named `custom-agent.json`, is an older config: until `migrate --go` rewrites it, the plugin starts and replaces no router for it, so a 0.6 router still running keeps using it, and with none answering the session talks to Claude directly. The plugin says so in a notice, the panel and, with `statusBar` on, the status line; see [UPGRADING.md](UPGRADING.md) |\n| `port` | `8950` | Localhost port for the relay HTTP server (`1025`–`65535`). Change it with `node tools\u002Fsetup.mjs set port \u003Cn>`, which also updates `settings.json`; the router already running keeps the old port until you stop it (the command prints its pid), and it refuses a port that already answers |\n| `statusBar` | `false` | Also show the relay state in Claude Code's plugin status line, the yellow line above the input. Off by default: each window's state is written to a status file for your own statusline instead (see below). Notices about upgrades and outages still appear as toasts |\n| `claudeUrl` | `\"https:\u002F\u002Fapi.anthropic.com\"` | Upstream Anthropic API base URL |\n| `idleTimeoutMs` | `300000` | How long an upstream may send nothing before the request counts as failed and follows the retry and failover rules (`1000`–`3600000`); a streaming answer that keeps sending data is never cut |\n| `proxy.enabled` | `false` | Accept HTTPS-proxy connections for Remote Control. Off by default. When on, a `CONNECT` to any host other than `api.anthropic.com` is a blind tunnel that any local process can use; `node tools\u002Fsetup.mjs proxy enable --go` turns it on and `proxy disable --go` off |\n| `failover.enabled` | `true` | Automatically switch to the failover chain on Claude 429 usage limits |\n| `failover.chain` | `[]` | Ordered providers that take over when Claude is out of quota or a provider keeps failing; unknown names are skipped. Empty means a 429 from Claude is passed back as it is (`add` puts the first provider of a new config here) |\n| `failover.cooldownMs` | `600000` | Milliseconds before probing Claude again for new sessions (10 minutes). Kept between `60000` (1 minute) and `7200000` (2 hours): a value outside that range is moved to the nearest limit |\n| `retry.enabled` | `true` | Master switch for retrying a failing provider; `false` keeps the other retry settings for later |\n| `retry.attempts` | `3` | Times the SAME provider is retried with backoff when it answers 429\u002F5xx; `0` means retry forever. Any other value is capped at `30` |\n| `retry.delayMs` | `2000` | First retry delay; multiplied by `retry.backoff` each attempt. Capped at `60000` (60 seconds), or `300000` (300 seconds) when set in a provider's own `retry` |\n| `retry.backoff` | `2` | Multiplier between retries (2s → 4s → …), capped by `retry.maxDelayMs`. Kept between `1` and `4` |\n| `retry.maxDelayMs` | `10000` | Longest single wait; a longer `Retry-After` keeps polling at this interval. Capped at `300000` (300 seconds) |\n| `retry.failover` | `false` | Opt-in: after retries are exhausted, forward the original request to the next provider in the chain instead of returning the error |\n| `providers.\u003Cname>.retry` | `true` | Per-provider switch: `false` never retries this provider (failure returns at once); `true` follows the global `retry`; or a partial object like `{ \"attempts\": 5 }` overrides just those fields, within the limits listed above |\n| `providers.\u003Cname>.fallback` | the chain | Who takes over from this provider: `false` for nobody, or a list like `[\"backup\"]` |\n| `providers.\u003Cname>.preset` | `\u003Cname>` | Optional preset (`zai`, `deepseek`, `kimi`, `minimax`, `openrouter`): the address and compatibility switches, never a model |\n| `providers.\u003Cname>.baseUrl` | (from preset) | Anthropic-compatible upstream URL for this provider |\n| `providers.\u003Cname>.token` | (required) | Literal token or `\"env:ENV_VAR_NAME\"`. The variable is read from the router process's environment (inherited from the Claude Code session or setup command that started it), so one exported later is not seen until the router restarts |\n| `providers.\u003Cname>.model` | (required) | Target model ID when rewriting requests (suffixes like `[1m]` are stripped on wire) |\n| `providers.\u003Cname>.matchModels` | (from preset, else the model's first word) | Regex patterns matched against the incoming request `model`, in config order; the first match wins. A request for a provider's own `model` goes to that provider before any pattern is tried. A pattern match sends the model name on unchanged (a Claude name is replaced by the provider's `model`), so match the real model ID the upstream knows (see Model alias routing) |\n| `providers.\u003Cname>.stripCacheControl` | `false` | Recursively remove `cache_control` fields before forwarding |\n| `providers.\u003Cname>.authHeader` | (from preset, else `\"bearer\"`) | How to send credentials: `\"bearer\"`, `\"x-api-key\"`, or `\"both\"`. Of the built-in presets only `zai` uses `\"both\"`; the others use `\"bearer\"` |\n| `providers.\u003Cname>.launch` | `null` | `{ \"command\": \"...\", \"args\": [...] }` to auto-spawn if localhost port is closed |\n| `providers.\u003Cname>.agent` | `null` | Fields for this provider's `model-relay:\u003Cname>` subagent, as in an agent file: `description`, `prompt`, `tools`, `disallowedTools`, `skills`, `effort`, `permissionMode`, `maxTurns`, `autoCompactWindow`, `initialPrompt`, `memory`, `isolation`, `background`, `omitClaudeMd`, `mcpServers`, `hooks`. A field of the wrong shape is ignored; if Claude Code refuses the whole set, the subagent keeps the defaults and a notice says so |\n\n### Show the relay in your statusline\n\nEach window writes its relay state to `~\u002F.claude\u002Fmodel-relay-status\u002F\u003Csession id>` (the same config directory as above) whenever it changes, as one line `kind\u003CTAB>label`. The session id is the `session_id` Claude Code passes to a `statusLine` command, so a statusline reads its own window's file directly. The router deletes files untouched for a week.\n\n| `kind` | `label` | Meaning |\n|---|---|---|\n| `claude` | `claude` | This window goes to Claude through the relay |\n| `pinned` | the provider's model | Pinned with `\u002Fprovider`, or by a failover |\n| `failover` | the provider's model | Claude's limit is reached; the next request goes there |\n| `limit` | `claude` | Claude's limit is reached and the failover chain has nobody to hand over to |\n| `offline` | `offline` | No relay of this config directory answers (it is restarted when it can be; an older config is not, see `format`) |\n| `direct` | `claude` | No relay answers, so this window talks to Claude directly |\n\n[coralline](https:\u002F\u002Fgithub.com\u002FNanako0129\u002Fcoralline)'s `relay` segment reads this file.\n\n### Experimental: show the model's picture in a pane\n\nThis feature is experimental: it may change or go away, and it is left out of `\u002Fprovider help` and the tables above. Add `avatars` and the plugin draws the picture of the model this window is talking to in a side pane, swapping it when you switch with `\u002Fprovider \u003Cname>`, when a Claude limit hands the window to a provider, and when it goes back to Claude. It is drawn with colored block characters (eighths and quarters of a cell), picked per cell to follow the picture's shapes, so it also works in terminals without an image protocol, such as Windows Terminal. It needs `ffmpeg` on `PATH`.\n\n```json\n\"avatars\": {\n  \"claude\": { \"image\": \"model-relay-avatars\u002Fclaude.webp\", \"head\": [471, 200, 340] },\n  \"backup\": \"model-relay-avatars\u002Fbackup.webp\"\n}\n```\n\n- A key is `claude` or a provider's name. A value is an image path, relative to the config directory unless it starts from a root, or `{ \"image\", \"head\" }`. `head` is the square that holds the head: `[centerX, centerY, size]` in the image's own pixels.\n- With `head` set, the pane shows the head, cropped to the pane's own shape around that square (narrowing to no less than three quarters of its side) so it fills the pane; without it the whole picture is fitted into the pane. Switching back to a model this window showed lately draws its picture at once. A persona with no entry shows its name and the key to add.\n- The pane is off by default. `\u002Fprovider avatar on` turns it on for every window (the `avatarPane` key): it then opens at the start of a session when the terminal is wide enough, and `\u002Fprovider avatar off` closes it everywhere. `\u002Fprovider avatar` alone opens or closes it in this window only, at any width, and changes no setting. It asks for a dock 80 columns wide, which Claude Code keeps to 45% of the terminal; the wider it is, the finer the picture. Close and reopen it after you change an image file.\n- Each conversion writes its pixels to `model-relay-avatars\u002F.cache\u002F\u003Csession id>.rgba` in the config directory and empties the file when it is done; the folder is safe to delete.\n- The repository ships no pictures; use only images you may use.\n\n## Commands\n\n| Command | Description |\n|---|---|\n| `\u002Fprovider` | Open the panel above the prompt to pick this window's model |\n| `\u002Fprovider \u003Cname>` · `\u002Fprovider claude` | Switch this window without the panel, answered in one line (e.g. `\u002Fprovider backup`); back to Claude, the next turn rebuilds the prompt cache |\n| `\u002Fprovider status` | The status as text: where this window goes, the switches every window shares, and anything to fix |\n| `\u002Fprovider help` | Every command, grouped by what it reaches: this window, every window, setup and checks |\n| `\u002Fprovider setup [goal]` | Let Claude walk you through setup with the setup tool; keys stay out of the chat |\n| `\u002Fprovider init` · `\u002Fprovider doctor [--ping] [--fix]` | First-run setup and health check; `doctor` only reports unless you add `--fix`, which applies the repairs it can make itself. Work from any folder, with or without a config file |\n| `\u002Fprovider restart` | Restart the relay so it runs the installed plugin's code. After `claude plugin update` the plugin does this by itself once the relay answers no request; a relay left by an older plugin cannot report that, so a notice asks you to run it when no window has a reply in progress |\n| `\u002Fprovider ping` | Test every provider: does it answer, how soon does the answer start, and what is wrong if it does not |\n| `\u002Fprovider probe` | Reset the 429 cooldown timer so the next new session tries Claude immediately |\n| `\u002Fprovider alias [haiku\\|sonnet\\|opus] [\u003Cname>\\|claude]` | Inspect or point a Claude model alias at a provider (updates both `settings.json` and `matchModels`) |\n| `\u002Fprovider failover [on\\|off\\|\u003Ca,b>]` | Turn 429 failover on\u002Foff or update `failover.chain` |\n| `\u002Fprovider retry [on\\|off]` | Turn automatic provider retries on or off |\n| `\u002Fprovider rc [on\\|off]` | Turn Remote Control HTTPS proxy mode on or off. On reaches new sessions only; off disconnects sessions already running in proxy mode until you restart them |\n\n## Cost, privacy, and permissions\n\n- **Network and data flow:** The router listens only on `127.0.0.1:\u003Cport>`. Requests go strictly to `claudeUrl` (`https:\u002F\u002Fapi.anthropic.com`) or the `baseUrl` endpoints you configure. `\u002Fprovider ping` sends a `max_tokens: 1` request to each configured provider when you trigger it. No telemetry or analytics exist.\n- **Local files:** Reads `~\u002F.claude\u002Fmodel-relay.json` and writes session sticky state to `~\u002F.claude\u002Fmodel-relay-state.json` plus local logs (`model-relay.log` and `model-relay-requests.log`, each emptied once it passes 5 MB). The old file names (`custom-agent.json`, `custom-agent-state.json`, `custom-agent-router.log`, `custom-agent-requests.log`) are no longer read; `migrate --go` renames them.\n- **Local certificate (proxy mode only):** `~\u002F.claude\u002Fmodel-relay-pki\u002F` holds a public CA file and one leaf certificate with its key (restricted to your user). The CA is limited to `api.anthropic.com` and trusted only through `NODE_EXTRA_CA_CERTS`; delete the folder or run `proxy disable --go` to stop using it.\n- **Plugin permissions:** Inspect all hooks, state keys, and env access at any time by running `claude plugin validate \u002Fpath\u002Fto\u002Fmodel-relay`.\n\n## Supported environments\n\n| Environment | Status |\n|---|---|\n| Windows 11 + Node.js 22 + Git Bash \u002F PowerShell | Tested |\n| macOS \u002F Linux + Node.js 20+ (`sh` + `curl`) | Per official Node.js & POSIX specs. CI runs only `node tools\u002Fcheck.mjs` on Node.js 22 (Ubuntu, Windows, macOS); Node.js 20, the plugin hooks and the Claude Code integration are not tested there |\n\n## Limitations\n\n- **Requires an Anthropic-compatible `\u002Fv1\u002Fmessages` endpoint:** Providers that only speak OpenAI Chat Completions require a local translation proxy (and can be supervised via `providers.\u003Cname>.launch`).\n- **Switching back to Claude mid-session rebuilds cache:** Because prompt caches do not transfer across providers, `\u002Fprovider claude` on a long conversation will resend the full conversation history to Anthropic.\n- **No upstream proxy:** The relay connects to Anthropic and your providers directly, so a network that only lets traffic out through a (company) proxy is not supported yet.\n\n## Troubleshooting\n\nStart with `\u002Fprovider doctor --ping`: it lists each problem with a `⚠` and the command that fixes it, and `\u002Fprovider setup` can run those fixes with you.\n\n| Symptom | What to check |\n|---|---|\n| `\u002Fprovider` says config is missing or invalid | Run `\u002Fprovider doctor` (or `node tools\u002Fsetup.mjs doctor` from the install directory) to verify JSON syntax and check if any `env:VAR` variables are unset in your shell |\n| The status line (with `statusBar` on) shows `Relay offline: connected directly to Claude`, or the status file says `direct` | In `ANTHROPIC_BASE_URL` mode the plugin bypassed a stopped router so your chat keeps working. In proxy mode there is no such bypass: Claude Code cannot connect until the router is restarted. Run `\u002Fprovider doctor` or check `~\u002F.claude\u002Fmodel-relay.log` (a router that finds its port taken by another process or another config directory logs that there and exits) |\n| Remote Control says it is only available via `api.anthropic.com` | Claude Code still uses the relay as its API address; run `node tools\u002Fsetup.mjs proxy enable --go` and start a new session |\n| `proxy enable` refuses because `NODE_EXTRA_CA_CERTS` is already set | Something you use (often a company network that inspects TLS) already relies on that certificate, and Claude Code accepts only one file there, so proxy mode would make it stop trusting yours. Remote Control through the relay cannot be combined with it yet; nothing was changed |\n| Claude Code cannot connect after enabling proxy mode | The router is probably down: run `node tools\u002Fsetup.mjs proxy status`, start it with `node tools\u002Fsetup.mjs rc`, or go back with `node tools\u002Fsetup.mjs proxy disable --go` |\n| Tools are missing on a third-party provider | The router now renames `ToolSearch` before it reaches the provider and keeps tools it has loaded callable. Run `node tools\u002Fsetup.mjs restart` so a running router picks up the change |\n| A `model-relay:\u003Cname>` subagent runs on Claude instead of the provider | The plugin shows a notice when this happens. Usually an `availableModels` list does not include the provider's model, so Claude Code runs the subagent on `CLAUDE_CODE_SUBAGENT_MODEL` or the parent's model; add the model ID exactly as configured. `node tools\u002Fsetup.mjs doctor` checks the list in `~\u002F.claude\u002Fsettings.json` only |\n| A notice says the config is from an older version, and `doctor` says it is outdated | The file is named `custom-agent.json` or has no `\"format\": 2`, so this version's router does not read it. `node tools\u002Fsetup.mjs migrate` shows what it would write, and `migrate --go` writes it, with the router then routing as the older version did (it refuses if the result would route differently); then run `\u002Fprovider restart` when no Claude Code window has a reply in progress. See [UPGRADING.md](UPGRADING.md) |\n\n## Uninstall\n\n1. Run `\u002Fprovider doctor` and note the router's `pid` (on the `Router health` line; `Router status: offline` means nothing is running, so skip stopping it). Do this before uninstalling, because the tool that prints it lives in the plugin's folder.\n2. Run `claude plugin uninstall model-relay@model-relay` and remove `ANTHROPIC_BASE_URL` from `~\u002F.claude\u002Fsettings.json` (in proxy mode remove `HTTPS_PROXY`, `NODE_EXTRA_CA_CERTS` and `NO_PROXY` instead; running `node tools\u002Fsetup.mjs proxy disable --go` first does that and puts `ANTHROPIC_BASE_URL` back, which you then remove).\n3. Close the Claude Code windows that still have the plugin loaded (an open one starts the router again), then stop the router with `kill \u003Cpid>` (macOS\u002FLinux) or `taskkill \u002FPID \u003Cpid> \u002FF` (Windows). It keeps running after the plugin is removed. A gateway the router started from a provider's `launch` also keeps running on its own, so stop it after the router, which restarts a closed one within about 30 seconds. Find its process by the port in that provider's `baseUrl` (`lsof -i :\u003Cport>` on macOS\u002FLinux, `netstat -ano | findstr :\u003Cport>` on Windows) and stop it the same way.\n4. Delete optional state files (`~\u002F.claude\u002Fmodel-relay.json`, `~\u002F.claude\u002Fmodel-relay-state.json`, `~\u002F.claude\u002Fmodel-relay-status\u002F`, `~\u002F.claude\u002Fmodel-relay-pki\u002F`, `~\u002F.claude\u002Fmodel-relay.log`, `~\u002F.claude\u002Fmodel-relay-requests.log`, and any `*.model-relay-backup` copies).\n\n## Run from source\n\n```bash\nnode tools\u002Fcheck.mjs\nclaude plugin validate .\nclaude plugin test .\nnode tools\u002Fsmoke.mjs   # one real Claude Code request through this checkout's router; add --provider \u003Cname> for a provider\n```\n\n`tools\u002Fsmoke.mjs` needs a logged-in `claude`, so CI does not run it. Run it before a release and after a Claude Code update. `node tools\u002Fcheck.mjs` also runs the TypeScript hook tests, which need Node.js 22.6 or later; only running the plugin gets by on 20+.\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for architecture and release steps.\n\n## More\n\n- [Changelog](CHANGELOG.md)\n- [Contributing](CONTRIBUTING.md)\n- [Security Policy](SECURITY.md)\n- [License (MIT)](LICENSE)\n",2,"2026-10-31 04:26:48","CREATED_QUERY"]