# magpie reference The full manual: every agent magpie wires, every provider option, the gateway, routing, plugins, the CLI, Docker, sync and the files magpie keeps. For the overview, see the [README](../README.md). Renamed a project folder? See [relocating Claude Code sessions](session-relocation.md). One place to pick every agent's model: Codex on DeepSeek, Claude Code on Kimi, Gemini CLI on GLM, from the menu bar. [usemagpie.ai](https://usemagpie.ai) [![Discord](https://img.shields.io/badge/Discord-join%20the%20community-5865F2?logo=discord&logoColor=white)](https://discord.gg/vGSnD3ZKQF) `magpie` is a single screen that lists each AI agent on your machine and the model it is set to. Click a value, pick a model. That is the whole app. It lives in the menu bar: click the icon and a panel drops down; the same screen also opens as a normal window (`magpie`, or *Open magpie* in the tray menu), which opens on the page you last left, as a Mac app comes back as it was, and there is a terminal version (`magpie tui`) and a plain CLI. The terminal version serves the gateway while it is open when no other magpie (the app, `magpie web`, `magpie serve`) does, and says so on its status line; agents connected to magpie lose it when it quits. ``` ◉ magpie ▸ Claude Code claude-fable-5-1[1m] ~/.claude/settings.json Codex gpt-6-astra effort medium Gemini CLI gemini-3.1-pro OpenCode anthropic/claude-sonnet-5 small anthropic/claude-haiku-4-5 MiMo Code anthropic/claude-sonnet-5 Pi openrouter/z-ai/glm-5.2:batch Goose anthropic/claude-sonnet-5 Cursor CLI auto Copilot CLI claude-fable-5 ↑↓ agent · ←→ field · ↵ change · s save profile · p profiles · q quit ``` - **One binary.** About 30 MB with the desktop app (it uses the system webview through [Wails](https://wails.io), nothing bundled; a 15 MB download on a Mac), and under 30 MB for the terminal-only build. macOS, Linux and Windows. - **Edits config files surgically.** Only the one key you change is touched; comments, ordering and indentation in your `settings.json`, `config.toml`, `opencode.jsonc` or `config.yaml` survive intact. Writes are atomic. - **One endpoint for every agent.** magpie runs a local gateway that speaks OpenAI chat completions, OpenAI Responses and the Anthropic Messages API, and forwards to whichever vendor serves the model. Codex, Claude Code, OpenCode and the rest all point at `http://127.0.0.1:3425/v1` and pick from one catalog; the translation between APIs happens in magpie, streaming and tool calls included. - **Your subscriptions, shared.** Sign in to Claude Code, Codex (ChatGPT) or Copilot and that login shows up as a provider: every other agent can use its models through the gateway, with nothing copied and no key to paste. - **Providers with one field.** Pick a preset (Anthropic, OpenAI, Gemini, Amazon Bedrock and its Mantle endpoint, DeepSeek, Kimi, GLM, MiniMax, StepFun, Qwen, Baidu Qianfan, Tencent Cloud Token Plan, Huawei Cloud MaaS, Volcengine Ark, Mistral, Groq, Nous Portal, xAI, OpenRouter, Together, Fireworks, SiliconFlow, NVIDIA NIM, ModelScope, AiHubMix, PipeLLM, 302.AI, Ollama, LM Studio…), paste a key, done. Custom vendors need a name and a base URL. magpie never reads keys from your shell environment. - **Real model lists, nothing compiled in.** With a key in hand magpie asks the vendor which models it serves and offers exactly those; the [models.dev](https://models.dev) catalog fills in names, reasoning efforts and the list for vendors that have none (a model the vendor's own entry doesn't list is named as the other providers serving it name it, so glm-5-turbo reads GLM-5-Turbo under Zhipu as under ZCode; an Azure deployment keeps its own name), and refreshes itself in the background once it goes stale. Choose which models each provider exposes, or expose them all — a model released this morning is in the picker on the next refresh. - **Each agent's own model list.** Under an agent's name on the Agents page, "Showing 5 / 32 models" opens its list: click a model to take it out of that agent's picker (Codex's `/model` included, its ChatGPT models too) or put it back; other agents still use it, and a new model is shown. Its "Only models I pick" switch turns the list around: the models ticked when it is switched on stay, and a model added later, of any provider, stays out of that agent's picker and its config until it is ticked (`magpie visible --only-picked`, back with `--show-new`). A model asked for by name still works either way. - **Profiles.** Snapshot every agent's settings under a name and switch all of them back in one move. The profile in use, the one applying would change nothing, is ticked, the tray panel's Profiles button shows its name, and `magpie profiles` marks it ✓. - **Real logos, no framework.** Plain HTML over the system webview; brand icons from [lobehub/icons](https://github.com/lobehub/lobe-icons). ## Agents | Agent | File | Fields | | ------------ | --------------------------------- | --------------- | | Claude Code | `~/.claude/settings.json` | provider, model, opus/sonnet/haiku/fable, auto mode, sign-in (through magpie) | | Claude Desktop | `Claude/` + `Claude-3p/configLibrary/` in `~/Library/Application Support` (`%LOCALAPPDATA%` on Windows, `~/.config` on Linux). A Desktop installed as an MSIX package on Windows keeps them in its package instead (`%LOCALAPPDATA%\Packages\Claude_\LocalCache\Local`, or `AnthropicPBC.Claude_` for the Store's package, its own data in `LocalCache\Roaming\Claude`). Desktop 2.31226 and later, packaged, reads the real `%LOCALAPPDATA%\Claude-3p` instead, so for a packaged Desktop magpie writes both ([`desktopdir`](../internal/desktopdir/desktopdir.go)) | provider (its third-party gateway mode: Code and Cowork on magpie, no Anthropic sign-in; restart Desktop), opus/sonnet/haiku/fable, subagent model and subagent effort (magpie's settings, applied by the gateway to subagent tool turns; no restart) | | Codex | `~/.codex/config.toml` | provider, model, effort | | Gemini CLI | `~/.gemini/settings.json`, `~/.gemini/.env` | auth, model | | OpenCode | `~/.config/opencode/opencode.json(c)` (`$OPENCODE_CONFIG_DIR`) | model, small | | OpenChamber | `~/.config/openchamber/preferences.json` (`$OPENCHAMBER_DATA_DIR`; magpie's provider in OpenCode's config) | model, small (its own defaults, over OpenCode's) | | MiMo Code | `~/.config/mimocode/mimocode.json(c)` | model, small | | DevEco Code | `~/.config/deveco/deveco.json(c)` (`$DEVECO_CONFIG_DIR`; its key is `magpie-deveco`, as parts of it send OpenCode's User-Agent) | model, small | | Pi | `~/.pi/agent/settings.json` | model | | StepCode | `~/.stepcode/config.toml` (+ `models.json`; `$STEP_CODING_AGENT_DIR`, `$STEPCODE_CONFIG_DIR`) | model | | MothX | `~/.mothx/settings.json` (`$MOTHX_DIR`; `%APPDATA%\mothx` on Windows) | model, thinking (`defaultProvider`/`defaultModel`/`defaultThinkingLevel`; magpie as the provider `magpie` beside the user's own, `openai-chat` with `thinkingFormat: "openai"`) | | Aside | `~/.aside/u/0/settings.json` (+ `models.json`; the first account, the only one magpie wires) | model, effort, fast, standard, deep, visual, image | | OmO (omo-ai) | `~/.omo/agent/settings.json` (+ `models.json`; `$OMO_CODING_AGENT_DIR`, `$SENPI_CODING_AGENT_DIR`) | model | | Prime Agent | `~/.prime/agent/settings.json` (+ `models.json`; `$PRIME_AGENT_CODING_AGENT_DIR`) | model, thinking (Pi's fields; its key `magpie-prime-agent` names it, since its requests carry no User-Agent; the global settings alone, `allowedModels` and `subagentDefaultModel` left as they are) | | Goose | `~/.config/goose/config.yaml` | model | | Cursor CLI | `~/.cursor/cli-config.json` | model | | Zed | `~/.config/zed/settings.json` (`$XDG_CONFIG_HOME` on Linux, `%APPDATA%\Zed` on Windows) | model (a `magpie` OpenAI-compatible provider; its catalog in Zed's picker) | | VS Code (Chat) | `~/Library/Application Support/Code/User/settings.json` + `chatLanguageModels.json` (`~/.config/Code/User` on Linux, `%APPDATA%\Code\User` on Windows) | model (`chat.defaultModel`; a `magpie` Custom Endpoint group, its catalog in Chat's model picker; VS Code 1.122+, no Copilot sign-in or key needed). Each profile's own pair under `User/profiles//` (listed in `globalStorage/storage.json`) gets the same, unless the profile uses the default's | | VS Code Insiders (Chat) | the same files under `Code - Insiders/User` in place of `Code/User` | as VS Code, a row of its own; its models send the token `magpie-vscode-insiders`, since its chat's User-Agent is VS Code's | | VSCodium (Chat) | the same files under `VSCodium/User` in place of `Code/User` | as VS Code, a row of its own; its models send the token `magpie-vscodium`, so Usage counts them as VSCodium; its Chat features must be turned on (`chat.disableAIFeatures=false`, and `defaultChatAgent` plus `trustedExtensionAuthAccess` for GitHub.copilot-chat in its product.json). With the Marketplace's GitHub Copilot Chat 0.48.1, which has no Custom Endpoint provider, the group is written for its OpenAI Compatible provider (`customoai`, the token sent as `x-api-key`), which lists models only while Copilot Chat is signed in to GitHub with a personal Copilot plan | | Copilot (JetBrains) | `byok.json` in `~/.config/github-copilot` (`$XDG_CONFIG_HOME/github-copilot` when set, `%USERPROFILE%\AppData\Local\github-copilot` on Windows), the Copilot language server's own file, which Xcode's and Eclipse's Copilot read too | provider (a `magpie` custom endpoint provider beside the user's: every magpie model at the gateway's `/v1/chat/completions`, sent with `magpie-copilot-jetbrains`; a model hidden in Manage Models stays hidden through a sync; off removes magpie's three keys alone). Found by the `github-copilot-intellij` plugin in a JetBrains IDE or Android Studio. Listed after the IDE restarts, and only while Copilot is signed in to GitHub on a plan with Bring Your Own Key (Free, Pro, Pro+; Business and Enterprise when the organisation allows it); the model is picked in Copilot Chat | | JetBrains Air | `acp.json` in `~/Library/Application Support/JetBrains/Air` (`~/.config/JetBrains/Air` on Linux, `%APPDATA%\JetBrains\Air` on Windows) + `magpie-opencode.json` beside it | model (a `Magpie` ACP agent: OpenCode's `opencode acp` on magpie's provider alone, its models and routing groups in Air's model menu; needs OpenCode installed) | | Copilot CLI | `~/.copilot/settings.json` | model | | Crush | `~/.config/crush/crush.json` | large, small | | DeepSeek Harness (dsh) | `~/.dsh/profiles/*/cordis.patch.yml` (`$DSH_HOME`; a custom provider, Magpie, its key `MAGPIE_GATEWAY_KEY` in `~/.dsh/.env` and dsh's own key store `~/.dsh/.credentials.yaml`, which the desktop app reads; on one of magpie's models its `web-search-deepseek` row also goes to the gateway, which searches with the model or Settings › Web search, unless dsh has a `DEEPSEEK_API_KEY` or a row of your own), or `~/.dsh/config.yaml` before dsh 0.1.5 | model, effort | | Reasonix Studio (2.x, or a native Go `reasonix` 2.x / 1.39.x CLI) | `~/.reasonix/config.toml` + `.env` (`%APPDATA%/reasonix` on Windows; `$REASONIX_HOME`) | model (Executor), planner (Plan), effort (a dedicated `magpie` provider, shared by Studio and the native CLI) | | Command Code | `~/.commandcode/settings.json` (+ `providers.json`) | model | | fx | `~/.fx/settings.json` | model (a keyless `magpie` provider) | | omp (oh-my-pi) | `~/.omp/agent/config.yml` (+ `models.yml`) | model | | Devin | `~/.config/devin/config.json` (`%APPDATA%\devin\config.json` on Windows) | model | | Hermes Agent | `~/.hermes/config.yaml` (`%LOCALAPPDATA%\hermes` on Windows; `$HERMES_HOME`), and each profile's under `profiles/` | model | | Mister Morph | `~/.morph/config.yaml` (`$MISTER_MORPH_CONFIG`) | model, effort (`llm` on the gateway as `openai_response_compatible`, the Responses API; what it had comes back when you switch away) | | Kimi Code | `~/.kimi/config.toml` (`$KIMI_SHARE_DIR`) | model (a `magpie` provider; magpie's models in Kimi's /model) | | Qwen Code | `~/.qwen/settings.json` (`$QWEN_HOME`) | model (magpie's models as `modelProviders.openai` entries on a `MAGPIE_QWEN_API_KEY` env var; settings.model; `security.auth.selectedType` openai while wired) | | Muse Code | `~/.config/muse/settings.json` (`$XDG_CONFIG_HOME`) | model (endpoint_transport to the gateway, auth none; magpie's models in Muse's list) | | Empryo | `~/.empryo/config.json` | defaultModel (a `magpie` provider at the gateway in `providers`) | | Ante | `~/.ante/catalog.json` and `settings.json` (`$ANTE_HOME`) | provider, model (a `magpie` provider on OpenAiCompatible, with no auth of magpie's: the gateway lets this machine in with any token and Ante counts a provider with none as authenticated; magpie's models in Ante's model picker, and Ante started on magpie, which is what its settings' `provider` decides) | | AstrBot | `data/cmd_config.json` under `$ASTRBOT_ROOT`, else `~/.astrbot` (the desktop app's) | model (a `magpie` entry in `provider_sources` and magpie's models in `provider`, the user's own entries kept; the default chat model in `agent_runner.config.model.provider_id`, or `provider_settings.default_provider_id` on AstrBot before 4.28's agent runner; restart AstrBot after connecting; Docker installs aren't reached) | | jcode | `config.toml` in `$JCODE_HOME`, else `~/.jcode` | model (a `[providers.magpie]` profile, `openai-compatible`, with magpie's models in it; `[provider] default_provider`/`default_model` set to it, what was there put back on Disconnect; the user's own keys in the profile, such as `headers` and a model's `reasoning_effort`, kept; restart open jcode sessions after connecting) | | MiniMax Code (mcode) | `~/.minimax/config.yaml` (`$MINIMAX_DATA_DIR`) | model (a `magpie` custom provider; magpie's models in its /model) | | Droid (Factory) | `~/.factory/settings.json` (`$FACTORY_HOME_OVERRIDE`) | model (magpie's models as BYOK `customModels`, in Droid's /model) | | Cline (CLI) | `~/.cline/data/settings/providers.json` (`$CLINE_DIR`) | model, effort (magpie takes its openai-compatible provider) | | Qoder (CLI) | `~/.qoder/settings.json` (`$QODER_CONFIG_DIR`) | model, effort (a `magpie` custom provider; needs a Qoder plan with BYOK) | | Qoder CN (CLI) | `~/.qoder-cn/settings.json` (`$QODERCN_CONFIG_DIR`) | model, effort (as Qoder; its own accounts, a Qoder CN plan with BYOK) | | Grok Build | `~/.grok/config.toml` (`$GROK_HOME`) | model, effort | | Grok App | `agent-home/config.toml` in its data folder (`$GROK_APP_HOME`, else `~/Library/Application Support/com.grokapp.grok-app`, `%APPDATA%\grokapp\grok-app\data`, `$XDG_DATA_HOME/grok-app`, or `~/.grok-app`) | model (a `[model.magpie]` custom provider under Accounts, with magpie's models and their effort levels in `app_models`; the route, `[models] default`, stays until the user presses Use on magpie there (one of the App's own routes picked in magpie is written as the route, and magpie's channel goes); a route an older magpie set to `magpie` is put back once; Disconnect drops the channel and puts the route back only if it is still `magpie`, else the one it was on before Connect if none is left; a model or default effort picked in the App kept; instructions, MCP and skills are the Library's Grok App row on agent-home; RTK has no hook for it or for Grok Build) | | ZCode | `~/.zcode/v2/config.json` | provider (magpie's models in ZCode's picker) | | WorkBuddy | `~/.workbuddy/models.json` (`$WORKBUDDY_CONFIG_DIR`) | provider (magpie's models in WorkBuddy's picker) | | WorkBuddy AI | `~/.workbuddy-ai/models.json` (the international build) | provider (magpie's models in WorkBuddy AI's picker) | | CodeBuddy Code | `~/.codebuddy/models.json` (`$CODEBUDDY_CONFIG_DIR`), `settings.json` | model (magpie's models in its list; the session model) | | Raycast | `~/.config/raycast/ai/providers.yaml` in the user's folder, on Mac and Windows | provider (a `magpie` entry in Raycast AI's Custom Providers, magpie's models in its model picker; Raycast Pro; the user's providers, `tools` and comments kept) | | OpenPencil | `openpencil/settings.json` in the config folder: `~/Library/Application Support` (macOS), `$XDG_CONFIG_HOME` or `~/.config` (Linux), `%APPDATA%` (Windows) | provider (a `magpie` entry in its `builtin_agents`, magpie's models in its chat's model picker; the user's entries and settings kept; refused while OpenPencil runs, as it writes the file back when it quits) | | T3 Code | `~/.t3/userdata/settings.json` (`$T3CODE_HOME/userdata`) | provider (a `magpie` provider instance on Claude Code, magpie's models as its custom models) | | OpenHanako | `~/.hanako/provider-catalog.json` + `agents//config.yaml` (`$HANA_HOME`; its local API while it runs) | model (the primary agent's; magpie's models as a provider) | | AtomCode | `~/.atomcode/config.toml` (`$ATOMCODE_HOME`) | model, effort (a `magpie` provider account, one model table per catalog model as its own sign-in writes) | | Snow CLI | `~/.snow/profiles/magpie.json` (`$SNOW_CONFIG_DIR`), made the active profile and copied to `config.json` | model (the user's other profiles' models too; Snow App takes the profile in with "Sync Snow CLI API config") | | Alma | Alma's local API (`localhost:23001`, while Alma runs; alma-server's data in `$ALMA_DATA_DIR`, `$XDG_DATA_HOME/alma` or `~/.local/share/alma` on Linux) | model (Alma's default; magpie's models as a provider), and Image Generation's model when it is Auto: the one magpie draws with | | Lody | Its own confirmation dialog (`lody://provider/import`) | adds selected Claude, Codex, Pi, DSH, Kimi Code, Grok and Bub Providers on the local machine; no settings edited by magpie | Provider-scoped agents (OpenCode, MiMo Code, DevEco Code, Pi, OmO, StepCode, MothX, Aside, Goose, Crush, omp, Hermes Agent) take `provider/model`. Only agents that are installed or configured are shown. ### Notes on some agents #### StepCode StepCode is configured via [`stepcode.go`](../internal/agent/stepcode.go). Magpie manages global `defaultProvider` and `defaultModel` in `~/.stepcode/config.toml` (or the parent of an absolute `STEP_CODING_AGENT_DIR`) and writes its catalog into `~/.stepcode/models.json` using Pi's provider format. Magpie only manages model selection; StepCode controls thinking levels itself. Project-local `.stepcode/config.toml` overlays take precedence over global settings. Restart StepCode or begin a new session after changing models. Detection is strictly directory-based (`~/.stepcode`), so having Smallstep's unrelated `step` certificate CLI on `PATH` is never mistaken for StepCode. The optional real CLI verification test is run with `MAGPIE_TEST_STEPCODE_BIN=/path/to/step go test ./internal/agent -run '^TestStepCodeRealCLISandbox$' -count=1`. It runs StepCode in an isolated temporary sandbox with telemetry disabled and without live credentials. #### MothX MothX (oschina/mothx, formerly vibecoding) is configured via [`mothx.go`](../internal/agent/mothx.go). Magpie writes its provider `magpie` into `providers` in MothX's global `settings.json` (`$MOTHX_DIR`, else `%APPDATA%\mothx` on Windows, else `~/.mothx`) and sets `defaultProvider`, `defaultModel` and `defaultThinkingLevel`. The provider uses `openai-chat` with `thinkingFormat: "openai"`, so a thinking level goes out as `reasoning_effort` for every model; without it MothX guesses Qwen's or DeepSeek's thinking fields from a model id. What the user adds to magpie's block (headers, a `thinkingFormat` of their own) stays. The key `magpie-mothx` names it, and so does its User-Agent (`Vibecoding Client/`). A project's `.mothx/settings.json` overlays the global one and is left alone. MothX reads its settings at start-up, so restart open `mothx` sessions after a change. Disconnect puts `settings.json` back byte for byte when nothing else changed in it; when MothX or the user changed it meanwhile (its `/model` and `/statusline … global` write the whole file), Disconnect takes out only magpie's provider, its pick and a `providers` object it left empty. #### Reasonix Studio Reasonix Studio is detected from its desktop installation or a native Go `reasonix` 2.x or 1.39.x CLI; historical npm wrappers and a shared config alone do not count. Tested with [Studio 2.24.0](https://github.com/esengine/DeepSeek-Reasonix/releases/tag/studio-v2.24.0). Pick the Executor model in the app, or use `magpie reasonix magpie/deepseek/deepseek-chat` (`magpie reasonix magpie/group/code` for a routing group). Select Plan independently with `magpie reasonix planner magpie/deepseek/deepseek-chat`, or use `magpie reasonix planner off` to disable the separate planner. `magpie reasonix effort high` sets an advertised Executor reasoning level. `magpie reasonix default` restores the previous Executor selection; `magpie reasonix planner default` restores Plan. Magpie's provider and private `.env` key are removed when neither role needs them. Restart Studio for new sessions; project/session overrides still take precedence. Other providers and credentials are preserved. A non-managed provider named `magpie` is a conflict, reported without overwriting it. Studio and the native CLI share these files, so updating only Studio does not isolate their settings. The released-client stream/tool contract test is run with `MAGPIE_TEST_REASONIX_CLI=/path/to/reasonix go test ./internal/agent -run '^TestReasonixStudioCLIIntegration$' -count=1`. It uses isolated settings and a local test upstream, with no vendor credentials. #### VSCodium Chat VSCodium's Chat features are disabled by default. To use the Chat model picker with magpie, set `"chat.disableAIFeatures": false` in VSCodium's settings and add the `defaultChatAgent` and `trustedExtensionAuthAccess` entries required by [VSCodium's Copilot guide](https://github.com/VSCodium/vscodium/blob/master/docs/ext-github-copilot.md) to VSCodium's `product.json`. The guide also explains how to install a compatible GitHub Copilot Chat extension, since the Open VSX registry does not normally provide Microsoft's extension. Restart VSCodium, or run **Developer: Reload Window**, after changing these files. Magpie's models then appear under the `magpie` custom endpoint group. #### Zed-compatible Agent paths Magpie can use a Zed-compatible fork or installation whose executable or configuration directory is different from the upstream defaults. Set these variables before starting Magpie: ```sh export MAGPIE_ZED_BIN=/Applications/ZedG.app/Contents/MacOS/zedg export MAGPIE_ZED_CONFIG_DIR="$HOME/.config/zed" export MAGPIE_ZED_PROCESS_NAMES=zedg,ZedG magpie serve ``` `MAGPIE_ZED_BIN` controls installation detection, `MAGPIE_ZED_CONFIG_DIR` selects the directory containing `settings.json`, and `MAGPIE_ZED_PROCESS_NAMES` supplies comma-separated process names used for restart notices. The existing Zed configuration adapter is reused, so this is intended for forks that keep Zed's `settings.json` and Agent model schema, such as [ZedG](https://github.com/x6nux/zed-globalization). These variables affect the Magpie process in which they are set; put them in the service environment when running Magpie under systemd or Docker. They configure a Zed-compatible Agent on the same machine as Magpie; they do not discover or modify an Agent running on another host. On macOS, if the configured binary does not resolve to a `.app` bundle, Magpie falls back to the standard Zed application locations when authorizing the gateway credential. ## Providers and the gateway Every model an agent can pick is spelled `provider/model` and served by magpie's gateway, so agents never hold vendor keys or vendor URLs. Add a provider, and its models appear in every agent's picker: ```sh magpie presets # the vendors magpie knows, grouped: vendors, relays, local magpie provider add deepseek sk-… # a preset needs only the key magpie provider add ollama # local servers need none magpie provider add "My Relay" url=https://relay.example.com/v1 key=sk-… models=gpt-5.5,claude-sonnet-5 magpie providers # host, key, exposed models, who uses what magpie provider deepseek # one provider in detail magpie provider models deepseek # re-fetch the vendor's list (add ids to choose which to expose) magpie provider models deepseek +deepseek-v4 -deepseek-chat # expose one more, take one out; the rest stay magpie provider models deepseek a b c # the whole list of models to expose, replacing it (all: the default) magpie provider refresh deepseek # re-fetch it, and drop picks it no longer has (the TUI: m) magpie provider refresh-every relay 6h # the gateway fetches its list every 6h (1d, 30m; off; at least 10m) magpie provider auto-include relay 'gpt-*' 're:claude-.*' # a newly listed model these match is exposed (none clears) magpie provider test deepseek # one tiny request per API, with latency magpie provider key deepseek sk-… # replace the key magpie provider rm deepseek magpie models # the catalog agents see magpie claude deepseek/deepseek-chat # use it ``` Custom providers take `url=` (an OpenAI-compatible base), `anthropic=` (an Anthropic-compatible base), or both, plus `responses=` when the vendor has a separate Responses endpoint, `catalog=` to borrow a models.dev list, and `models=` to name the models to expose. Anything a preset does not know can be overridden the same way. A model whose vendor answers that it is deprecated or retired (OpenCode Zen's 410 "Model exo-free has been deprecated.") leaves that provider's models in `magpie models`, `/v1/models` and every agent's list, even while the vendor's own list still names it. It comes back once a request for it as `provider/model` is answered, or after a week. `magpie provider set header.=` sends a header of your own on every request to a key+URL provider (an empty value removes it; signed-in accounts ignore them). It replaces a header of the same name magpie would send, except `anthropic-beta`, which is a list: your betas are added after the ones the agent asked for that request (Claude Code's, its 1M context's `context-1m-2025-08-07` for a `[1m]` model, fast mode's), each once. A beta the provider turns away (`Unexpected value(s) … for the anthropic-beta header`) is dropped from the retry and from then on, yours as well as the agent's; any other of yours is always sent. The beta of the agent's own sign-in (`oauth-2025-04-20`, which Claude Code signed in to claude.ai asks) never goes, as the sign-in never does; one you set yourself does. Requests go to a vendor with magpie's own User-Agent (`magpie/`). A coding plan that serves only the agents it names knows them by theirs: `magpie provider set user-agent=agent` (the editor's "Send the agent's own User-Agent") sends a key+URL provider the User-Agent the agent sent magpie, as it sent it, on every API (chat, Anthropic, Responses, Gemini, count_tokens, embeddings, images, videos). A request the agent sent without one, and magpie's own (its classifier's, the image tool's), still go as magpie. A `header.User-Agent` you set wins over both. `user-agent=magpie` turns it off again; off is the default. A signed-in account, a plugin's and a vendor whose own client magpie goes as (OpenCode, Cline, Kilo, another magpie) keep the User-Agent their sign-in sends, and don't take it. `magpie usage` (and `--csv`) takes `today`, `7d`, `30d` or `all`, each from a local midnight, or a window ending now: `--last 24h` (any Go duration, `90m`) or `--days 1.5`; the CSV then holds that window's calls only (#2075). `magpie usage` also lists **upstream provider keys** to help check upstream bills. Each request records the fingerprint and saved name of the key that actually served it, including image calls and account/key failover. The CSV adds `provider_key_id` and `provider_key_name`. JSON uses `providerKeyId` and `providerKeyName`, distinct from gateway caller keys. System One calls use the same attribution. No raw credential is stored in usage records. Rotating the provider's first key does not move old usage to its replacement; deleted keys keep their historical identity. Older records appear as **key not recorded**, never inferred from today's configured key. These are upstream credentials, not keys clients use to call Magpie. The CSV's `cache_write_tokens` is still every cache write; the two columns, `cache_write_5m_tokens` and `cache_write_1h_tokens`, split it by how long Anthropic keeps it (a record from before the split is all 5-minute). The app shows the split on a request's Cache and the usage tiles' tooltips. **Requests and failures** count requests, each once by how it ended: a request failed when the agent got an error. One that a later try answered (the next member of a group, another account) is not a failure; the Usage page, the Requests card, its rankings and group rows, and `magpie usage` count it as answered on a retry (`retried` in the JSON). On the Requests card each count is a button for its requests in the list below (the Failed and Retried filters, `failed=1` and `retried=1`). A try a vendor refused or turned away keeps its own row, for what it billed and why, marked handed on, and isn't counted as a request. The CSV's last columns are `tries` (on the final row, when the request took more than one), `handed_on`, then `tier_asked`, `tier_sent` and `tier_served`: the service tier the client asked for (Claude's fast speed reads `fast`), the one that try went out at (a fast pick sends `priority`, or `ultrafast` where the pick is set to Ultrafast; Ultrafast becomes `priority` on an account whose plan doesn't offer it), and the one the vendor's reply said it ran at, empty when the reply doesn't say. The JSON log keeps them as `tier_asked`, `tier_sent` and `tier_served`, and each try in `/v1/magpie/route` has `tier` and `tier_served`. The last column, `upstream_stop`, is why the upstream said its reply ended, in its own words (`stop`, `tool_calls`, `end_turn`, a Responses reply's `completed`), empty when it said none. A request's row on the Requests page shows it as Upstream stop reason. **Output speed** (tok/s on the Usage and Routing pages, `magpie usage`) is output tokens over the time after the first token, summed per request: the tokens of every timed request over the sum of their windows, so concurrent subagents never add up. A reply that reasoned counts only its answer, from its first text (`usage.DecodeOf`): hidden reasoning is written before the stream shows anything, so counting it in a window that starts after it read gpt-6.1-sol at hundreds of tok/s. A reply that reasoned and then wrote only tool calls, one not streamed, and a burst (under 100 ms, or over 10,000 tok/s) tell no speed. So does a reply its vendor held back and let go in a burst at its end, however long the burst took to come: one whose content came in under a quarter of its window (`flow_ms`, from a tenth of its bytes to nine tenths, scaled to the whole), or that reasoned and whose answer would have come over 20 times faster than its reasoning came before its first text (its text came with the burst). The time a burst takes to come is not the time it took to write, and that time isn't seen. History is read the same way, as it keeps the reasoning, the first text and the flow; a record from before the flow was kept is told by its reasoning's pace alone. A call read from an agent's session file rather than seen by the gateway has no first token: Claude Code's file has only when the line that asked for a reply and the reply's last line were written. Such a row on the Requests tab shows the least its reply wrote at instead (`≥98 tok/s`): its output tokens over that whole time, the wait for the first token in it, so the speed after the first token is never under it. Only a reply of 1,000 tokens or more written over a second or more shows one; a shorter reply's wait is too much of its time to tell anything; a count of tokens bounds that share, a duration doesn't (a 5 s reply can be 4 s of wait). Claude Code's, Claude Desktop's and Codex's rows show one: Claude's line that asked for a reply is written before it is sent, and a Codex call in a turn of several is timed from what the model had last been given before the call's first output item to its last, never from the output of a tool the call itself ran, which an older rollout writes before the call's token count. OpenCode's rows show none. A call whose session file timed it (a Codex turn of one call) shows its own speed when the time after its first token is a second or more and it wrote 100 tokens or more in it (`usage.CodexWindowMs`, `usage.CodexBodyTokens`). That time can be one network push rather than the writing: a reply whose first token came at 6.8 s and the rest 0.2 s later read 1,900 tok/s. When that call reasoned, the file has its first token but not its first text, so no speed is measured; it shows the least its answer wrote at instead: its output less the reasoning, all written after the first token, over the time from the first token to its end, held to the same second and 100 tokens. Its whole output over that time would count reasoning written before the first token and read high. A Codex turn of one call that tells neither shows the least it wrote at over the whole turn, the wait for the first token in it (`≥120 tok/s` for 1,200 tokens in 10 s), by the same rule as any floor over a whole time: 1,000 tokens or more over a second or more. Under that the wait is too much of the time to tell anything, so an 11-token answer in 2.75 s shows none, as an 815-token Claude Code reply does. Gateway rows and other agents' keep their own speed. A floor never mixes into a measured speed: the Speed trend and `magpie usage` count timed calls only. A group's speed (a row of the Usage page's ranking, by model, provider or agent, its Output speed card, and the overview's lists) says how many replies it is of, by one rule: with replies whose speed was measured, their speed and how many they are (`62 tok/s · 2 timed replies`, not beside "260 requests" alone); with rows that tell a floor, the least those wrote at and how many (`≥100 tok/s · 30 replies`); with both, both, the one of more replies first (`≥50 tok/s · 259 replies; 125 tok/s · 1 timed reply`), as a few measured replies may not stand for the rest; with neither, nothing. Counts are words, never a bare number in parentheses. The Output speed card shows the first as its figure, its count beside it, the other under it, and the mean wait for the first token on a line of its own with how many timed replies it is of. The tooltip says what each is of. A ranking by speed orders and bars a group by the figure it shows first (`usage.Totals` `decode_n`, `floor_out`, `floor_ms`, `floor_n`; `usage.Row.Floor`). It lists **accounts** too: each Codex, Claude or other subscription account's tokens and cost, by the account that actually answered — the one that took over after a failover, the one `X-Magpie-Account` pinned. The account is named as the Routing view names it (its email or login, never a token); the CSV adds `provider_account` (JSON `providerAccount`), `magpie usage --csv --account ` keeps one account's calls, and the app's Usage page has an Accounts list and an Account filter on Requests. An older record names the account its call went out as when its host says so (`chatgpt.com as dee@example.com`); otherwise it appears as **account not recorded**, never inferred from today's sign-in. OTLP export never carries the account. One magpie can serve several computers (an office one, a personal one): share it on the network (Settings → Share on local network), and on each other computer add it as a **Remote magpie** — in the app's Add sheet, under its own heading, Another computer's magpie, after On this machine; on the TUI's Providers page (`a`, then its address and key; `w` changes the address, `m` fetches its list again), or `magpie provider add remote-magpie sk-magpie-… url=http://192.168.1.20:3425 id=office`. Each computer's own magpie still wires its agents, while the providers, routing groups (`office/group/…`) and usage are the shared one's. A request goes on in the API the agent spoke — Anthropic Messages, Responses, Chat Completions, token counting — and a model the shared magpie's provider serves on another API only is turned into that API once, never on both computers. Its list is the models the shared magpie's agents are shown, each named with its provider there (`Claude Sonnet 5 · Relay A · office`) — the ids stay ids (`office/relay-a/claude-sonnet-5`), only these labels carry the names. Its image and video models are discovered automatically and served through its image generation/edit and video creation/status/content APIs. System One is also configured automatically at its `/v1/systemone`: refresh the remote provider's model list to discover its decision models, including Jev, Clef and models with custom names. These appear as decision models rather than chat models. A bare provider id such as `office` asks the first listed decision model; it does not pick another model to satisfy a key's limits. If the key permits only another model, use its full `office/provider/model` id to avoid a 403. Embeddings, rerank and speech-to-text requests use the remote's corresponding APIs, and its exposed retrieval models are kept when fetching its list. These remain in agent model lists as on the remote; being listed does not mean an embedding or rerank model can hold a conversation. The remote must serve the requested API and model, and allow the supplied key to use them. Responses uses HTTP, including SSE, rather than WebSocket. An older remote without decision-model discovery still serves its existing APIs; update it and refresh the list to expose System One models here. Its quotas show too: the Usage page, the menu bar and `magpie quota` list the shared magpie's cards named with it (`Codex · office`, id `office/codex`), as that magpie last read them — only the computer holding the sign-ins asks the vendors, and it answers from what it has kept (`GET /v1/magpie/quotas/cards`). A card's refresh here has it read that card once more (`POST /v1/magpie/quotas/refresh`, at most once in 30 seconds a card); until it has read anything, the remote's one card says so. Their history is shown with its own. A Codex reset or a check-in is pressed on that computer, not here. Codex's native image tool first asks the provider that served its conversation turn for the requested image model. If that provider does not list the model, or the turn cannot be identified, it uses Settings → Images → Image generation (including Automatic). Off disables this fallback, not a matching model on the turn's provider. An explicit `provider/model` keeps its destination; an upstream image error does not retry through the setting. Magpie Image MCP is not required. WorkBuddy (China)'s daily check-in (签到) is pressed from the app's Usage card (*Check in now*), `magpie accounts checkin`, or `c` on the TUI's Usage page, which checks in every account signed in on this computer at once; its WorkBuddy lines say how today's went. A shared magpie's accounts are checked in on that magpie, from its own app, TUI or CLI. A vendor with two or more accounts lists each under its Daily check-in in **Settings → Usage**, with its own Off/On (#1902). An account turned off is left out of the daily check-in and of *Check in now*, the CLI's and the TUI's too, while the vendor's other accounts are checked in; its Usage card says its auto check-in is off. The choice is kept in `checkinOff` in `settings.json`, by the account's entry in the vendor's check-in file (`workbuddy|`), and set with `POST /api/settings/checkin-account {key, on}`. A plugin whose `auth` hook has `checkin` checks its own accounts in the same way: once a day while its switch under **Settings → Usage → Plugins** is on, with the result on each account's Usage card. A check-in the vendor wants a captcha for is shown as such (check in in the vendor's app); magpie never solves captchas. See [provider-plugins.md](subsystems/provider-plugins.md#a-plugins-daily-check-in). A plugin can also declare its own switches for its provider (`auth.options`). They are listed under **Plugin options** in the provider's editor, an Off/On each with the plugin's own words, and saved with the provider. The values are kept in `pluginOptions` in `settings.json`, and the plugin reads them as it works. Changing one starts nothing by itself. See [provider-plugins.md](subsystems/provider-plugins.md#a-plugins-own-options). The gateway issues **gateway keys** for clients, separate from a provider's upstream API keys. Turn on **Settings → Share on local network**, then open **Gateway → Gateway keys → Add gateway key**. This block appears only while sharing is on. Create a named key for each client and copy it from its row. To move from another gateway without touching its clients, enter the key they already send in **Your own key (optional)** (8–256 characters, no spaces, not starting with `sk-magpie-`, unlike any other key); left empty, magpie makes one. The list shows only its last four characters, and it is counted, limited and restricted as any other key, from this computer too. Rename, disable, rotate or remove keys independently; rotation and removal ask for confirmation. Rotation keeps the name, enabled state and usage history; other keys are unchanged. While sharing is on, **Gateway → Connect** offers loopback and shared addresses, plus enabled gateway keys, for all connection examples. With sharing off, Connect keeps the original **API key** field and local `magpie` token, without a gateway-key picker. Its arbitrary local option is **This computer**, distinct from the named **Magpie** key. For a headless gateway, use the CLI before exposing the port: ```sh magpie gateway-key add "Remote laptop" # prints the new credential once magpie gateway-key add Family --key - # keeps a key clients already send, read from stdin magpie gateway-key list # ids, names, enabled state and masked keys magpie gateway-key rotate # prints the replacement; identity stays the same magpie gateway-key remove # revokes remote access magpie gateway-key limit week --tokens 2m --cost 5 # its own limit magpie gateway-key limit list # each limit, used, left and reset magpie gateway-key limit 10d --tokens 5m # every 10 days, from now magpie gateway-key limit 5h --tokens 200k # every 5 hours, from now magpie gateway-key limit add week --cost 20 # one more limit; all must pass magpie gateway-key limit remove 2 # take off the 2nd limit magpie gateway-key limit reset # count its usage from 0 again (reset 2: only the 2nd) magpie gateway-key limit off # no limit magpie gateway-key models openai/gpt-5 anthropic/* # only these models magpie gateway-key models all # every model magpie gateway-key accounts codex/me@example.com # only these accounts magpie gateway-key accounts all # every account magpie gateway-key quotas on # told the quotas of what it may use ``` Each gateway key can have its own **limit**: a token total, an estimated cost in US$, or both, per day, week or month (calendar windows in local time: from midnight, from Monday, from the 1st), every N days (#1509; N from 1 to 3650) or every N hours (#2102; N from 1 to 8760, such as 5 for a subscription's 5-hour pace). An N-day or N-hour cycle starts when the limit is set and runs N×24 or N hours, then the next one starts where it ended, so a restart, a time-zone change or a daylight-saving shift doesn't move it. Changing only the caps or **Count cache reads too** keeps the cycle; changing N or the period, or turning the limit on, starts a new one then. Set it with **Limit** on the key's row (saved with Save) or `magpie gateway-key limit`; the row shows what the key has used, what is left and when it resets. A key can have up to five limits, each in a window of its own (#2227), such as $3 every 5 hours and $20 a week, as a subscription's two windows: **Add a limit** in the editor or `magpie gateway-key limit add`. A request passes only while every one has something left; its reservation is held in each window, and its 429 names the spent limit that resets last. Two limits can't share a window: a window's tokens and cost caps go in one limit. Setting a window without `add` changes the key's limit of that window, or its only one. `GET /v1/magpie/limit` gives each in `limits`, and in `limit` the one holding the key most, which a magpie from before #2227 reads; such a magpie also keeps only the first limit (`limit` in caller-keys.json, the rest under `moreLimits`). Tokens counted are a call's uncached input, output and cache writes, plus cache reads when **Count cache reads too** is on. Cost is an estimate at the Usage page's prices; a call with no known price adds none. A call counts in the window it started in. Once a key is spent, its requests are refused before any provider is asked, with a 429 in the API's own error shape that names the key, the limit and the reset time, plus `Retry-After`; other keys are unaffected. The counts are read from the usage log, so they survive a restart. A request in flight holds a reservation (its body's size in tokens plus the key's mean output per call), so requests sent at once overshoot by about one call; a streamed reply is settled when it ends with the usage its vendor reported. **Reset usage** in the key's limit editor (or `magpie gateway-key limit reset`) counts the key's tokens and cost from 0 together, from now: the editor's resets the limit it is open on, the CLI's every limit, or the nth with `reset `. An N-day or N-hour key starts a new cycle then. A day, week or month key keeps its window's end (midnight, Monday, the 1st): only the calls before the reset stop counting, so it doesn't drift off the calendar. The reset is stored with the key (`since` in caller-keys.json, magpie's own: a value a client sends is ignored), so it survives a restart; the usage log itself is untouched. Keys saved before keep their JSON as it was. A key can read its own status with `GET /v1/magpie/limit`; it is told its own and no other key's, whatever the request names. A magpie that has this one as a Remote magpie provider shows each of its keys' limits on its Usage page this way (#1547). Requests from this computer that send no gateway key are not limited; a gateway key used from this computer is. A gateway key can also be held to some **models** (#882): pick them with the **All models** badge on the key's row, or run `magpie gateway-key models openai/gpt-5 anthropic/*` (`all` takes the restriction off). A pattern is `/` or `/*`, matched against the provider that serves the call, so a bare model name is resolved first, or a routing group, `group/` (`group/*` for every group). A key that names a group may use the group with every member in it, though not those members asked for by name. Such a key sees only its models in `/v1/models`, `/v1/codex/models` and the Anthropic and Gemini lists, a routing group it doesn't name only when it may use every member, and is refused any other model with a 403 in the API's error shape before a provider is asked; a fallback it may not use is skipped. A key with no models listed may use every model. A gateway key can also be held to some **accounts** (#905): the accounts and keys its requests may use, picked in the same menu right after their provider's models — an account by who is signed in, a key by its fingerprint — or with `magpie gateway-key accounts codex/me@example.com openai/` (`all` takes the restriction off). The list holds a key to some accounts **of the providers it names**: a provider it names no account of, the key uses as it always did. The list keeps an account by its stable id, kept through renames, shown by who is signed in; one gone later — a key rotated, an account signed out, an account of an agent that keeps no logins renamed — is kept as it is, matching nothing, so the key is held closer, never wider. A routing group the key names is its members' through the accounts it may use only: naming a group is not naming its accounts, and magpie's own calls for the key — a web search, a picture described, a Codex title — are of the models the user picked, but their spend lands on an account, so the accounts hold them too. When every account or key behind a model is one the key may not use, the request is refused with a 403 in the API's own error shape naming the model and the accounts it may use, before a provider is asked — after a usage cap's 429 and before an account barred for the model's — and `/v1/models` lists the key only the models an account or key it may use serves, the Routing view's left-out list marking what the key held out. A key with no accounts listed may use every account, as keys always did. While LAN sharing is enabled, or `MAGPIE_ADDR` puts the gateway on the network (a host other than loopback or `localhost`: `0.0.0.0` in the Docker image, a server's address), remote requests require an enabled gateway key sent as Bearer, `x-api-key`, `x-goog-api-key` or `?key=`; one without gets 401. A request is remote when its peer isn't loopback, which includes Docker's port publishing (the peer is the bridge, not the host's loopback) and a WSL distro under NAT. Loopback remains permissive: any token works, including a stale or disabled gateway key. Only a valid, enabled key is attributed to its named identity. Neither shared nor on the network, a remote request is refused (403). A `MAGPIE_ADDR` pinned to one interface's address asks a key even of this computer's agents calling that address; `0.0.0.0` with agents on `127.0.0.1` doesn't. The Claude subscription's own tool calls back to magpie are let in there by their run's one-time token, without a key (#1546). Sharing listens on every interface, unless `MAGPIE_ADDR` names a host of its own: `MAGPIE_ADDR=127.0.0.1:3425` behind Tailscale Serve, or one interface's address, stays where it is while shared, and what reaches it from elsewhere still needs an enabled gateway key (#1112). **Codex on another computer** (#1281) can name the shared magpie its model provider, with a gateway key, and read magpie's models in Codex's own catalog shape from `GET /v1/codex/models`. `/v1/models` is OpenAI's list, which Codex can't read, and `/backend-api/codex/models` is the ChatGPT backend's, for a Codex signed in to ChatGPT on the gateway's own computer; a gateway key gets a 401 there. ```toml model_provider = "magpie" model = "provider/model" [model_providers.magpie] name = "magpie" base_url = "http://192.168.1.20:3425/v1" model_catalog_url = "http://192.168.1.20:3425/v1/codex/models" env_key = "MAGPIE_API_KEY" # the gateway key wire_api = "responses" supports_websockets = false ``` The list is the models the shared magpie shows Codex (its Agents page picks), each as the `model_catalog_json` magpie writes for a Codex on its own computer describes it, and only the models the key may use. Codex 0.161 reads it by default; 0.160 needs `[features] api_key_model_discovery = true`. Codex reads at most 1 MiB of a catalog, and every entry carries Codex's prompt, so past about 125 models the list keeps the first that fit and the answer's `X-Magpie-Left-Out` header says how many it left out: pick fewer for Codex, or hold the key to fewer. A request that reaches loopback through a proxy or tunnel on this computer (Cloudflare Tunnel's `cloudflared`, ngrok, Tailscale serve or funnel, frp's HTTP proxy, Caddy, nginx) is someone else's, and is answered as one from another machine (#1022): it carries a forwarding header (`Forwarded`, `X-Forwarded-For`, `X-Real-IP`, `Cf-Connecting-IP`, `True-Client-IP`, `Tailscale-User-Login`), which no agent sends. Without sharing it is refused; with sharing, or a `MAGPIE_ADDR` on the network, it needs an enabled gateway key, as from the network. This covers every route: the model APIs, quotas, MCP sign-ins. To serve magpie through a Cloudflare Tunnel, turn on **Settings → Share on local network**, add a gateway key for each client, point the tunnel's service at `http://127.0.0.1:3425`, and give clients the tunnel's address with that key. A tunnel that forwards bare TCP (frp's `tcp`, `ssh -R`, socat) adds no header and can't be told from an agent here: point it at this computer's LAN address with sharing on, not at loopback. A proxy here that signs its clients in itself can be trusted with `MAGPIE_TRUST_PROXY=1`, which keeps what it forwards local, as before. To let other computers in only from some addresses, list them as IPs and CIDR ranges in **Settings → Network and sharing → Allowed addresses**, or in `MAGPIE_ALLOW_IPS` (`203.0.113.7,10.0.0.0/8,2001:db8::/32`), which comes first. A request from any other address is refused 403, even with an enabled gateway key, and the refusal names the address it came from. This computer is always let in. The address is the connection's own, never a forwarding header, so behind a proxy or tunnel every client has the proxy's address: limit them at the proxy. A mistyped entry in `MAGPIE_ALLOW_IPS` lets nothing in rather than everything. When settings.json can't be read, or doesn't parse and has no good backup, the list read last stays in force; if none was read since magpie started, only this computer is let in until the file is repaired. `magpie web`'s page honours the same list. **HTTPS.** A magpie reached over the internet (a server used as a relay) can serve HTTPS itself, with no proxy in front: ```sh magpie serve --tls-cert /etc/letsencrypt/live/relay.example.com/fullchain.pem \ --tls-key /etc/letsencrypt/live/relay.example.com/privkey.pem ``` `MAGPIE_TLS_CERT` and `MAGPIE_TLS_KEY` do the same (for Docker), and `magpie web` and `magpie daemon install` take the same flags; the service keeps the pair when it is installed again without them, and `--no-tls` takes it away. The files are PEM: the certificate with its chain after it, and its private key. One without the other, or files that don't load, refuse to start rather than serve plain HTTP. The certificate is read again when either file changes, so a renewal (certbot, acme.sh) is served on the next connection without a restart; files that don't load then (a renewal half written) leave the one before in place, and the log says so. Other computers reach the gateway, and `magpie web`'s page, over `https://` only, and the network links magpie prints say `https://`; plain HTTP from them is answered 400. This computer may still call `http://127.0.0.1` on the same port, as the agents, MCP servers and status checks magpie wires here do, since no certificate for the server's name covers loopback. A container or a WSL distro calling through its NAT address is another computer, and uses `https://`. Gateway keys, sharing and allowed addresses work as over HTTP. Caddy is the alternative that also gets the certificate for you: run `relay.example.com { reverse_proxy 127.0.0.1:3425 }` with sharing on and a gateway key per client. A proxy on this computer adds forwarding headers, so its requests count as another computer's, as above. **Usage → Overview → Gateway keys** groups calls by the client's key, never the provider's credential. **Usage → Requests** offers the same filter; CSV includes `caller_key_id` and `caller_key_name`. Deleted keys keep their history. The usual local `magpie` token and older records stay unattributed. Caller attribution includes chat, images, video creation and System One. Provider attribution stays independent; CSV puts `provider_key_*` before `caller_key_*`. An existing LAN key becomes **Magpie** without changing the credential. `lanKey` remains in settings for older Magpie versions. Disabling or removing the default key replaces that mirror with a random non-empty revoked value; rotation does not re-enable it. Turning sharing off and on again neither re-enables a disabled default key nor makes a removed one again; only asking for a new sharing key makes one. The key store records migration completion, even if `lanKeyId` cannot be saved, so reads do not keep retrying that write. A migration write failure is logged without preventing gateway startup, CLI key management, or the Settings and key-list pages from opening. If settings are read-only, changing the default key fails without changing it: make `settings.json` writable and retry so older versions cannot keep accepting its old credential. Independent named keys remain manageable. Gateway credentials stay in `~/.config/magpie/caller-keys.json` (XDG-aware, mode `0600`), never in usage records or list responses. Baidu Qianfan's [Token Plans](https://cloud.baidu.com/doc/qianfan/s/Dmrabu8b6) are available as `baidu-qianfan`: a personal (个人版) and an enterprise (企业版) plan and pay as you go, each with its own Chat Completions, Responses and Anthropic Messages endpoints, and a key that works only on its own plan. Add it with `magpie provider add baidu-qianfan ` — the id it carried its first day, `qianfan-token-plan`, is taken too. The plans serve no model list, so the preset carries their documented models; pay as you go serves its own at `/v2/models`. Alibaba's Token Plan is sold in two places, each with its own `sk-sp-` key that only its own host takes: on the Qwen AI platform as `qwen-token-plan` (`token-plan.maas.qianwenaiapi.com`), and on Alibaba Cloud Bailian as `bailian-token-plan` (`token-plan.cn-beijing.maas.aliyuncs.com`). Both serve Chat Completions at `/compatible-mode/v1` and Anthropic Messages at `/apps/anthropic`, and carry the plans' documented text models for when the host lists none. Bailian's also answers its decision model, `decision-model-preview`, on System One at `/compatible-mode/v1/systemone` with the same key, so a routing group can pick it as its classifier. The same model on the Qwen AI platform's pay as you go is `qwen-decision` (`maas.qianwenaiapi.com`), and on a Bailian workspace `bailian-decision`. Neither plan has an API for its credits: the console's subscription page shows them, so magpie shows no plan usage for these keys. ### Google Vertex AI Google Vertex AI (`google-vertex`), which Google's documentation now calls Gemini Enterprise Agent Platform, serves Google's Gemini models from your own Google Cloud project, which is billed for them. It takes no API key: each request is signed with a token minted from your Google credentials. First turn on the Agent Platform API (`aiplatform.googleapis.com`) in the project (billing has to be on there), then sign in with `gcloud auth application-default login` or have a service account's key file ([Google's guide](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/start/gcp-auth)). [Where magpie looks for the credentials, and the kinds it reads](subsystems/providers-accounts.md#responsibilities-and-sources-of-truth). ```sh magpie provider add google-vertex project=my-project magpie provider set google-vertex location=us-central1 # global (the default), us, eu or a region magpie provider set google-vertex credentials=~/keys/vertex.json magpie provider set google-vertex impersonate=vertex@my-project.iam.gserviceaccount.com magpie provider set google-vertex impersonate= # empty clears it magpie provider test google-vertex ``` `project=` is the project's id. `location=` is `global` (the default, and what an empty value goes back to), `us`, `eu` or a region such as `us-central1`. `credentials=` names a credentials file to sign with in place of gcloud's sign-in, such as a service account's key; a relative path is made full from the folder the command runs in. `impersonate=` has requests made as that service account; the account signed in needs the Service Account Token Creator role on it. The app's editor asks the same in **Project ID**, **Location**, **Credentials file** and **Service account**; the TUI's `a` asks for the project alone, and the CLI sets the rest. A key is refused, and so is an address (`url=` and the like): the project and location make the only one its token is sent to. Adding, listing and showing it ask Google nothing; the credentials are read when a request is signed. Each location serves models of its own, and Vertex AI has no list to ask which, so magpie gives each location the models it answered with on 2026-10-06 (and `gemini-4-argon`, at `global` since 2026-10-13): | Location | Models | | --- | --- | | `global` | `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.5-flash-lite`, `gemini-4-argon`, `gemini-3.1-pro-preview`, `gemini-3.1-pro-preview-customtools`, `gemini-3.1-flash-lite`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` | | `us`, `eu` | `gemini-3.8-flash`, `gemini-3.7-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.5-flash-lite`, `gemini-3.1-flash-lite` | | A region | `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` | A region that serves one more, as asia-northeast1 serves `gemini-3.5-flash`, has it typed in with the rest you want: ```sh magpie provider models google-vertex gemini-2.5-pro gemini-2.5-flash gemini-2.5-flash-lite gemini-3.5-flash magpie provider models google-vertex all # back to the location's list ``` `magpie provider set google-vertex location=…` keeps the models picked, so pick again after a move. Google's [Priority PayGo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/priority-paygo) and [Flex PayGo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/flex-paygo) are asked for with request headers. The editor's **Headers** offers both, `X-Vertex-AI-LLM-Request-Type` and `X-Vertex-AI-LLM-Shared-Request-Type`, to add; their values are the ones Google's pages give. From the CLI: `magpie provider set google-vertex header.=`. [What was seen of Priority PayGo](subsystems/providers-accounts.md#constraints-and-failure-behavior). When a request fails: - `no Google credentials at `: there is no credentials file there. When it is the provider's own, correct `credentials=`, or clear it with `credentials=` alone; otherwise run `gcloud auth application-default login`, or set `credentials=`. - `GOOGLE_APPLICATION_CREDENTIALS names , which isn't there`, or `GOOGLE_APPLICATION_CREDENTIALS is "…", not a full path` (`CLOUDSDK_CONFIG` too): the variable names no file magpie can read, and gcloud's own isn't read in its place. Give the whole path (a `~/` at its start is the home), unset it, or set `credentials=`. - `the Google credentials in : the sign-in was refused`, with the advice to sign in again: the sign-in expired or was revoked. Run the command the advice names for that file: `gcloud auth application-default login` (with `--impersonate-service-account=` when the file impersonates one) for gcloud's Application Default Credentials, `gcloud auth login ` for a file under gcloud's `legacy_credentials`. A file of your own is written anew, or another one given with `credentials=`. The next request reads the new file, with no restart. - A 403 `PERMISSION_DENIED` naming `aiplatform.endpoints.predict`: the account that signs, or the service account it goes as, may not use Vertex AI in the project. Give it Agent Platform User (`roles/aiplatform.user`) on the project ([the role](https://docs.cloud.google.com/gemini-enterprise-agent-platform/machine-learning/general/access-control#aiplatform.user)). - `impersonating …: the sign-in was refused (403)`: the account signed in needs Service Account Token Creator (`roles/iam.serviceAccountTokenCreator`) on that service account. ### Plugins A subscription magpie doesn't sign in to itself can come from an [OpenCode](https://opencode.ai) provider plugin or a [pi](https://github.com/earendil-works/pi) package: the npm packages OpenCode users install to sign in to a plan (their `auth` hook), and the pi packages that register a provider (`pi.registerProvider`), work in magpie as they do there. magpie runs them on [Bun](https://bun.sh), downloaded the first time a plugin needs it, and the plugin signs in, lists the models and makes each request; magpie serves them to agents like any provider's. ```sh magpie plugin add opencode-gemini-auth # an npm package, or a path to a plugin of your own magpie plugin add pi-antigravity # a pi package, the same way magpie plugin # the plugins, what each signs in to, and whether you are magpie plugin login google-plugin # its sign-in: the method, its questions, the browser or a key magpie plugin logout google-plugin magpie plugin off opencode-gemini-auth # on brings it back; rm removes it; update updates them all ``` A provider id magpie already has (google, openai, anthropic) is `-plugin`. In the app, Settings → Plugins adds and removes them, and the providers they sign in to are in Add provider → From plugins. Sometimes two installed plugins sign in to the same provider id, for example a third-party plugin and your own copy of it. One of them serves that id: the one you picked, or else the one added last. Both plugins stay listed, and each row in Plugins › Installed says which one serves the provider. The other row has **Use for **, which switches the provider to it. `magpie plugin` prints the same thing, and ```sh magpie plugin use ~/dev/my-acme-auth acme # this plugin serves acme from now on ``` makes the pick. Each plugin can still be switched off or removed on its own. A package is pi's when its `package.json` has a `pi` manifest, the `pi-package` keyword, or depends on `@earendil-works/pi-coding-agent`. magpie installs pi beside it and loads it with pi's own loader; the providers it registers are what magpie uses, and its commands, tools and renderers are left alone. Its sign-in runs as in pi: an OAuth login's questions and pages, and the dialogs and pickers it shows (`ctx.ui`, its terminal components too), are asked in magpie's sign-in; an API key works as well. pi's own files (its `auth.json` and what a package keeps) are in `pi/` under magpie's config directory, not your `~/.pi`, unless `PI_CODING_AGENT_DIR` says otherwise. Requests reach the package as pi sends them, and its replies keep their thinking and tool calls. #### For plugin authors A plugin is an OpenCode plugin or a pi package; nothing magpie-specific is needed. For an OpenCode plugin magpie reads a few more fields, which OpenCode ignores: - **The provider's icon**: `icon` on the `auth` hook, or `"magpie": { "icon": "…" }` in the plugin's `package.json` (for every provider it signs in to that names none). An `https://` URL of a picture on a public host, which magpie fetches once and keeps, or a `data:image/…` URI; PNG, JPEG, GIF, WebP, ICO or SVG, at most 1 MB. Anything else is ignored, and the icon the plugin market lists for the plugin is shown instead. The `package.json` icon is also the card's picture under Discover → Unofficial, on GitHub for a repository tagged `magpie-plugin`. - **An API key's field**: a `type: "api"` method's `label` titles the key's field, as OpenCode's dialog does (one that only says "API key" reads " API key"), and its `placeholder` is the hint inside the field (and after the question in `magpie plugin login`). The method's `prompts` are asked first, as in OpenCode, and reach `authorize(inputs)`. A prompt of `type: "password"` (magpie's own; OpenCode has `text` and `select`) is typed unseen: a masked field in the app, no echo in `magpie plugin login` (#1982). ```js export const LemonPlugin = async () => ({ auth: { provider: "lemon", icon: "https://lemon.example/icon.png", // or "data:image/svg+xml;base64,…" methods: [ { type: "api", label: "Lemon API key (lemon.example/keys)", placeholder: "sk-lemon-…" }, ], }, }) ``` In TypeScript, `icon` and `placeholder` aren't in OpenCode's types: build the hook as a variable (or cast it), or put the icon in `package.json`. #### Gateway middleware A plugin can also be gateway middleware: `onRequest(body, ctx)` sees each request an agent sends before it is routed, `onProvider(body, ctx)` the body each provider it is tried on is sent (`ctx.provider` has its `id`, `name`, `account` and `model`; each try starts from what `onRequest` left), `onEvent(event, ctx)` each event of a streamed reply and `onResponse(body, ctx)` a whole one, all in the agent's own API. A hook returns the changed object, or nothing to leave it; `onEvent` returns `null` to drop an event, and `ctx.reject(status, message)` turns a request away. Declare it with `"magpie": { "middleware": "./mw.js" }` in `package.json`, or add a single `*.middleware.js` file: ```js // alias.middleware.js — magpie plugin add ./alias.middleware.js export const events = ["message_start"] export function onRequest(body, ctx) { ctx.state.asked = body.model if (body.model === "fast") body.model = "deepseek/deepseek-chat" return body } export function onEvent(ev, ctx) { ev.message.model = ctx.state.asked // the agent sees the name it asked for return ev } ``` Middleware runs inside the gateway in [moejs](https://github.com/Calcium-Ion/moejs), a JavaScript engine written in Go (about a microsecond a streamed event), not on Bun: no timers, `fetch` or Node APIs, and imports only of files beside it. A hook that throws or takes too long (250 ms a request, 50 ms an event) leaves what it was given as it was. Settings → Plugins shows each middleware's calls, time and failures. The [plugin guide](https://usemagpie.ai/docs/plugins#middleware) has the rest. Middleware runs through the built-in plugin `middleware` (Gateway middleware in Plugins › Built-in), which stays on while one is on. Turned off, no middleware runs and agents' requests go as they came. Privacy masking is the built-in plugin `redact` (Privacy masking), on while Settings → Privacy asks for any masking; if masking is asked for and it isn't serving, a request gets a 503 and nothing is sent. `ctx.options` is the middleware's entry's `options` in `plugins.json`: set them with its **Options** button in Plugins › Installed, or ```sh magpie plugin options model-map '{"mapping": {"fast": "deepseek/deepseek-chat"}}' magpie plugin options model-map off # clears them ``` The name can be a package's short name (`model-map` for `@magpie-community/middleware-model-map`). A package's `"magpie": { "options": {…} }` is the example the editor starts from. Ready-made middleware, under Plugins › Discover › Gateway middleware (`@magpie-community/middleware-`), mostly New API's channel settings with the same JSON: `param-override` (`param_override`), `model-map` (`model_mapping`), `system-prompt`, `word-guard` (sensitive words) and `think-tags` (strip `…`, or `thinking_to_content`), and `antigravity-cloak`, which keeps the words Antigravity turns Claude Code's system prompt away for out of what it sends Antigravity alone. A middleware's entry in the community `registry.json` has `"kind": "middleware"` and no `providers`. #### Agent plugins A plugin can add an agent magpie has no setup of its own for. Its `package.json` names the module (`"magpie": { "agent": "./agent.js" }`), or it is a single `*.agent.js` file: ```js export const agent = { id: "aider", name: "Aider", bin: "aider", config: "~/.aider.conf.yml", // json, yaml or env model: "model", // the key of the model prefix: "openai/", // written before magpie's model id ua: ["aider", "litellm"], } export function connect({ gateway, model, models }) { return { "openai-api-base": gateway.v1, "openai-api-key": gateway.key } } ``` It is then on the Agents page like magpie's own: picking one of magpie's models writes the model and what `connect` returns, and its own model or Disconnect puts every key back as it was. It runs in moejs like middleware. An id magpie's own agents use is refused. In the market an agent's entry has `"kind": "agent"` (`@magpie-community/agent-`). The full reference is on the site's Plugins page, under Agent plugins. ### What a model costs A call is counted at its **effective price**: what you set for that provider and model if you did, otherwise what the provider's own catalogue lists, and otherwise what models.dev lists for the model's maker. A provider you added by its address, with no catalogue named, has for its own the one models.dev lists at that address (its `api`), when exactly one does and it isn't a plan's: a provider at `https://developer.amd.com.cn/radeon/api/v1` is priced from models.dev's `amd`, whatever you called it. Out of the box that last one is the whole story, and it is the wrong number for any provider that does not charge list price — a relay reselling at a discount or a multiplier is counted at whatever the model's maker charges. Say what a provider actually charges, and the usage ledger and the session totals use that: ```sh magpie model price relay-a/gpt-5.5 # what it is counted at, and where that came from magpie model price relay-a/gpt-5.5 0.12,0.60,0.01,0.15 # input,output,cache read,cache write magpie model price relay-a/gpt-5.5 --reset # take your price off this model magpie model prices # every model you priced ``` The four numbers are USD per million tokens. All four are asked for, because a price missing one would understate the rest of every call; `0` is a model served at no cost, which is a price, not the absence of one. Decimals take a point or, in the app's boxes, a comma (`0,25`). A call to a model no price is known for is counted at the price of the model the reply said answered, at the same provider, when that one has a price: a relay that sells `kimi-k3` as `moonshot-kimi-k3` answers as `kimi-k3`, so its calls are counted at `relay-a/kimi-k3`'s price. A model with a price of its own, `0` included, keeps it whatever its reply says. `magpie model price provider/model` says where a price came from, and for a model with none, the two commands that give it one. A call with no price at all is left out of a total's cost, never counted as free: the Usage page's sum says how many calls it couldn't price beside it ("≈$1.20+", then "3 calls unpriced"; Sessions, which prices by model, says how many models), and `magpie usage` and the terminal app's Usage page say the same after their totals. magpie doesn't guess a variant's price from a similar name (`-auto`, `-max`, `-fast` can each cost differently); give it one with `magpie model price`, or in the provider's editor under the model. A **Kimi Code** membership's models are counted at the Kimi API model each one is, not at the $0 models.dev lists them at for the plan: `k3` and `k3-256k` at `kimi-k3`, `kimi-for-coding-highspeed` at `kimi-k2.7-code-highspeed`. `kimi-for-coding` is K2.8 Preview, which the API doesn't sell, so it has no price until you give it one (`magpie model price kimi-code/kimi-for-coding …`). A **fifth number** is a 1-hour cache write's price. Anthropic bills a cache write kept for 5 minutes at 1.25× input and one kept for an hour at 2× input, and its usage says which were which (`cache_creation.ephemeral_5m_input_tokens` / `ephemeral_1h_input_tokens`, from the API and from Claude Code's transcripts). The fourth number is the 5-minute price; the fifth, when not given, is 2× input for a Claude model — Anthropic's rule. Any other model has no 1-hour price unless you give one: a 1-hour write it reports is counted at the 5-minute price, and neither `magpie model price` nor the app's boxes show a 1-hour price for it. A call recorded before magpie kept the split counts all its writes at the 5-minute price, as it did. A **long-context price** is what the whole request costs once its prompt is over a size, as OpenAI bills gpt-6-astra, gpt-6.1-sol and gpt-6-luna over 272K input (2× input and cache, 1.5× output): ```sh magpie model price openai/gpt-6-astra 10,50,1,12.5 --tier 272k 20,75,2,25 ``` The size counts the prompt the way OpenAI does: uncached input + cache reads + cache writes, and the tier applies when that is **over** the size (272,000 is not, 272,001 is). Then every part of the call — input, output, cache — is at the tier's price, not only the tokens past the size. `--tier` may be given again for another size. The built-in prices carry a tier only where models.dev lists one (`cost.tiers` of type `context`), as it does for those three. Session and day totals sum many calls, so they are counted at the base price; the ledger and each request are counted at the tier they reached. In the app, a model's price row has a quiet **Long-context price** row under it, shown with the list's tier greyed in, or behind a link where there is none. The order a price is looked for in is: **the price for this model → the price for `/*`, which covers every model of that provider → what the provider's own catalogue lists → what models.dev lists for the maker → what models.dev lists for the maker of the model you said it is the same as.** The last is for a provider's own id that no list prices, as WorkBuddy's `kimi-k3-1` or a plugin's `qfmodel` (#2344): set **Same as** on the model in the provider's model editor (`"modelSameAs": {"workbuddy/kimi-k3-1": "kimi-k3"}` in settings.json) and its calls are costed at Kimi K3's list price, at the provider's price rate, never at another provider's tariff. A model with no Same as is never matched to another by its name. `magpie model price ` says when a price came from it. `--reset` removes the first, and says so when a `/*` price is still in force; reset that one by name to take it away too. A price is **one provider's tariff for one model**, not the model's own: the same model through two providers is two prices, and each keeps its own. Nothing an agent can see changes. The model list, the agents' own settings and the pickers that choose a model for a background task — an image, a web search, a description — all still work from the catalogue; only the cost reports read the effective price. Two things worth knowing. The ledger and the session totals re-price when they are read, so adding or changing a price restates earlier figures: they are estimates at the effective price, not settled charges. And a price is per provider and model. Records now identify upstream API keys, but a provider charging different tariffs per key still cannot be costed exactly from a single provider-wide price. ### What a model takes A provider that serves a model models.dev does not list, or lists at the wrong size, has a window and a reply limit magpie cannot know. Say what they are: ```sh magpie model context relay-a/gpt-5.5 262144 # the window a request may hold, or 1m magpie model output relay-a/gpt-5.5 131072 # the most a reply may hold, or 128k magpie model context "relay-a/*" 200000 # every model of that provider magpie model output relay-a/gpt-5.5 --reset # take your limit off this model ``` The `*` is quoted because zsh treats a name it cannot expand as a command that failed, rather than passing the name on as bash does. Both are looked for in this order: **this model → this provider's `*` → the provider's own list → models.dev.** `--reset` removes only the value this model has of its own. A provider you keep unlisted, or switch off, takes them like any other: the numbers are kept, and are what its models take once it is serving again. A window is a number agents are shown **and a routing input**: at 95% of the window a request held on a routing-group member moves to one that takes more, so overstating a window makes that move happen too late. A reply limit is advertised in `/models` and is what a group advertises the smallest of. The gateway doesn't lower a limit an agent sends by it. A request that names none and goes to Anthropic's Messages API, which needs one (Codex's Responses turns, a Chat client's), is sent it as `max_tokens`, within the window as agents are told it. Only a model whose limit nobody knows goes at 16384. A reply that runs into it ends with `stop_reason: max_tokens`, which a Responses client reads as `incomplete_details.reason: "max_output_tokens"` (#1792). Saving a window or a reply limit writes that number into the model lists magpie keeps in the agents' own files — Pi's `contextWindow` and `maxTokens`, OpenCode's `limit`, and Crush's, droid's, Cline's, Qoder's and Zcode's — which an agent reads at start-up. A session already running therefore keeps the window it began with, while the gateway's own `/models` and every request from then on are right at once. ### The name a vendor knows a model by A relay often serves a model under an id of its own — a prefix it namespaces with, a dated name, a `-latest` that is not what models.dev calls it. Say which name to ask for: ```sh magpie model wire relay-b/model-2 # what the vendor is asked for magpie model wire relay-b/model-2 vendor-c/model-2-preview # ask for it by this magpie model wire 'relay-b/*' 'vendor-c/*' # every model, * being the model magpie model wire relay-b/model-2 --reset # ask for it by its own name again magpie model wires # every name your vendors are asked for models by ``` Quote the arguments with a `*` in them: zsh reads a bare `*` as a glob and answers `no matches found`. In the name, `*` stands for the model itself, so `'relay-b/*' 'vendor-c/*'` sends `vendor-c/model-3` for `model-3` and `vendor-c/model-2` for `model-2` — one name for a relay that namespaces its models, each still asked for by its own. A name with no `*` in it sends every model of that key under that one name, which is the right answer only for a relay that does serve them all alike. The model's own key wins over the provider's, and `--reset` takes away only the one it is given, so resetting `relay-b/model-2` while `'relay-b/*'` is set leaves the provider's name in force — the CLI says which of the two is in force after every change, naming the models a name for the whole provider leaves to their own, and `--reset` over a name that was never given says so rather than ticking a removal that took nothing away. Only the request that goes out carries that name, in the model field of a chat, Responses or Anthropic Messages request and of an Anthropic token count; image requests are left with the name magpie knows the model by. Gemini CLI and Antigravity sign-ins go on Code Assist, and there the model an effort picks is a variant of the model magpie knows — `gemini-3.7-flash` at `high` is sent as `gemini-3.7-flash-high` — so a `*` in the name is that variant: `'antigravity/*' 'vendor-c/*'` asks for `vendor-c/gemini-3.7-flash-high` and `vendor-c/gemini-3.7-flash-low` each by its own, as it does everywhere else. A name with no `*` in it, or one given for `antigravity/gemini-3.7-flash` itself, is that one name at every level. Everything else keeps the name magpie knows the model by: the catalog agents pick from, the routing groups' membership, `GET /models`, and what a call is recorded and priced as. What the vendor's own reply said answered is kept beside that, in the ledger's `served_model`, which is what it is for, and it is compared with the name the vendor was asked for — so a model answered under the relay's own id is not read as a swap. That is against the names in force when the ledger is read, since a record keeps what the vendor answered and not the name the request went out under: naming a model after the call re-judges that call, which is then left reading as a swap. What a provider *supports* — whether it takes a temperature, which reasoning levels fit — is still asked about the model magpie knows, so a rename upstream does not change how magpie behaves towards the model. An upstream name is **one provider's**, not the model's: another provider serving the same id is asked for it under its own name, or this one. The model it is given for has to be one that provider serves — `/*` is the way to say one for every model — because a name for a model magpie would never ask the provider for is not a name of its own: the model is asked for by the name magpie knows it by, and nothing anywhere would say the name given for it is not the one in force. A relay that serves a model under an id of its own *and* lists it does get the name; what is refused is a model the provider's list does not have at all. The model test uses the name too, so a relay that only knows its own ids does not report a working model as broken. An image model is the exception that follows the rule above: it is tested by the name magpie knows it by, on the images API and on the chat it falls back to, because that is how a drawing is asked for. ### Routing groups A routing group is several models, from one provider or many, that an agent picks as one: `group/`. The gateway routes each request over every member's keys and accounts together. A model two of your providers serve under the same name becomes a group on its own; the Routing view in the app and `magpie group` make any other: ```sh magpie groups # yours, then those magpie found magpie group add "Opus anywhere" models=claude/claude-opus-5-5,copilot/claude-opus-5.5 routing=order stays=session magpie group opus-anywhere # one group, its models in order magpie group set opus-anywhere models+=openrouter/anthropic/claude-opus-5.5 routing=usage magpie group set opus-anywhere models-=copilot/claude-opus-5.5 magpie group set opus-anywhere routing=weight weight=claude/claude-opus-5-5=3,copilot/claude-opus-5.5=1 magpie group copy opus-anywhere "Opus test" # the same models, routing and rules, to change on its own magpie group rm opus-anywhere # one magpie found is hidden; magpie group restore brings it back magpie claude group/opus-anywhere # use it ``` `routing=` is `smart` (the default: of the subscriptions with quota to spare, the one whose allowance renews soonest first), `order` (the first model until it can't answer, then the next), `rotate` (each turn to the next member), `usage` (least used first) or `pace` (weekly pace: the account with the most of its week left per hour until it renews first, so less of a week is lost at its reset) or `weight` (each model takes its share of the requests by its weight, smoothly interleaved: `weight=claude/claude-opus-5-5=3` gives it three requests for every one a model left at weight 1 takes; a weight is 1 to 1000, `weight=` alone clears them all, and a group in the group counts as one member, `weight=group/fast=2`). A key has no allowance to weigh, so it goes by its order or by what magpie sent it lately — except a sub2api key its owner gave a 5-hour, day or 7-day limit: with the provider's Balance URL set to the relay's `/v1/usage`, its card shows those windows and routing weighs it by them as it does a subscription, resting it till the window renews once the relay says `api key 7天限额已用完` — or till its windows are next read short of full, its limit raised or its usage reset. A key bought as a plan — Kimi Code, GLM Coding Plan or Z.ai, MiniMax Coding Plan, OpenCode Go, Command Code, Ollama Cloud — is weighed by its plan's windows the same way. With one key or account, the Routing page still shows what it has left. `stays=` is how long a conversation stays with the key or account that answered it: `auto` (the default, while the vendor's cache of it is worth keeping), `session`, `turn`, `off` or `strict` (never moved to another key or account, even past its usage cap: its errors go to the agent). `models=` replaces the whole list, in order; a bare model id works when only one provider serves it. `listed=no` (the group editor's **Model lists: The group alone**) keeps the models a group names, and those its patterns match, out of `magpie models`, `/v1/models` and every agent's list, so a client offers the group alone. They are still served: the group routes over them, and a request naming one as `provider/model` is answered. A group in it keeps its own setting, and a group switched off keeps nothing out. `listed=yes` lists them again. To keep a whole provider for groups, use its **Only through routing groups** switch instead. A group can also be of decision models alone (Jev and the like: TypeSafe's, Bailian's `decision-model-preview`, a plugin's marked models) — never mixed with models that hold a conversation. It is not offered to agents; it is asked at `POST /v1/systemone` with `"model":"group/"` (or its id or name), and a chat group can take it as its classifier. Each of its models' keys and accounts is asked in the group's routing order: one that fails as a chat request would fail over (a 429, a 5xx, an unreachable host, an exhausted key) rests and the next is asked, and the Routing page's Requests list shows every try. It takes no patterns, rules, classifier, effort or fast mode. ```sh magpie group add Jevs models=typesafe/jev-latest,bailian-token-plan/decision-model-preview routing=order curl -s localhost:3425/v1/systemone -H 'Authorization: Bearer magpie' \ -d '{"model":"group/jevs","state":{"message":"fix the login bug"},"questions":{"intent":{"type":"choice","choices":["bug","feature"]}}}' ``` The Routing page's Requests list defaults to the time-ordered By request view. Choose By session to group calls by the agent's session ID; the page remembers your choice across reloads. Codex title helpers with an explicit parent or fork source join their originating chat, retaining their title badge and contributing to its cost. Titles without ancestry and ordinary forked chats stay separate. Codex chat names come from its local name index and follow renames. Other agents' sessions (Claude Code and the rest the Sessions page reads) take the name the Sessions page shows for them, read from their own files on this computer. Unknown or remote-only names fall back to the ID; the full ID remains in the heading tooltip. Expand a session to see each request. Each request and session shows its estimated cost at the effective model prices, including cache reads and writes. Session totals cover the listed requests only (the live trace or the selected day's retained history), and a `+` marks a partial estimate. Calls without a session ID are listed separately; old history without token tiers, or a model without a known price, shows `—`. Opening the Providers page automatically checks Alma, CC Switch, Claude Code and Codex for local provider configurations that can be imported. With no providers yet, a hint shows how many were found and which apps they came from; otherwise a small **Import local configurations** link sits beside **Add provider**. Both open the existing picker with only the offered configurations selected; nothing is added until you confirm the selection. **Ignore** remembers the offered configurations on this device across window restarts; new or changed configurations can be offered again. **Add provider → Import…** still includes ignored configurations. Discovery runs in the background, at most once a minute when opening or refreshing the page, and refreshes after an import. Configurations already in magpie, entries that cannot be imported, providers turned off in their source app, and ID collisions that cannot join as another key are excluded from the hint. The latter two remain available through manual import. Counts refer to configurations in source apps, which may include the same provider in more than one app. Only app names and opaque configuration fingerprints reach the automatic hint; ignored fingerprints are kept in local browser storage. The app's Import from other apps dialog can copy providers from Claude Code's `settings.json` (`CLAUDE_CONFIG_DIR` when set) and Codex's `config.toml` (`CODEX_HOME` when set) into magpie. Codex imports custom `[model_providers.*]` entries with an inline `experimental_bearer_token`, including fixed headers for custom providers in `[model_providers.*.http_headers]` and models from `[profiles.*]` or `model_catalog_json`. Review the entries before importing; subsequent changes to agent settings are not automatically copied to magpie. Entries that point back to magpie or only name an `env_key` are skipped. The same dialog reads OpenCode's own sign-ins, `auth.json` in `$XDG_DATA_HOME/opencode` (`~/.local/share/opencode` when unset, on Windows too), where `opencode auth login` keeps them. An API key comes in on the magpie preset at the same address: OpenCode Go's key on OpenCode Go, Zen's (`opencode`) on OpenCode Zen, and a vendor's (OpenAI, Anthropic, DeepSeek, OpenRouter and others) on its own. OpenCode keeps no address beside a key, so one of a provider magpie has no preset for is listed and skipped. Sign-ins (Claude Pro/Max, Copilot, an enterprise server's token) are listed and skipped; add the subscription in magpie instead. OpenCode is read only when you open the dialog: the Providers page's automatic check leaves it out, as the file holds every sign-in OpenCode has. magpie never writes to it. ### How magpie picks an account When a subscription has several accounts on (Claude Code's or Codex's saved accounts, say), or a routing group has several members, the gateway orders them for each request in these steps. The Routing page shows the result for every request: who went first, and why. 1. **Who can answer.** The account the agent is signed in to, unless it is paused, and the other accounts that are ticked. An account set not to serve the model, or held at its usage cap, is never tried. An account whose plan doesn't list the model (a Free one behind a Plus) is tried only when no other lists it. A Claude account that has to be signed in again stays in the list, but is passed over when its turn comes, without resting. 2. **The routing.** What is left is put in order by the routing the subscription or group uses (below). 3. **Rate limited ones last**, only with *Sink* on: an account a 429 rate limited while it still had quota goes behind every one not rate limited since. 4. **The provider's fallback models** come after its own accounts. 5. **Resting ones last.** An account resting after a failure goes to the back, behind every fallback. It is never dropped: when it is the only one, it is tried anyway. 6. **The conversation's account first.** Once an account has answered a conversation, it is moved to the front for the conversation's next requests, as *Stays* says. In auto mode that is within a turn, and across turns while the last request read from the vendor's cache and wrote to it at least 1,024 tokens between them, and it answered in the last 5 minutes. A first turn reads nothing and writes the whole prompt, so the second stays to read it; a vendor that keeps a prompt cached without reporting it (OpenAI's automatic prefix cache, Gemini) is taken to have written what it was sent. This never brings back an account that is resting, nor one that has used 98% of a window (100% In order). *Stays: Strict* does, and asks no other: the conversation stays on its account, resting, spent or past its cap, and its errors go to the agent. So routing decides who goes first only for a conversation nobody has answered yet. An account that has used 90% goes behind the others for new conversations, but a conversation already on it stays there until 98%. **The routings.** Smart, Least used first and Weekly pace go by the share used of the fullest window that counts the model, the 5-hour window included. Smart and Weekly pace put an account behind the others once a window is at 90% or more, and behind those once one is at 98% or more, each of these groups ordered by the share used. - **Smart** (the default): of the accounts under 90%, the one whose allowance renews soonest goes first, because what it has left is lost at the reset. The biggest window decides: the week, then the 5 hours only when the weeks renew in the same hour. Reset times are compared to the hour, by the clock: 13:55 and 14:05 are different hours, 14:05 and 14:55 the same. An account with no week (Claude Enterprise) goes by its 5 hours. Credits that run out before their window renews, or that have no reset at all (WorkBuddy's packages, each ending on its own day), count as renewing when they run out: of several WorkBuddy accounts, the one whose credits left end first goes first. The daily Free Plan credits aren't counted, since they end at the same midnight on every account. A Claude account magpie knows nothing of yet goes first once, to learn what it has left from the answer; any other unknown account goes after those known. An account with a month beside its week (Factory) goes by its month: when the month holds less than its weeks add up to (Factory's, by one user's readings, about two weeks' worth), what a week leaves isn't lost at the week's reset, as the month still has it to spend later. Its row on the Routing page says the fullest window's share with when that window renews: "32% used · renews in 3 days" is the week's. - **In order**: the accounts in their order. The first takes every request until it is used up (100%) or fails. - **In turn**: each turn of a conversation goes to the next account. A new conversation goes to the account the fewest other conversations of the last 30 minutes are on. - **Least used first**: the lowest share used first; then the account magpie sent the fewest tokens lately (half of them stop counting after an hour). An account not known counts as unused. - **Weekly pace**: the account with the highest pace first. Pace is the share of the week left divided by the hours until the week renews (at least 1): 60% left with 20 hours to go is 3 per hour. With two weekly windows that count the model (Opus's own and the general one), the lower pace counts. An account with no week goes by its 5 hours. Accounts whose pace is within a tenth of the highest in their band count as alike, and among them the one magpie sent the fewest tokens lately goes first. A Claude account not known goes first once, as in Smart; another unknown account counts as a fresh week. The routings besides Smart are the built-in plugin `rotation` (Routing strategies in Plugins › Built-in). It stays on while a provider or group is routed by one of them. Turned off, the pickers offer Smart alone, and a provider or group still set to one of them is tried in its listed order. Your own routing groups are the built-in plugin `groups` (Routing groups in Plugins › Built-in), which stays on while you have one. The groups magpie finds for a model two providers serve don't need it. Turned off, the gateway doesn't list or serve a group of your own, and the Routing page has no New group button; `magpie groups` still lists them. **Make first** sets the accounts' order. How much that order counts depends on the routing: | Routing | What the order decides | | --- | --- | | In order | Everything: the first takes every request it can. | | In turn | Where the rotation goes next. | | Smart | Only between accounts whose windows all renew in the same hours, or whose shares used are equal. | | Least used first | Only between accounts with the same share used and the same tokens sent lately. | | Weekly pace | Only between accounts in the same pace band that magpie sent the same tokens lately, in practice none since magpie started. | For Claude Code and Codex, *Make first* also signs the agent in to that account, because the account the agent is signed in to is first. With *Keep … signed in to* on, *Make first* changes only the gateway's order, and the agent stays signed in where it is kept. **When an account runs out.** A 429 that says the allowance is used up rests the account until it is back, and the same request goes to the next candidate at once, as long as none of the reply has been sent. How long it rests comes from the first of these that is known: the reset time in the refusal (Claude Code's `usage limit reached|