Files
Claude Code's picker had 37 rows where Codex's has 18. The Claude subscription added in magpie is, while Claude Code is signed in, the account Claude Code asks on its own, and both groups are titled "Claude Code", so every model showed twice (claude-opus-5-5, then "Claude Opus 5.5 · <account> · via magpie"); and models.dev lists claude-opus-4-5 and claude-opus-4-5-20251101 (haiku-4-5, sonnet-4-5 too) as two models. The via-magpie rows stay, as some use them (the [1m] fix, failover to their other accounts): viaMagpie now knows which provider is the agent's own sign-in (not a stand-in for a signed-out Claude Code, not paused; provider.Account.StandIn is new), and Claude Code's model field marks those options Same while its own group goes straight to Anthropic (not under a base URL of the user's, not for the tiers or a WSL distro). The picker folds them into one row, "13 models via magpie · <account> · the account Claude Code is signed in to" with a chevron, folded each time the picker opens; a click or Enter opens or closes it in place, the row staying under the pointer and the page not moving; the value set stays a row of its own, and Favorites never folds. claudeDated marks a dated Claude id whose undated id is in the same group as that one's Alias (one dated id per name only; [1m] kept), and the picker shows one row for the two: the alias, which a pick writes as before, or the dated one while that is the value set, so a choice made before still shows as picked. The option lists themselves are unchanged, so Spell, Reseat, renames and profiles still find every id. New strings have zh translations. claude-picker-fold (Chromium and WebKit, en and zh, API fulfilled in memory) fails without the change in all 8 subtests and passes with it; TestClaudeDatedAlias covers the marking. model-pick, model-suffix-own, claude-direct, favorite-star, agent-models, unlisted-models and plain-names pass in both engines, click-scroll in WebKit; Chromium here only starts as the headless shell (the full Chrome for Testing bundle dies on icudtl.dat), where click-scroll fails the same way with the change undone. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui ./internal/agent ./internal/provider ./internal/gui (sandbox HOME) pass.
gui: Claude Code's model picker shows each model once (#496) — the rows magpie serves on the very Claude account Claude Code is signed in to fold into one, and models.dev's alias and dated id of one model are one row
tests: TestDriftUnwiredEveryAgent leaves WSL agents alone — it walks every agent and picks a magpie model to see the wiring come back, and on a Windows machine with a real distro running, the WSL agents in All() are the user's own: the pick wrote fake/m1 into their real ~/.codex/config.toml, ~/.claude/settings.json and ~/.pi/agent/settings.json through \wsl.localhost. Their files live in the distro, beyond the sandbox home, so the test skips them (a.WSL set). Verified on such a machine: the full ./internal/agent suite leaves the three files' hashes untouched. (#283)
agents: omp's roles pick their own models (subagents, smol, slow), keep their thinking level, and get back what they had before magpie (#325)
gui: Claude Code's model picker shows each model once (#496) — the rows magpie serves on the very Claude account Claude Code is signed in to fold into one, and models.dev's alias and dated id of one model are one row
agent: Codex's subagents can start at an effort of their own, and Claude Code's subagents can have a model of their own (#468, #469) — Codex reads [agents] default_subagent_reasoning_effort, the effort a spawned subagent starts at when the spawn names none (openai/codex core/src/agent/child_config.rs, config_toml.rs; in codex 0.159.2), and magpie never wrote it. Codex gets a subagent effort field: a square beside its subagents' one that opens the effort slider, unset said to mean the session's effort, or the subagent model's own default. Codex refuses a spawn at a level the subagents' model lacks, so only one it has is taken, and one a new subagent or main model lacks is dropped. Claude Code takes the Agent call's model, then the agent's frontmatter, then CLAUDE_CODE_SUBAGENT_MODEL, then the session's (2.1.287); magpie wrote that env only by itself, the main model while every tier followed it. It gets a subagents field, offered once routed through magpie, whose model outlives a new main model and a tier of its own; unset, the env is written as before. Claude Code has no setting for its subagents' effort beyond each agent's own frontmatter, and omp's task role already takes a thinking level, so neither changes. TestCodexSubagentEffort, TestClaudeSubagentModel and subagent-effort.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; subagent-effort, omp-roles, claude-effort, panel-effort, agent-layout, model-pick, click-scroll, old-webkit pass.
gui: Claude Code's model picker shows each model once (#496) — the rows magpie serves on the very Claude account Claude Code is signed in to fold into one, and models.dev's alias and dated id of one model are one row
agent: Claude Desktop runs on magpie in its third-party mode (#156: Claude Desktop 不会解锁 Claude Code 面板 — CC Switch 可以,咱这个支持吗?) — signed in with a Free account, Desktop's Code tab is only "Get Claude Code"; in its 3p deployment mode it needs no Anthropic sign-in and Code and Cowork run on the gateway. A Claude Desktop agent whose provider field switches it: on writes deploymentMode "3p" into Claude/ and Claude-3p/claude_desktop_config.json, a magpie profile in Claude-3p/configLibrary/<id>.json (inferenceProvider gateway, inferenceGatewayBaseUrl the gateway's address, inferenceGatewayApiKey magpie-claude-desktop, inferenceGatewayAuthScheme bearer; disableDeploymentModeChooser and coworkEgressAllowedHosts ["*"] only where unset) and its entry in _meta.json, applied — the keys and files CC Switch writes (claude_desktop_config.rs). No inferenceModels: Desktop lists /v1/models, which the gateway already gives it by ids it keeps. Off puts each deploymentMode back as it was (gone where there was none), the profile applied before applied again, magpie's profile and entry removed, and files and folders magpie made gone if nothing else went in; other keys and profiles are never touched. Folders as CC Switch finds them: ~/Library/Application Support, %LOCALAPPDATA% (a Claude… folder there if not Claude itself), $XDG_CONFIG_HOME. Desktop reads them at start-up, which the picker and notice say. The Library keeps giving Desktop's MCP servers to %APPDATA%'s file on Windows, listed once. Tested in a sandbox home: fresh, with the user's settings and 3p profiles (off restores every byte), on twice, on → off → on, drift, odd files refused, each system's folders; and against Claude Desktop 2.9939.2 on Windows 11 (winget): on, it started in 3P mode (provider gateway, apiHost the gateway) with the Code tab and no sign-in, and asked the gateway's /v1/models; off, it went back to claude.ai
claude: Claude Code is told what each magpie model can do, so ultracode works on one with xhigh (#430) — Claude Code run on a gateway (CLAUDE_CODE_USE_GATEWAY, as Claude Desktop's Code tab runs it) takes no model it doesn't know for one with effort, xhigh or max, and "ultracode": true needs xhigh, so on group/…, provider/model and mythos-magpie-<number> it was silently off. While Claude Code is routed through magpie, settings.json's env now has CLAUDE_CODE_MODEL_CAPABILITIES: <model>=effort[,xhigh_effort][,max_effort] for each magpie model with reasoning levels, only the levels magpie knows it has (a Claude model's no more than Claude Code gives it, a model of no known levels not named), lowercased exact ids, the models Claude Code is set to first and the whole within 8000 characters; while Claude Desktop runs on magpie, its mythos-magpie-<number> ids too. It is rewritten as the catalog changes and when Desktop is put on or off magpie, and goes when Claude Code is taken off magpie; a value the user set is left as it is. Prompts and headers are unchanged. TestClaudeModelCapabilities and TestClaudeCapabilitiesSync fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; in a sandbox HOME, claude 2.1.286 -p '/effort ultracode' on the settings.json magpie wrote, in gateway mode: group/auto-deep (xhigh) and its Desktop alias are on, group/auto-mid (no xhigh) and the same settings without the env are not.
agents: Codex signed in with only an API key gets magpie as its provider, its picker listing magpie's models (#322, dakango: switching the Codex app to one of its own models went to OpenAI, 502) — magpie counted an API key in auth.json as a ChatGPT sign-in and only set openai_base_url, counting on Codex to ask the gateway for its models; but Codex asks for none with an API key (models-manager refresh_available_models returns early), so the picker showed Codex's built-in models and the one magpie model, and a built-in one went to OpenAI with the relay's key. Now only a ChatGPT sign-in takes that route; with an API key alone magpie is the model_provider with model_catalog_json listing every magpie model on for Codex, as when signed out, and a sync moves a setup an older magpie wrote; going back to Codex's own model restores the user's model_provider. A Codex model whose OpenAI can't be reached now fails naming the model and what to do instead of a bare 502. TestCodexAPIKeyUsesProvider, TestCodexAPIKeyBaseURLMoved and TestCodexOwnModelUnreached fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
agents: Pi's models go to PI_CODING_AGENT_DIR (#304), and the Codex app offers magpie's models' efforts (#310) — Pi keeps settings.json and models.json in PI_CODING_AGENT_DIR when set (~ expanded; @earendil-works/pi-coding-agent config.js), and magpie wrote ~/.pi/agent regardless; omp is read by its own rules (PI_CONFIG_DIR, OMP_PROFILE/PI_PROFILE, then PI_CODING_AGENT_DIR), and the library's Pi and omp targets follow. The Codex app's model picker offers only the efforts in [desktop] enabled-reasoning-efforts of config.toml (ChatGPT.app 26.924: default low…xhigh, ultra, persistent), so a magpie model's max or minimal was never offered; routed through magpie, the efforts its models take are added to the user's list (or the default), nothing else changed, only values the app knows (it drops the setting for any other), comments kept, nothing written when nothing is missing. TestPiAgentDir, TestOmpAgentDir and TestCodexDesktopEfforts fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
toml: Codex's config.toml with a multi-line array (notify = [ … ]) is edited right, and an edit that fails leaves every file as it was (#253 kerwinliu00001: "edited TOML is invalid: line 2, column 1: incomplete number", config.toml left with model_provider gone and [model_providers.magpie] added but no model) — the top-level key edits (GetTOMLTop, SetTOMLTop, DelTOMLTop) went line by line with regexes: a new key went after the last line that looked like
key = …, which for notify = [ was inside the array; key = value lines inside a multi-line string were read as keys and a line starting with [ as a table; replacing or deleting a multi-line value touched only its first line. They now locate the top-level keys with the TOML parser, as the table edits already did, each with its value's whole range however many lines it takes (parseTOMLFile, the unnamed root table beside the tables); SetTOMLTop finds all its keys in the file as read and writes once. A file the parser refuses is still read line by line, so a broken config keeps showing its model, and writes to it are refused untouched. And a pick is several writes (Codex: drop model_provider, add magpie's provider table, set openai_base_url and model), each checked, so one failing left the earlier ones on disk: edit.Atomically(fn, paths…) puts each file back to its bytes and mode, or removes one it created, when fn fails, and every field set and Sync of Codex (config.toml and magpie-models.json, so Codex in WSL too), Kimi and Grok goes through it, as do Codex's MCP server writes (putCodex, delCodex). Tested in temp dirs and a temp HOME: the reporter's file read, set and deleted directly and through Codex's model field (signed in, with model_provider and model above the array, with magpie as the provider), the array's text kept and the TOML valid, and back to a Codex model; nested arrays, trailing commas, comments in arrays, [x] and a = b in strings, an inline table in an array; """ and ''' strings holding model = … and [table] lines not read as keys, new keys after their closing quotes, one deleted whole; a forced failure of Codex's last write leaving config.toml byte-identical; TestAtomically. They fail on main (the Codex ones with the reporter's error). go vet ./internal/..., go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried against a real ~/.codex, in WSL or in the GUI; multi-line inline tables (TOML 1.1) are refused by the parser and so left untouched with an error.
agent: Codex's subagents can start at an effort of their own, and Claude Code's subagents can have a model of their own (#468, #469) — Codex reads [agents] default_subagent_reasoning_effort, the effort a spawned subagent starts at when the spawn names none (openai/codex core/src/agent/child_config.rs, config_toml.rs; in codex 0.159.2), and magpie never wrote it. Codex gets a subagent effort field: a square beside its subagents' one that opens the effort slider, unset said to mean the session's effort, or the subagent model's own default. Codex refuses a spawn at a level the subagents' model lacks, so only one it has is taken, and one a new subagent or main model lacks is dropped. Claude Code takes the Agent call's model, then the agent's frontmatter, then CLAUDE_CODE_SUBAGENT_MODEL, then the session's (2.1.287); magpie wrote that env only by itself, the main model while every tier followed it. It gets a subagents field, offered once routed through magpie, whose model outlives a new main model and a tier of its own; unset, the env is written as before. Claude Code has no setting for its subagents' effort beyond each agent's own frontmatter, and omp's task role already takes a thinking level, so neither changes. TestCodexSubagentEffort, TestClaudeSubagentModel and subagent-effort.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; subagent-effort, omp-roles, claude-effort, panel-effort, agent-layout, model-pick, click-scroll, old-webkit pass.
codex: with magpie as Codex's provider, openai_base_url points at magpie as well, so a thread started on Codex's built-in provider and reopened sends a magpie model to magpie, not to ChatGPT's backend (#259 isymbo: ChatGPT Desktop, "The 'xiaomi/mimo-v2.6-pro' model is not supported when using Codex with a ChatGPT account")
agent: Codex's subagents can start at an effort of their own, and Claude Code's subagents can have a model of their own (#468, #469) — Codex reads [agents] default_subagent_reasoning_effort, the effort a spawned subagent starts at when the spawn names none (openai/codex core/src/agent/child_config.rs, config_toml.rs; in codex 0.159.2), and magpie never wrote it. Codex gets a subagent effort field: a square beside its subagents' one that opens the effort slider, unset said to mean the session's effort, or the subagent model's own default. Codex refuses a spawn at a level the subagents' model lacks, so only one it has is taken, and one a new subagent or main model lacks is dropped. Claude Code takes the Agent call's model, then the agent's frontmatter, then CLAUDE_CODE_SUBAGENT_MODEL, then the session's (2.1.287); magpie wrote that env only by itself, the main model while every tier followed it. It gets a subagents field, offered once routed through magpie, whose model outlives a new main model and a tier of its own; unset, the env is written as before. Claude Code has no setting for its subagents' effort beyond each agent's own frontmatter, and omp's task role already takes a thinking level, so neither changes. TestCodexSubagentEffort, TestClaudeSubagentModel and subagent-effort.test.cjs fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; subagent-effort, omp-roles, claude-effort, panel-effort, agent-layout, model-pick, click-scroll, old-webkit pass.
agents: Factory's Droid CLI is an agent magpie sets up (#242 tasselx) — droid keeps its settings in ~/.factory/settings.json ($FACTORY_HOME_OVERRIDE/.factory when set), its BYOK models the customModels array ({model, displayName, baseUrl, apiKey, provider "anthropic" | "openai" (Responses) | "generic-chat-completion-api", maxOutputTokens, noImageSupport …}), picked as "custom:<id>". Picking a catalog model through magpie appends one entry per catalog model after the user's own (so their ids never move), each with an explicit id "custom:magpie/<provider>/<model>", the catalog's label as displayName, the gateway and token
magpie, its context (maxContextLimit) and output when known, noImageSupport always written (droid takes a Chat Completions model as blind otherwise), and the API its provider speaks natively so the gateway relays it as it is: Responses → "openai" at /v1, Messages → "anthropic" at the gateway root (Anthropic's SDK adds /v1), anything else "generic-chat-completion-api" at /v1; and sets sessionDefaultSettings.model. The model shows in magpie as magpie/<ref>; the user's sessionDefaultSettings.model is stashed and put back on reset (a top-level "model", the docs' spelling, is left alone and is what droid falls back to), magpie's entries go and an emptied customModels or sessionDefaultSettings with them; everything else in the file, the user's entries included, is kept byte for byte. The picker offers the user's own custom models by the id droid gives them (its "id", else the displayName with whitespace as dashes and a count of the same name before it), including the legacy ~/.factory/config.json custom_models, which magpie never writes. Sync rewrites magpie's entries as providers change; Check reports a missing entry or a changed baseUrl/apiKey; its requests are Droid's in usage (User-Agent factory-cli/<version>). Icon is Factory's own favicon glyph (factory.ai/favicon.svg); lobehub has none. From the docs: docs.factory.ai/cli/byok/overview (customModels fields, the three providers, config.json legacy snake_case merged under settings.json), /cli/configuration/settings (model, sessionDefaultSettings.*), /droid-exec/overview (custom:<Display-Name>-<index>). Verified by reading the shipped droid 0.229.0 bundle (@factory/cli-linux-x64, unpacked, never installed or run): the zod schema of an entry (also id, index, maxContextLimit, enableThinking, reasoningEffort, bedrock-converse), ids as custom:${displayName.trim().replace(/\s+/g,"-")}-${n} with n counting equal names, an entry's own id winning, the lookup by id, top-level model/reasoningEffort/specModeModel moved into sessionDefaultSettings only when unset there, getModel reading sessionDefaultSettings.model, noImageSupport defaulting by provider, FACTORY_HOME_OVERRIDE, and BYOK requests carrying factory-cli/<version>. Tested in a sandbox HOME (internal/agent/droid_test.go): set/sync/reset on a settings.json as droid leaves it with a Groq BYOK model (its entry byte-identical, the file restored exactly), each protocol's provider and baseUrl, a moved baseUrl caught by Check and healed by Sync, picking the user's own model while on magpie's, no file at all, a top-level model, FACTORY_HOME_OVERRIDE and legacy config.json ids (custom:Local-Qwen-0/-1) offered and never written. go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass (TestClaudeSignedOut and TestClaudeAccountNamedAsItsLogin flaked under load and pass alone). Not tried with a real droid: whether droid's /model lists the entries, that it reaches the gateway on each protocol, and how it sends thinking for a custom model (magpie sets no reasoningEffort/enableThinking) are unverified.
agents: Droid, Qoder, OpenHanako, WorkBuddy, OpenCode and ZCode no longer get an output limit above the model's context window (#347)
agents: dsh's patch list written in flow style is edited like a block one, and a file magpie can't edit is named as itself (#445) — a profile's cordis.patch.yml whose top-level array was written as [ { id: some-plugin, disabled: false } ] (valid YAML, and what dsh's own writers keep the template's [] as once they add a row) failed every model switch with "config.yaml is not a list of entries magpie can edit": the patch-list reader took only '- ' lines, and the error named config.yaml whatever the file; since web's list goes first, one such profile stopped all of them. New edit.BlockList reads a file whose one document is a flow sequence as the same list in block style (nested mappings and lists in block style, scalars quoted and tagged as they were, the lines before it and the comments after it kept); dsh's model/effort wiring and its MCP servers (internal/library) read and write such a file through it, and its errors name the real path. dshProfiles puts web first by partition instead of a sort whose less wasn't a strict weak order. TestDshFlowPatchList (agent, library) fails without the change; TestBlockList covers one-line, folded/nested/!!js, empty, CRLF and what isn't a flow list. Sandbox HOME: magpie dsh deepseek-v4-pro on main fails with the config.yaml error, with the change both profiles get the model and web keeps some-plugin. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
dsh: a model through magpie that lists no thinking levels offers none, rather than dsh's own four it then turned away (Dazzle-sys, #473: on LongCat-2.5-Preview, every step of DeepSeek Harness's thinking slider failed with "takes an effort of for this model, not \"low\"") — the Agents page's effort field fell back to dsh's own off, low, high and max when a gateway model's catalog entry listed no levels, while setting one checked the model's own levels, which were none, so the slider offered four stops and each was refused with an empty list. Such a model has no levels in dsh either (its route gives it no reasoningEfforts), so none is offered now and the Agents page shows no thinking control for it, as it does for Claude Code on Haiku 4.5; an effort asked for anyway (magpie dsh effort low) says the model has no thinking levels in magpie, never an empty list. dsh's own models and gateway models with levels are unchanged; no other agent's effort field offers levels its setter refuses. TestDshEffortModelWithoutLevels fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; panel-effort, claude-effort, click-scroll, old-webkit pass.
dsh: a model through magpie that lists no thinking levels offers none, rather than dsh's own four it then turned away (Dazzle-sys, #473: on LongCat-2.5-Preview, every step of DeepSeek Harness's thinking slider failed with "takes an effort of for this model, not \"low\"") — the Agents page's effort field fell back to dsh's own off, low, high and max when a gateway model's catalog entry listed no levels, while setting one checked the model's own levels, which were none, so the slider offered four stops and each was refused with an empty list. Such a model has no levels in dsh either (its route gives it no reasoningEfforts), so none is offered now and the Agents page shows no thinking control for it, as it does for Claude Code on Haiku 4.5; an effort asked for anyway (magpie dsh effort low) says the model has no thinking levels in magpie, never an empty list. dsh's own models and gateway models with levels are unchanged; no other agent's effort field offers levels its setter refuses. TestDshEffortModelWithoutLevels fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; panel-effort, claude-effort, click-scroll, old-webkit pass.
agents: "Hide all" in an agent's model list keeps its line, and an uninstalled Gemini CLI goes from the Agents page (#356, #230) — the line under an agent ("Showing 0 / N models") came only when a picker still listed one of magpie's models, so hiding them all took away the one way back; it now stays while any is hidden. Gemini CLI was taken as present from what it leaves in ~/.gemini (settings.json, .env, installation_id…), which stays after it is uninstalled, so its row stayed, offering to set up a CLI that can't run; it is now its gemini binary. TestAgentModelLineAllHidden and TestGeminiNotAntigravity fail without the change; agent-models-all-hidden.test.cjs (Chromium, WebKit, en/zh) added. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/agent ./internal/gui ./internal/library ./internal/provider pass; agent-models, click-scroll, old-webkit pass.
toml: Codex's config.toml with a multi-line array (notify = [ … ]) is edited right, and an edit that fails leaves every file as it was (#253 kerwinliu00001: "edited TOML is invalid: line 2, column 1: incomplete number", config.toml left with model_provider gone and [model_providers.magpie] added but no model) — the top-level key edits (GetTOMLTop, SetTOMLTop, DelTOMLTop) went line by line with regexes: a new key went after the last line that looked like
key = …, which for notify = [ was inside the array; key = value lines inside a multi-line string were read as keys and a line starting with [ as a table; replacing or deleting a multi-line value touched only its first line. They now locate the top-level keys with the TOML parser, as the table edits already did, each with its value's whole range however many lines it takes (parseTOMLFile, the unnamed root table beside the tables); SetTOMLTop finds all its keys in the file as read and writes once. A file the parser refuses is still read line by line, so a broken config keeps showing its model, and writes to it are refused untouched. And a pick is several writes (Codex: drop model_provider, add magpie's provider table, set openai_base_url and model), each checked, so one failing left the earlier ones on disk: edit.Atomically(fn, paths…) puts each file back to its bytes and mode, or removes one it created, when fn fails, and every field set and Sync of Codex (config.toml and magpie-models.json, so Codex in WSL too), Kimi and Grok goes through it, as do Codex's MCP server writes (putCodex, delCodex). Tested in temp dirs and a temp HOME: the reporter's file read, set and deleted directly and through Codex's model field (signed in, with model_provider and model above the array, with magpie as the provider), the array's text kept and the TOML valid, and back to a Codex model; nested arrays, trailing commas, comments in arrays, [x] and a = b in strings, an inline table in an array; """ and ''' strings holding model = … and [table] lines not read as keys, new keys after their closing quotes, one deleted whole; a forced failure of Codex's last write leaving config.toml byte-identical; TestAtomically. They fail on main (the Codex ones with the reporter's error). go vet ./internal/..., go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried against a real ~/.codex, in WSL or in the GUI; multi-line inline tables (TOML 1.1) are refused by the parser and so left untouched with an error.
agents: Droid, Qoder, OpenHanako, WorkBuddy, OpenCode and ZCode no longer get an output limit above the model's context window (#347)
provider: Antigravity's models at several levels are one model with those levels (#150: 统一 Antigravity 模型变体与客户端推理档位的映射) — Antigravity lists gemini-3.7-flash-low, -medium and -high as three models, "Gemini 3.7 Flash (Low)" …, so a client picked a level twice: once in the model, once as its reasoning effort, with no telling which won. Now ids that are one id with an effort word after it (-low, -medium, -high, -extra-low, -minimal, -xhigh) are one model, gemini-3.7-flash "Gemini 3.7 Flash", declaring only the levels there are (low,medium,high; Gemini 3.1 Pro low,high), so Codex, Pi and the rest get reasoning levels for it. The level a variant is at is what Antigravity's name says where each name says a different one (gemini-3.5-flash-extra-low is "(Low)", gemini-3.5-flash-low "(Medium)"), else its id's word; a model only named like another (gemini-3-flash-agent, "Gemini 3.5 Flash (High)"; gemini-pro-agent) or with no sibling (gpt-oss-120b-medium) stays as listed. A request asks for the variant the effort picks, an effort it lacks at the nearest level (xhigh is high; medium on low/high is high); with none asked, the id without a level if Antigravity has one, else high — what Gemini 3 does unasked — else the highest. A variant's request carries no thinkingLevel, so the id and the level can't disagree. An old variant id still works: as named with no effort, and moved to the effort asked for. Saved picks and agents set to a variant (antigravity/gemini-3.7-flash-high) are moved to the family, at that level where the agent's own is unset, as Cursor's are. For Gemini 3 models without variants, medium now goes as medium to Flash (which takes minimal/low/medium/high) and still as high to Pro (low/high). Tested against the list an account's fetchAvailableModels gave (sandbox cache)
agent: MiniMax Code (#362) — MiniMax's mcode CLI (and its desktop app, same runtime) reads $MINIMAX_DATA_DIR/config.yaml, else ~/.minimax/config.yaml. Choosing a magpie model adds custom_provider.magpie there (the gateway, spoken to as Anthropic messages, with the catalog's models, efforts, limits and image support) and sets defaultModel to custom_provider:magpie/<model>; the user's own default and variant are kept and put back on reset, their comments, providers and keys on magpie's entry stay, and an emptied custom_provider goes. The Library installs skills into its skills folder; it reads no user-wide MCP file, so it gets no MCP target. Checked against @minimax-ai/code 0.5.10 in a sandbox HOME: mcode provider list shows magpie's entry and mcode exec reaches a fake upstream through it with magpie's key, model and effort; after a reset it loads the restored file. TestMiniMaxCode, TestMiniMaxCodeSync, TestMiniMaxCodeLeavesNoEmptyProviders, TestMiniMaxCodeDataDir and TestMiniMaxCodeSkills fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestUserPath and the Claude account tests pass alone).
agents: a command WSL finds on a Windows drive is not the distro's (#406) — WSL appends Windows' PATH, so command -v pi in Ubuntu found /mnt/c/Users/<u>/AppData/Roaming/npm/pi, the Windows npm shim, and magpie listed "Pi · WSL Ubuntu-24.04" and kept bin:pi in wsl.json. The probe now says where each command is and where /proc/mounts puts Windows' drives (/mnt/<letter>/ when it can't), and a command on one is not taken as installed in the distro, for Codex, Pi, OmO and Claude Code alike; one inside the distro, or its config directory, still is. A distro's findings carry the probe that made them, and a command found by an older probe is forgotten on load, so a stale bin:pi goes without the distro running. TestWSLWindowsCommandIsNotTheDistros, TestWSLOldProbeBinsForgotten and TestWSLRunningStaleWindowsPiReprobed fail without the change; TestWSLProbeScriptSaysWhere runs the script under sh. On a real WSL2 distro the probe said win:/mnt/c and win:/mnt/d. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
agent: OmO (oh-my-openagent, omo-ai), a fork of Pi that keeps its own ~/.omo/agent, gets its own row: a magpie model sets settings.json's defaultProvider/defaultModel and magpie's provider in models.json there as Pi's does, $OMO_CODING_AGENT_DIR / $SENPI_CODING_AGENT_DIR are honoured, the Library writes its AGENTS.md, mcp.json and skills, and
omo update updates it (#264 rhyme227). Pi's own ~/.pi is left alone. piIn becomes piLike(at, id, name, dir) so both share one wiring. Tested with omo_test.go in agent and library (fail without the change); go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows), go test -tags nogui ./... pass.
agents: OpenChamber gets its own row, its model and small model set to magpie's (#321) — OpenChamber keeps its own default model (preferences.json, a copy in settings.json, under $OPENCHAMBER_DATA_DIR or ~/.config/openchamber) and sends it with every session over OpenCode's, so setting OpenCode's model changed nothing it used. Its row writes defaultModel (a fresh updatedAt, defaultVariant cleared as its own settings do) and the small-model override, keeping every other key and refusing a preferences.json it can't read; magpie's provider goes into the OpenCode config it runs on, and stays there while either uses it. OpenCode's config now follows $OPENCODE_CONFIG_DIR: the OpenCode 2 OpenChamber bundles reads only that folder, so what magpie wrote to ~/.config/opencode was never read; OpenCode 1 reads it last, so it wins there too. A project with its own model in OpenChamber is named in the row. Checked against @openchamber/web 2.0.4 on OpenCode 2.0.18 in a sandbox HOME: its settings API returned magpie's model and OpenCode listed magpie's provider. TestOpenCodeConfigDir, TestOpenChamberModel and TestOpenChamberSettingsOnly fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; click-scroll, old-webkit pass.
agents: OpenChamber gets its own row, its model and small model set to magpie's (#321) — OpenChamber keeps its own default model (preferences.json, a copy in settings.json, under $OPENCHAMBER_DATA_DIR or ~/.config/openchamber) and sends it with every session over OpenCode's, so setting OpenCode's model changed nothing it used. Its row writes defaultModel (a fresh updatedAt, defaultVariant cleared as its own settings do) and the small-model override, keeping every other key and refusing a preferences.json it can't read; magpie's provider goes into the OpenCode config it runs on, and stays there while either uses it. OpenCode's config now follows $OPENCODE_CONFIG_DIR: the OpenCode 2 OpenChamber bundles reads only that folder, so what magpie wrote to ~/.config/opencode was never read; OpenCode 1 reads it last, so it wins there too. A project with its own model in OpenChamber is named in the row. Checked against @openchamber/web 2.0.4 on OpenCode 2.0.18 in a sandbox HOME: its settings API returned magpie's model and OpenCode listed magpie's provider. TestOpenCodeConfigDir, TestOpenChamberModel and TestOpenChamberSettingsOnly fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; click-scroll, old-webkit pass.
agent: Pi starts on the model magpie picks, and asks a Messages-only provider's models on Messages (#160 Aimer779; Cleary on Discord)
agents: Pi's models go to PI_CODING_AGENT_DIR (#304), and the Codex app offers magpie's models' efforts (#310) — Pi keeps settings.json and models.json in PI_CODING_AGENT_DIR when set (~ expanded; @earendil-works/pi-coding-agent config.js), and magpie wrote ~/.pi/agent regardless; omp is read by its own rules (PI_CONFIG_DIR, OMP_PROFILE/PI_PROFILE, then PI_CODING_AGENT_DIR), and the library's Pi and omp targets follow. The Codex app's model picker offers only the efforts in [desktop] enabled-reasoning-efforts of config.toml (ChatGPT.app 26.924: default low…xhigh, ultra, persistent), so a magpie model's max or minimal was never offered; routed through magpie, the efforts its models take are added to the user's list (or the default), nothing else changed, only values the app knows (it drops the setting for any other), comments kept, nothing written when nothing is missing. TestPiAgentDir, TestOmpAgentDir and TestCodexDesktopEfforts fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
agent: Pi starts on the model magpie picks, and asks a Messages-only provider's models on Messages (#160 Aimer779; Cleary on Discord)
pi: a model's thinkingLevelMap in models.json names every one of Pi's levels, the model's own mapped to its effort and the rest null, so Pi's /thinking offers only what the vendor takes (#243) — magpie wrote only xhigh and max (glm-5.3-flash at low/high/max got {"max":"max"}), and Pi offers every level a map leaves out but xhigh and max, so minimal and medium were offered and sent efforts the vendor turned away. Now glm-5.3-flash gets {"off":null,"minimal":null,"low":"low","medium":null,"high":"high","xhigh":null,"max":"max"}. Pi's off (read from pi-ai 0.99's getSupportedThinkingLevels and its openai-completions, openai-responses and anthropic-messages streams) maps to none for a model that takes none, so off really turns reasoning off; a model without none has off null, since Pi's off asks a Responses model for effort none and leaves a Chat model on the vendor's default; on Anthropic's Messages API off is left out, as before, since there Pi's off sends thinking disabled, which Claude takes, and Claude keeps its xhigh and max. A model whose levels magpie doesn't know still gets no map and reasoning false. Tested with TestPiThinkingLevels (full maps with and without none, Anthropic keeping off, none for unknown) and TestOpenCodeNoResellerLevelsForSwitchModel, which now checks the full map written for a relay's low/high/max model and no map for an unknown one (fails on main with map[max:max]). go vet ./internal/agent, go build -tags nogui (and GOOS=linux), GOOS=windows go build and go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked, TestCodexVersion and TestAddSecondOfPreset failed in the full run under load and pass alone). Not tried in a real Pi session.
pi: changing the thinking level writes only that level, and only when it changed — with the model on magpie it rebuilt providers.magpie, so a contextWindow (or any other field) edited by hand was replaced. Default still clears defaultThinkingLevel and leaves the model and the provider where they are. Picking a model, and the catalog sync, still refresh the provider. OmO shares this path. (#285)
agent: Pi starts on the model magpie picks, and asks a Messages-only provider's models on Messages (#160 Aimer779; Cleary on Discord)
agents: Droid, Qoder, OpenHanako, WorkBuddy, OpenCode and ZCode no longer get an output limit above the model's context window (#347)
agents: omp's roles pick their own models (subagents, smol, slow), keep their thinking level, and get back what they had before magpie (#325)
gui: Claude Code's model picker shows each model once (#496) — the rows magpie serves on the very Claude account Claude Code is signed in to fold into one, and models.dev's alias and dated id of one model are one row
models: Provider in model names has a third way, a name the user gave used just as they wrote it (#92 marsxxl: v0.1.591 的 Codex 下拉里自定义名称又带上了 · WorkBuddy / · Claude Code) —
559ce021 (#203) had a custom name keep " · <provider>" after it like any other, so "Opus 5.5" read "Opus 5.5 · Claude Code" in every list Labels feeds (Codex's model_catalog_json and the gateway's Codex /models, Pi's, OpenCode's, ZCode's, OMP's, Muse's), while #92 had been promised the name as written. Both are kept: the setting is now Off / Not on names I set (自定义名称不带供应商) / On. On stays the default and keeps #203's behaviour, so nobody's list changes unasked; own leaves a name the user gave as it is and a vendor's name with its provider's; off is #335's plain names. Stored as plainOwnNames beside plainNames, so a settings.json of before reads as it did (plainNames: true is off). POST /api/settings/plain-names takes mode on|own|off (a body of on alone still works), magpie model suffix on|own|off, a Settings save keeps it, and /v1/models' magpie_label now follows the setting through Labels as the agents' lists do. The row's hint is one line that fits (en, zh), and its zh name says 供应商 as the rest of the app. Prompts and headers are unchanged. TestSuffixMode, TestSuffixModesReachAgentFiles (Codex model_catalog_json and Pi models.json), TestModelsListSuffixModes (/v1/models magpie_label) and model-suffix-own.test.cjs (Chromium and WebKit, en and zh) fail without the change; TestModelName and TestModelNameAndLevelsReachAgentFiles keep #203's assertions. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (TestConfiguredModelsAdvertiseImageInput load-flaky, passes alone); plain-names, click-scroll, old-webkit pass.
agents: Droid, Qoder, OpenHanako, WorkBuddy, OpenCode and ZCode no longer get an output limit above the model's context window (#347)
agents: each agent's CLI shows its version and updates in one click (#202 JoeyMa-zh: Agents 页面增加"更新 CLI"功能 — 检查各 agent CLI 的版本并一键更新) — the Agents page now puts each CLI's installed version after its name (read with
<cli> --version, 10s timeout, no stdin) and, when a newer one is out and magpie can tell how it was installed, a small "Update to x.y.z" pill. How it was installed comes from the resolved binary: the vendor's own installer first (Claude Code's ~/.local/share/claude/versions → claude update, Codex standalone → codex update, ~/.opencode/bin → opencode upgrade), then a global node_modules (bun, pnpm, else npm, run as npm install -g --prefix <prefix> pkg@latest with that prefix's npm, and only where the prefix is writable), then a Homebrew formula or cask allowlisted per agent (brew upgrade [--cask]); anything else shows the version alone, with no button. The latest comes from the npm registry or brew info --json=v2 (no auto-update), cached 6h (15min after a failure); installed versions are cached by the binary's path, size and mtime. GET /api/agents/cli waits at most 3s and says pending, and the page asks again; POST /api/agents/cli/{id} runs the update (one at a time per agent, 10min cap), reads the version again and says so if it did not change, or gives the command's last lines when it fails. The pill goes busy in place and the page never scrolls; not in the tray panel. zh strings added. Tested with a new internal/agent/cliupdate_test.go (version order and parsing; npm/bun/pnpm/brew formula and cask/native installs, a read-only prefix, a local node_modules and unknown binaries in a fake PATH; an update against a fake registry and updater) and a new internal/gui/tests/cli-update.test.cjs in Chromium and WebKit (versions, pills only where magpie can update, row heights, an update with the view scrolled, a failing one, zh). No real update was run. go build (nogui, GOOS=windows, GOOS=linux), go test -tags nogui ./... and the GUI tests pass
agents: each agent's CLI shows its version and updates in one click (#202 JoeyMa-zh: Agents 页面增加"更新 CLI"功能 — 检查各 agent CLI 的版本并一键更新) — the Agents page now puts each CLI's installed version after its name (read with
<cli> --version, 10s timeout, no stdin) and, when a newer one is out and magpie can tell how it was installed, a small "Update to x.y.z" pill. How it was installed comes from the resolved binary: the vendor's own installer first (Claude Code's ~/.local/share/claude/versions → claude update, Codex standalone → codex update, ~/.opencode/bin → opencode upgrade), then a global node_modules (bun, pnpm, else npm, run as npm install -g --prefix <prefix> pkg@latest with that prefix's npm, and only where the prefix is writable), then a Homebrew formula or cask allowlisted per agent (brew upgrade [--cask]); anything else shows the version alone, with no button. The latest comes from the npm registry or brew info --json=v2 (no auto-update), cached 6h (15min after a failure); installed versions are cached by the binary's path, size and mtime. GET /api/agents/cli waits at most 3s and says pending, and the page asks again; POST /api/agents/cli/{id} runs the update (one at a time per agent, 10min cap), reads the version again and says so if it did not change, or gives the command's last lines when it fails. The pill goes busy in place and the page never scrolls; not in the tray panel. zh strings added. Tested with a new internal/agent/cliupdate_test.go (version order and parsing; npm/bun/pnpm/brew formula and cask/native installs, a read-only prefix, a local node_modules and unknown binaries in a fake PATH; an update against a fake registry and updater) and a new internal/gui/tests/cli-update.test.cjs in Chromium and WebKit (versions, pills only where magpie can update, row heights, an update with the view scrolled, a failing one, zh). No real update was run. go build (nogui, GOOS=windows, GOOS=linux), go test -tags nogui ./... and the GUI tests pass
agents: a command WSL finds on a Windows drive is not the distro's (#406) — WSL appends Windows' PATH, so command -v pi in Ubuntu found /mnt/c/Users/<u>/AppData/Roaming/npm/pi, the Windows npm shim, and magpie listed "Pi · WSL Ubuntu-24.04" and kept bin:pi in wsl.json. The probe now says where each command is and where /proc/mounts puts Windows' drives (/mnt/<letter>/ when it can't), and a command on one is not taken as installed in the distro, for Codex, Pi, OmO and Claude Code alike; one inside the distro, or its config directory, still is. A distro's findings carry the probe that made them, and a command found by an older probe is forgotten on load, so a stale bin:pi goes without the distro running. TestWSLWindowsCommandIsNotTheDistros, TestWSLOldProbeBinsForgotten and TestWSLRunningStaleWindowsPiReprobed fail without the change; TestWSLProbeScriptSaysWhere runs the script under sh. On a real WSL2 distro the probe said win:/mnt/c and win:/mnt/d. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
library: agents in a running WSL distro take skills, MCP servers and instructions (#331) — the Library knew only this machine's agent ids, so codex@wsl:Ubuntu and the like were never offered, and the id's colon was refused as an agent's name and can't be in a Windows file name. Claude Code, Codex, Pi and OmO in a distro now get them at their default places under the distro's $HOME through \\wsl.localhost, only while it runs (opening a stopped one's files starts it; its state is kept and asking for it says to start it). Skills go in as copies, made beside and renamed into place and made again when the library's changes, since a Linux agent can't follow a link to a Windows folder (project skills share the same copy code); an MCP server that runs a Windows program (C:\…, *.exe/.cmd/.bat/.ps1) is refused for them with a message; Pi there gets no MCP servers, where its version decides the file. The id's colon is a dot in file and folder names, and setup export/import turns it back. TestWSLTargets, TestWSLSkillsCopied, TestWSLServers and TestWSLInstructions (a fake distro) fail without the change. go vet (and GOOS=windows for agent, library), go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. Not run against a real distro.
agents: a command WSL finds on a Windows drive is not the distro's (#406) — WSL appends Windows' PATH, so command -v pi in Ubuntu found /mnt/c/Users/<u>/AppData/Roaming/npm/pi, the Windows npm shim, and magpie listed "Pi · WSL Ubuntu-24.04" and kept bin:pi in wsl.json. The probe now says where each command is and where /proc/mounts puts Windows' drives (/mnt/<letter>/ when it can't), and a command on one is not taken as installed in the distro, for Codex, Pi, OmO and Claude Code alike; one inside the distro, or its config directory, still is. A distro's findings carry the probe that made them, and a command found by an older probe is forgotten on load, so a stale bin:pi goes without the distro running. TestWSLWindowsCommandIsNotTheDistros, TestWSLOldProbeBinsForgotten and TestWSLRunningStaleWindowsPiReprobed fail without the change; TestWSLProbeScriptSaysWhere runs the script under sh. On a real WSL2 distro the probe said win:/mnt/c and win:/mnt/d. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
agents: a command WSL finds on a Windows drive is not the distro's (#406) — WSL appends Windows' PATH, so command -v pi in Ubuntu found /mnt/c/Users/<u>/AppData/Roaming/npm/pi, the Windows npm shim, and magpie listed "Pi · WSL Ubuntu-24.04" and kept bin:pi in wsl.json. The probe now says where each command is and where /proc/mounts puts Windows' drives (/mnt/<letter>/ when it can't), and a command on one is not taken as installed in the distro, for Codex, Pi, OmO and Claude Code alike; one inside the distro, or its config directory, still is. A distro's findings carry the probe that made them, and a command found by an older probe is forgotten on load, so a stale bin:pi goes without the distro running. TestWSLWindowsCommandIsNotTheDistros, TestWSLOldProbeBinsForgotten and TestWSLRunningStaleWindowsPiReprobed fail without the change; TestWSLProbeScriptSaysWhere runs the script under sh. On a real WSL2 distro the probe said win:/mnt/c and win:/mnt/d. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass.
gui: Claude Code's model picker shows each model once (#496) — the rows magpie serves on the very Claude account Claude Code is signed in to fold into one, and models.dev's alias and dated id of one model are one row