Files
A model not under its provider's own models.dev vendor (a relay or provider of the user's, a live id that doesn't match, or a preview/-exp or typed id Known looks up) took the levels most listed for it across all of models.dev. For mimo-v2.6-flash those are the resellers' (llmgateway, counted once per upstream host it lists, and requesty, up to max), while Xiaomi lists it with a thinking switch alone. OpenCode was offered variants up to max, Pi and Codex the same levels, and the gateway passed max on unchanged (fitEffort keeps an effort when no levels are known), which Xiaomi turned away with a 400. The same held for Zhipu's glm-5/5.1/5v-turbo, Kimi k2.6, MiniMax M2.x, gemma-4 and others whose makers give no levels while resellers give up to max. Now catalog.ListedBy looks a model up in given catalogs (vendor prefix, case, a ':free' or '(…)' suffix aside) and gives the levels listed there, none for a switch or no options, and skips an entry with a token budget alone (Claude 4.5), which says nothing of efforts. Known looks in the provider's own catalogs first, and every borrow (Known, effortsOf) asks the makers' catalogs (vendor presets, less Groq and Ollama Cloud, marked Hosts as they serve others' models) before the crowd. The crowd vote counts a provider once per list, so an aggregator listing a model under several hosts no longer outvotes the rest. Provider.Levelless says a model is known to have no levels, and the gateway (fitFor, on the direct and fallback paths) sends such a model no more than high; a model whose levels are simply unknown is still sent what was asked. Tests: TestEffortsOfOneVoteAProvider and TestListedBy (catalog); TestMakerLevelsBeforeResellers (provider: Xiaomi's preset, a preview left out of the list and a relay get no levels, an unlisted model and a budget-only Claude one still borrow, Levelless); TestOpenCodeNoResellerLevelsForSwitchModel (opencode.json has empty variants for Xiaomi's and a relay's mimo, three for a reseller-only model; Pi has no reasoning or thinkingLevelMap for mimo); TestSwitchModelSentNoMoreThanHigh (gateway: mimo asked max/xhigh sent high, low as asked, an unknown model max). The agent and gateway tests fail on main. An audit of every preset against the current models.dev snapshot found the cases above. Not tested with a real Xiaomi key or a running OpenCode. go build -tags nogui (and linux, windows), go test -tags nogui ./... pass.
provider tests: a signed-in test's home is Windows' home too — os.UserHomeDir reads USERPROFILE there, not HOME, so #145's Windows fixture path was looked for under the real profile
providers: Codex on Amazon Bedrock's GPT models goes to the runtime's Responses API (#176: Amazon Bedrock: Function tools with reasoning_effort are not supported for global.openai.gpt-6-luna in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'.) — the preset gave Bedrock chat completions alone for every model not Claude, so Codex's Responses, tools and reasoning in it, were translated to chat, which the runtime refuses for its GPT models. bedrock-runtime serves the Responses API at the same /openai/v1 with the same Bedrock API key as a Bearer, for OpenAI's closed GPT models (not gpt-oss, which stays on chat): the preset and each region now have it, a Bedrock provider saved before is given it where its chat is, and a GPT model is asked there first (Codex's request relayed as it came; a chat client's taken there once chat refuses it). A chat endpoint with no Responses to go to that refuses tools with reasoning so is asked again with reasoning_effort "none". Tested with a fake runtime returning that 400 on chat and serving /openai/v1/responses (Codex and a chat client with tools, a provider saved without Responses, gpt-oss still on chat) and a chat-only relay refusing the same (retried at none); both fail on main. Not tested against a real Bedrock account
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)
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)
sync: magpie's setup kept the same on every computer through WebDAV (坚果云, Nextcloud, a NAS…) — Settings has Sync and backup: the providers with their keys, groups and icons, the settings, the profiles and each agent's models go to the server as one file sealed with a passphrase, so the server never sees them; the magpie serving the gateway looks every three minutes, sends what changed here and brings in what changed elsewhere, a removal too; a part changed on both sides goes to the newer one, and the copy it replaced is kept in the sync folder with a note saying what was replaced; the proxy and the window size stay each computer's own, and a computer set not to send keys never strips them from the server; Export and Import carry the same file by hand (#28)
providers: Codex on Amazon Bedrock's GPT models goes to the runtime's Responses API (#176: Amazon Bedrock: Function tools with reasoning_effort are not supported for global.openai.gpt-6-luna in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'.) — the preset gave Bedrock chat completions alone for every model not Claude, so Codex's Responses, tools and reasoning in it, were translated to chat, which the runtime refuses for its GPT models. bedrock-runtime serves the Responses API at the same /openai/v1 with the same Bedrock API key as a Bearer, for OpenAI's closed GPT models (not gpt-oss, which stays on chat): the preset and each region now have it, a Bedrock provider saved before is given it where its chat is, and a GPT model is asked there first (Codex's request relayed as it came; a chat client's taken there once chat refuses it). A chat endpoint with no Responses to go to that refuses tools with reasoning so is asked again with reasoning_effort "none". Tested with a fake runtime returning that 400 on chat and serving /openai/v1/responses (Codex and a chat client with tools, a provider saved without Responses, gpt-oss still on chat) and a chat-only relay refusing the same (retried at none); both fail on main. Not tested against a real Bedrock account
windows: the desktop app no longer opens a console window for every command it runs (#54) — it is linked as a GUI program and has no console, so each console CLI it started (devin auth status and models list, devin acp for every Devin turn, claude, codex, cursor-agent and grok probes and runs, grok login) was given a new console by Windows, and a Windows Terminal window with it, some every few seconds while the app sat idle. Every command magpie runs is now made through internal/proc, which starts it with CREATE_NO_WINDOW when magpie has no console of its own: the child gets a console without a window, and its output, which magpie always pipes back, arrives as before. A magpie run in a terminal (magpie serve, the TUI, the desktop app started from one with a command) has one, and its children share it as they did, so Ctrl+C there still reaches them. The relaunch after an update keeps its DETACHED_PROCESS, added to rather than replacing the flags it was made with, and a test fails on any command made with os/exec directly, so a new one can't bring the windows back
provider: a Claude account's Fable week is read and routed on (#139) — /api/oauth/usage gives a week's allowance per model only in its limits[] array, which readClaudeWindows didn't parse, so magpie accounts, magpie quota and the app never showed Fable's, often the first to run out on Max, and routing went on sending Fable requests to an account whose Fable week was used up. Each weekly_scoped item with a scope.model.display_name is now a window "7 days · <name>", counted in percent, renewing at resets_at, a week long and counting only its model (the name lowercased, spaces and dots as dashes, so "Fable" counts every claude-fable-*), which Allowance.For and Full already weigh against that model alone, as they do the Opus and Sonnet weeks; one without a scope (seven_day again) or one for a model seven_day_opus/seven_day_sonnet already told is left out. Claude Code's rate_limit_event names the same window seven_day_overage_included, its "Fable limit" (in 2.1.283's bundle, for its allowlist Fable, Fable 5, Fable 5.1); claudeKinds takes it too, so the Fable week stays known while the usage endpoint turns magpie away. Reported by Cyb3rN8
provider: a Claude account's Fable week is read and routed on (#139) — /api/oauth/usage gives a week's allowance per model only in its limits[] array, which readClaudeWindows didn't parse, so magpie accounts, magpie quota and the app never showed Fable's, often the first to run out on Max, and routing went on sending Fable requests to an account whose Fable week was used up. Each weekly_scoped item with a scope.model.display_name is now a window "7 days · <name>", counted in percent, renewing at resets_at, a week long and counting only its model (the name lowercased, spaces and dots as dashes, so "Fable" counts every claude-fable-*), which Allowance.For and Full already weigh against that model alone, as they do the Opus and Sonnet weeks; one without a scope (seven_day again) or one for a model seven_day_opus/seven_day_sonnet already told is left out. Claude Code's rate_limit_event names the same window seven_day_overage_included, its "Fable limit" (in 2.1.283's bundle, for its allowlist Fable, Fable 5, Fable 5.1); claudeKinds takes it too, so the Fable week stays known while the usage endpoint turns magpie away. Reported by Cyb3rN8
claude: the signed-in account is one account, and one out of quota hands over to the next (#177) — the account Claude Code is signed in to was named one way as a provider and another as a saved login, so it was served twice and its limits weren't found under its name; and
claude auth status was asked with magpie's own ANTHROPIC_BASE_URL/token in the environment, telling of those rather than the sign-in. Now both go by ~/.claude.json's email (with the org), the CLI's email when that has none, and the status is asked without magpie's wiring. A turn the CLI ends with an error result (out of quota, rate limited) ends the reply there instead of waiting on, and a non-streamed one answers 429 for quota words, so another account takes over as in a stream. Tested: the account named as its login, the quota result ending the reply, another account taking over
warmup: a window that hasn't started is started, and started again after a try that didn't take (#178) — the warm-up only acted on a window with a reset time, so a Codex or Claude account whose 5-hour window had never started (no reset time, nothing used) was never warmed. Now a window the usage reads as not started, or one the agent's windows should have but the reading lacks, is due at once when idle; a try that leaves it unstarted is tried again after 15 minutes, doubling to 4 hours; reading the usage and seeing a window not started kicks the warm-up rather than waiting its 10-minute round, and a warm-up forgets the cached usage so the new window shows. Tested with the repro of the issue, the backoff, and the kick
warmup: a window that hasn't started is started, and started again after a try that didn't take (#178) — the warm-up only acted on a window with a reset time, so a Codex or Claude account whose 5-hour window had never started (no reset time, nothing used) was never warmed. Now a window the usage reads as not started, or one the agent's windows should have but the reading lacks, is due at once when idle; a try that leaves it unstarted is tried again after 15 minutes, doubling to 4 hours; reading the usage and seeing a window not started kicks the warm-up rather than waiting its 10-minute round, and a warm-up forgets the cached usage so the new window shows. Tested with the repro of the issue, the backoff, and the kick
providers: the context window field offers its usual sizes under it, one click each — 128K, 200K, 256K, 1M — and, where models take more than they are said to, each one's own most: Codex's GPT-6 and GPT-5.6 are said to hold 272K and take 872K when asked, a size nobody remembers, so one chip sets it for every such model (#120). The most is read from max_context_window in the list ChatGPT gives and in Codex CLI's cache
routing: an account out of quota rests until it says it's back, not an hour at a time (关于路由组优先级高的模型异常的展示文案 #147) — ChatGPT's usage-limit refusal reaches the failover put in words, its resets_at only as the Retry-After magpie made of it, and a Retry-After is trusted for an hour at most, so a Plus account out until 21:34 read "59 分钟后恢复" and was tried, refused and benched again each hour; the reset now goes on beside the error for the rest to take as the vendor's own, the window it filled renews next, a bare Retry-After alone keeps the hour, a rate limit and a failure keep their short rests, and a Codex reset spent in magpie brings the account back at once. The Routing page says when as well as how long: "quota used up · back at 10:29 PM · in 3 h 41 min" (额度用尽 · 22:29 恢复 · 还有 3 小时 41 分钟)
providers: Claude Code is signed in to its next account once the one it is on is spent, as Codex is, and an account keeps its own affinity, backoff and usage across a switch (#208 Cyb3rN8: Claude Code 用自己的模型时,登录账号额度用满不会自动切换 · #209: Smart 选中的账号和 Claude Code 登录账号互不同步…切换登录账号还会把按 id 记的状态串到另一个账号上) — Claude Code on its own model asks Anthropic as the account it is signed in to, never through magpie, so with A spent its sessions got "You've hit your session limit" while the gateway had moved on to B. The Codex watcher (KeepCodexOnAnAccountWithRoom, now KeepOnAnAccountWithRoom, same place: started by the magpie serving the gateway, a minute in and every 5 minutes) now looks at Claude Code too: when the account it is signed in to is spent and another ticked account's allowance is known and has room, it is signed in to that one through SwitchLogin (credentials and .claude.json's oauthAccount), as Use on the Accounts page does; not when the Claude account was removed from magpie. The rule for #209: the sign-in follows Smart, not the other way round — it moves at the share Smart counts an account spent (provider.SpentShare = 98, which the gateway's usedShare now is), for Codex too (it moved at 100%). The Routing field of a Codex or Claude subscription with several accounts says so: routing picks the account for what comes through magpie; the agent on its own uses the one it is signed in to, moved on at 98%. Sessions already running keep the account they started with until restarted, as SwitchLogin says; routing Claude Code's own model through the gateway (the codexFailover way) is not done here. #209's second part was a real bug: the account the agent is on is the candidate "claude" and the others "claude@user", so a switch handed "claude" from A to B — a conversation A answered was kept on B and traced kept=true, and the Least used tokens and the backoff count followed the id. who(), routed.used and routed.failures are now keyed by restKey (provider@user, as rests already were); rest itself (group members, the trace) is unchanged. Tested with new TestClaudeSwitchedWhenSpent (sandbox HOME, keychain off, a fake usage endpoint: 97% stays, 98% signs Claude Code in to b — credentials and .claude.json — a stays ticked; on b it stays; both spent it stays), TestCodexSwitchedWhenUsedUp still passing, new TestLoginSwitchKeepsEachAccountItself (fails on main: the conversation went to b, kept) and new internal/gui/tests/routing-signin.test.cjs in Chromium and WebKit, en and zh; login-import and routing-kind GUI tests pass. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... pass (TestUserPath, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked and TestCodexVersion timed out at 5s under the full run's load and pass on their own). Not tested against real Claude accounts or a running Claude Code.
providers: Claude Code is signed in to its next account once the one it is on is spent, as Codex is, and an account keeps its own affinity, backoff and usage across a switch (#208 Cyb3rN8: Claude Code 用自己的模型时,登录账号额度用满不会自动切换 · #209: Smart 选中的账号和 Claude Code 登录账号互不同步…切换登录账号还会把按 id 记的状态串到另一个账号上) — Claude Code on its own model asks Anthropic as the account it is signed in to, never through magpie, so with A spent its sessions got "You've hit your session limit" while the gateway had moved on to B. The Codex watcher (KeepCodexOnAnAccountWithRoom, now KeepOnAnAccountWithRoom, same place: started by the magpie serving the gateway, a minute in and every 5 minutes) now looks at Claude Code too: when the account it is signed in to is spent and another ticked account's allowance is known and has room, it is signed in to that one through SwitchLogin (credentials and .claude.json's oauthAccount), as Use on the Accounts page does; not when the Claude account was removed from magpie. The rule for #209: the sign-in follows Smart, not the other way round — it moves at the share Smart counts an account spent (provider.SpentShare = 98, which the gateway's usedShare now is), for Codex too (it moved at 100%). The Routing field of a Codex or Claude subscription with several accounts says so: routing picks the account for what comes through magpie; the agent on its own uses the one it is signed in to, moved on at 98%. Sessions already running keep the account they started with until restarted, as SwitchLogin says; routing Claude Code's own model through the gateway (the codexFailover way) is not done here. #209's second part was a real bug: the account the agent is on is the candidate "claude" and the others "claude@user", so a switch handed "claude" from A to B — a conversation A answered was kept on B and traced kept=true, and the Least used tokens and the backoff count followed the id. who(), routed.used and routed.failures are now keyed by restKey (provider@user, as rests already were); rest itself (group members, the trace) is unchanged. Tested with new TestClaudeSwitchedWhenSpent (sandbox HOME, keychain off, a fake usage endpoint: 97% stays, 98% signs Claude Code in to b — credentials and .claude.json — a stays ticked; on b it stays; both spent it stays), TestCodexSwitchedWhenUsedUp still passing, new TestLoginSwitchKeepsEachAccountItself (fails on main: the conversation went to b, kept) and new internal/gui/tests/routing-signin.test.cjs in Chromium and WebKit, en and zh; login-import and routing-kind GUI tests pass. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... pass (TestUserPath, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked and TestCodexVersion timed out at 5s under the full run's load and pass on their own). Not tested against real Claude accounts or a running Claude Code.
providers: test that a subscription's context window set in its editor reaches the models agents are told of (#120) — Codex's 272K from the ChatGPT backend gives way to the 872K a user sets on the Codex account, through the account's save
windows: the desktop app no longer opens a console window for every command it runs (#54) — it is linked as a GUI program and has no console, so each console CLI it started (devin auth status and models list, devin acp for every Devin turn, claude, codex, cursor-agent and grok probes and runs, grok login) was given a new console by Windows, and a Windows Terminal window with it, some every few seconds while the app sat idle. Every command magpie runs is now made through internal/proc, which starts it with CREATE_NO_WINDOW when magpie has no console of its own: the child gets a console without a window, and its output, which magpie always pipes back, arrives as before. A magpie run in a terminal (magpie serve, the TUI, the desktop app started from one with a command) has one, and its children share it as they did, so Ctrl+C there still reaches them. The relaunch after an update keeps its DETACHED_PROCESS, added to rather than replacing the flags it was made with, and a test fails on any command made with os/exec directly, so a new one can't bring the windows back
workbuddy: requests are streamed to it, Test's too (#124: 测试 400 · Non-stream chat request is currently not supported, 对话正常的) — WorkBuddy refuses any request that doesn't stream, so its Test, a small non-streaming request, failed with 400 while agents, which stream, answered. A WorkBuddy account is now a backend that only streams: Test sends it stream:true, and the gateway streams it a client's non-streaming request and gives the client one answer, as it does Codex's and Google's. Tested: Test's request to such a backend streams on each endpoint, and a non-streaming chat request through the gateway to a fake upstream that refuses one comes back as one chat.completion
providers: a custom provider's icon can come from its website (#12) — From the website, beside Choose a picture, finds the site behind the base URL (its host, then the domains it sits under: api.deepseek.com, then deepseek.com) and keeps the icon its home page names (an SVG first, then the apple-touch-icon, data: URIs too) or its /favicon.ico, like a picture picked by hand; only public https hosts are reached. Icons are fetched as magpie, since deepseek.com turns Go's own User-Agent away
providers: a custom provider's icon can come from its website (#12) — From the website, beside Choose a picture, finds the site behind the base URL (its host, then the domains it sits under: api.deepseek.com, then deepseek.com) and keeps the icon its home page names (an SVG first, then the apple-touch-icon, data: URIs too) or its /favicon.ico, like a picture picked by hand; only public https hosts are reached. Icons are fetched as magpie, since deepseek.com turns Go's own User-Agent away
providers: an account signed in while magpie runs is listed with its vendor's models (#204 uclort: 首次添加 WorkBuddy 模型显示不全,点击刷新后模型全部显示) — a signed-in account's own list was fetched only at start-up, for the accounts there then (and once: a fetch that failed was not tried again). WorkBuddy AI signed in afterwards, in magpie or in WorkBuddy itself, showed magpie's built-in list ("magpie's list · Refresh asks the vendor", 14 models) until Refresh fetched /v3/config's 20-odd. Now provider.FetchNew, which start-up uses in place of its loop, also runs before the providers are listed (providersState, 8s each): each ready account with no fetched list is asked for it, so the first listing after a sign-in is the vendor's. One that fails is asked again after 10 minutes, not on each listing; one fetched is not asked again. Tested with a new TestFetchNewAccount (a fake WorkBuddy /v3/config: nothing asked with no account; a WorkBuddy auth file written while running lists the built-in models, then the vendor's three after FetchNew; not asked again; a 502 asked once over two listings, then again after the retry time). go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... pass (TestUserPath and three Claude sign-in tests timed out once under load, pass on their own and on a rerun of the package). Not tried in the GUI with a real WorkBuddy account
provider: a base URL with a version in its path is used as written, never given a /v1 (tcdw: 提供一个选项能够取消自动给 LLM 提供商地址补全 /v1/ 的默认行为) — a custom provider at Volcengine Ark's Agent Plan, https://ark.cn-beijing.volces.com/api/plan/v3 (its 其他工具 page, docs/82379/2373746), was asked for its list at …/v3/models and then …/v3/v1/models, and only the last failure was shown, so the error named a URL the user never gave. Ark answers both 401 without a valid key (checked unauthenticated: auth comes before routing), so which path it serves can't be told from outside; and had …/v3/v1/models answered, fixV1 would have rewritten the chat base to …/v3/v1 for good. Now an OpenAI-style base with a version segment anywhere in its path (v1, v3, v4, v1beta: Ark's /api/plan/v3 and /api/coding/v3, Zhipu's /api/paas/v4, Gemini's /v1beta/openai) is asked only at base/models and fixV1 leaves it alone; a bare host or an unversioned path (a relay's …/api) is still looked around for the /v1 it may lack, and an Anthropic base still has its /v1/models. A failed list names every URL asked with its status and the vendor's own message ("401 Unauthorized (The API key doesn't exist.)") and says the model ids can be typed in by hand or a models URL given — both already work, the base stays as typed; magpie provider add now prints it instead of saying nothing. No "don't touch my URL" toggle: a versioned URL is never touched, and an unversioned one is only fixed when its /v1 alone answered, when requests at the base as written would miss too. Tested against an httptest server shaped like Ark (/api/plan/v3/chat/completions served, /models 404, a lenient /v3/v1/models) in a sandbox HOME: before, …/v3/v1/models: 404 and, lenient, chat moved to …/v3/v1; after, only …/api/plan/v3/models is asked and the base is kept
subscription: Antigravity accounts can be imported from a JSON file (莫: https://github.com/jlcodes99/vscode-antigravity-cockpit 这个开源的里面有 你可以看看) — beside Add account (and as "Import instead…" on the risk note) the Antigravity card takes files or pasted text exported by Antigravity Cockpit (a JSON array of {email, refresh_token}, or refresh tokens one a line), Antigravity Manager (the same array, {accounts:[…]}, or an account file with token{refresh_token, email, project_id}) and CLIProxyAPI (an antigravity auth file, type/email/refresh_token/project_id; another type's is refused). All three sign in with the Antigravity app's own OAuth client, as magpie does, so their refresh tokens are good here. Each account is checked as signing in does: the token refreshed at Google, the email read from userinfo (the file's if that's refused), the project and plan from loadCodeAssist, then kept in logins.json like a signed-in account. A token magpie already has is not sent again (exists); a known email with a new token replaces the old (updated); duplicates in the file, entries with no token and tokens Google refuses are reported per account, Google's bare "Bad Request" explained. POST /api/signin/import; tokens are never logged, shown or returned, and the paste box is masked. Tested against an httptest token/userinfo/Code Assist server in a sandbox HOME
subscription: Antigravity accounts can be imported from a JSON file (莫: https://github.com/jlcodes99/vscode-antigravity-cockpit 这个开源的里面有 你可以看看) — beside Add account (and as "Import instead…" on the risk note) the Antigravity card takes files or pasted text exported by Antigravity Cockpit (a JSON array of {email, refresh_token}, or refresh tokens one a line), Antigravity Manager (the same array, {accounts:[…]}, or an account file with token{refresh_token, email, project_id}) and CLIProxyAPI (an antigravity auth file, type/email/refresh_token/project_id; another type's is refused). All three sign in with the Antigravity app's own OAuth client, as magpie does, so their refresh tokens are good here. Each account is checked as signing in does: the token refreshed at Google, the email read from userinfo (the file's if that's refused), the project and plan from loadCodeAssist, then kept in logins.json like a signed-in account. A token magpie already has is not sent again (exists); a known email with a new token replaces the old (updated); duplicates in the file, entries with no token and tokens Google refuses are reported per account, Google's bare "Bad Request" explained. POST /api/signin/import; tokens are never logged, shown or returned, and the paste box is masked. Tested against an httptest token/userinfo/Code Assist server in a sandbox HOME
providers: account usage no longer writes its map from two goroutines at once — an account fetched less than a minute ago went into the map the other accounts' fetches were still writing to, which Go answers with a fatal concurrent map writes, not a panic anything recovers from; LoginUsage and Grok's copy of it now write that hit under the lock their fetches use (#119)
grok: Grok Build is found on Windows where its installer puts it (#180: the Grok Build installer finished, but magpie can't find it) — magpie looked for ~/.grok/bin/grok, without .exe, and then only at the first grok on PATH, which on Windows may be another (an npm grok.cmd). Now it looks at GROK_BIN_DIR, the grok home's bin and ~/.grok/bin (grok.exe on Windows), then every folder on PATH and on the registry's PATH, which has what was installed since magpie started, taking a grok there only under a .grok folder; the install-first error gives the PowerShell command on Windows. Tested the order and names of the places looked at, and which grok is taken, in a fake home
grok: Grok Build is found on Windows where its installer puts it (#180: the Grok Build installer finished, but magpie can't find it) — magpie looked for ~/.grok/bin/grok, without .exe, and then only at the first grok on PATH, which on Windows may be another (an npm grok.cmd). Now it looks at GROK_BIN_DIR, the grok home's bin and ~/.grok/bin (grok.exe on Windows), then every folder on PATH and on the registry's PATH, which has what was installed since magpie started, taking a grok there only under a .grok folder; the install-first error gives the PowerShell command on Windows. Tested the order and names of the places looked at, and which grok is taken, in a fake home
routing: a group's member may be a model at an effort of its own, provider/model:low (#189, @luw2007: model + effort 才是完整的组合项) — a member was only a model, reasoning as the agent asked or as the group's classifier picked, so "the same model, cheap and quick" and "the same model, thinking hard" could not both be in a group, nor could a rule send to one of them. A member now may carry an effort (none … max): a request routed to it is asked for that much reasoning whatever the agent asked or effort=auto picked, at the level the model has nearest, while the vendor is asked for the model's own id. The same model at two efforts is two members that rules (use=m:high), stickiness and the trace tell apart; an id whose own last part is a colon (a :free, :7b, :0) stays the model's. The group's reasoning levels are those of its members that follow the agent, and the editor has a reasoning button on each member. Tested with a fake provider through a sandbox magpie: the upstream bodies carry the fixed effort in Chat, Responses and Anthropic shapes, and the routing view shows m1:low / m1:high as two seats, light and dark
routing: a group's member may be a model at an effort of its own, provider/model:low (#189, @luw2007: model + effort 才是完整的组合项) — a member was only a model, reasoning as the agent asked or as the group's classifier picked, so "the same model, cheap and quick" and "the same model, thinking hard" could not both be in a group, nor could a rule send to one of them. A member now may carry an effort (none … max): a request routed to it is asked for that much reasoning whatever the agent asked or effort=auto picked, at the level the model has nearest, while the vendor is asked for the model's own id. The same model at two efforts is two members that rules (use=m:high), stickiness and the trace tell apart; an id whose own last part is a colon (a :free, :7b, :0) stays the model's. The group's reasoning levels are those of its members that follow the agent, and the editor has a reasoning button on each member. Tested with a fake provider through a sandbox magpie: the upstream bodies carry the fixed effort in Chat, Responses and Anthropic shapes, and the routing view shows m1:low / m1:high as two seats, light and dark
group: a group's id can change — a group magpie found is group/auto-<model>, and agents could only pick it by that; now its id (any group's) is changed in the editor or with magpie group set auto-gpt-6-astra id=gpt-6-astra: the found one stays removed under the old id, the groups that have it in them and their rules follow, and an agent set to the old id is told it needs setting again (#83). magpie groups no longer says a group in a group is not served
routing: a request a group's rule sent to a member that can't take it fails over to the members the rules after it that match too name, in their order, before the group's others (#112) — two rules for images, the first's model out of quota, now go on to the second's vision model rather than the group's text-only first; and while the first's rests, the second's goes first. The trace names who comes next, and says a turn stays with whoever took it, not the model the rule named
provider: Huawei Cloud's MaaS Token Plan is a preset (Lemon: 能否支持华为 codearts 订阅啊) — huaweicloud, Huawei Cloud MaaS: the Token Plan (personal accounts, 西南-贵阳一 only) is spent only at its own endpoints, https://api.modelarts-maas.com/plan/v2 for chat completions (its OpenClaw, Cherry Studio and CodeArts pages, tokenplan-maas-0004/0009/0014) and /plan/anthropic for messages (its Claude Code page, tokenplan-maas-0003); pay-as-you-go MaaS is the other Plan, /openai/v1 (qs-maas-0001, OpenCode page maas-modelarts-0909) and /anthropic (model-call-060). No Responses on either. Its models (glm-5.3, glm-5.1, kimi-k2.6, deepseek-v4.1-flash, deepseek-v4-flash, lowercase as its OpenClaw page has them) are given until a list is fetched; the key is a MaaS API key from console.huaweicloud.com/modelarts/?#/model-studio/authmanage; no usage API is documented (quota is shown in the console's 订阅管理), so none is read. CodeArts (码道) itself isn't a provider: it has no official API, its private endpoints are reverse-engineered, and its own doc has it reach the Token Plan through these same endpoints. Icon is lobehub's huaweicloud-color, as the others are.
providers: a custom provider's icon can come from its website (#12) — From the website, beside Choose a picture, finds the site behind the base URL (its host, then the domains it sits under: api.deepseek.com, then deepseek.com) and keeps the icon its home page names (an SVG first, then the apple-touch-icon, data: URIs too) or its /favicon.ico, like a picture picked by hand; only public https hosts are reached. Icons are fetched as magpie, since deepseek.com turns Go's own User-Agent away
provider: a preset's provider takes request headers of the user's own, and a preset can be added more than once (#50) — which workspace a key that reaches several is for (anthropic-workspace-id), or who the app is to OpenRouter, could only be said by a custom provider, so it meant giving up the preset's endpoints and catalog; the editor now shows headers for presets as well, offering the ones the vendor documents with their values left to fill in, and "Add another" makes one more provider of a preset under an id of its own (anthropic-2) where adding again replaced the first; the same key, host and headers twice is refused as the one already there, and importers keep a preset's headers and its preset when they split an entry off to a free id
grok: Grok Build is found on Windows where its installer puts it (#180: the Grok Build installer finished, but magpie can't find it) — magpie looked for ~/.grok/bin/grok, without .exe, and then only at the first grok on PATH, which on Windows may be another (an npm grok.cmd). Now it looks at GROK_BIN_DIR, the grok home's bin and ~/.grok/bin (grok.exe on Windows), then every folder on PATH and on the registry's PATH, which has what was installed since magpie started, taking a grok there only under a .grok folder; the install-first error gives the PowerShell command on Windows. Tested the order and names of the places looked at, and which grok is taken, in a fake home
grok: Grok Build is found on Windows where its installer puts it (#180: the Grok Build installer finished, but magpie can't find it) — magpie looked for ~/.grok/bin/grok, without .exe, and then only at the first grok on PATH, which on Windows may be another (an npm grok.cmd). Now it looks at GROK_BIN_DIR, the grok home's bin and ~/.grok/bin (grok.exe on Windows), then every folder on PATH and on the registry's PATH, which has what was installed since magpie started, taking a grok there only under a .grok folder; the install-first error gives the PowerShell command on Windows. Tested the order and names of the places looked at, and which grok is taken, in a fake home
provider: saved accounts are kept signed in — a Claude or ChatGPT account saved in magpie but not the one the agent is on was refreshed only when used or its usage looked at, and smart routing leaves the ones waiting their turn idle for days; the magpie serving the gateway now renews each such sign-in about once a day (hourly check, so a machine that slept catches up), never the agent's own; a refresh the vendor refuses (400/401) marks the account signed out — the account list says to add it again, magpie accounts too — and one that succeeds clears it; magpie accounts refresh renews them all now, for cron or launchd; a switch waits for a refresh in flight, so the agent is never handed a refresh token being spent (#53)
accounts: a Claude or Codex account removed from magpie is left alone — its sign-ins are no longer saved from the agent nor kept renewed in the background, until it's brought back; before, removing it only kept it out of the providers while magpie went on copying and refreshing the sign-in (#121)
providers: ChatGPT and Claude accounts come in from CLIProxyAPI's auth files (#124 Mrhe525: 要是能把那什么cpa的json文件可以导入直接使用就无敌了) — the ChatGPT (Codex) and Claude subscriptions now take the import Antigravity had: Import accounts from a file… under the accounts, and Import from a file instead… while a browser sign-in waits. It reads CLIProxyAPI's files ({type:"codex"|"claude", access_token, refresh_token, id_token, account_id, email, …}), one or a list or {accounts:[…]}, Codex's own auth.json ({tokens:{…}}), Claude Code's .credentials.json ({claudeAiOauth:{…}}) and bare refresh tokens one a line. Each account is checked as a sign-in in magpie is finished: its refresh token traded with the agent's own OAuth client (codexRefresh / claudeRefresh), then the ChatGPT claims or Claude's profile read (plan, tier, account and organization), and it is kept as that sign-in would keep it: Codex's or Claude Code's own if the agent has none, else added and ticked. The refresh rotates the token, so the file's copy stops working; the box says so. A refresh token magpie holds already (saved or the agent's own) is "Already in magpie" and never refreshed; another app's file ("a claude sign-in, not Codex's"), an API key, a token in the files twice and one the vendor turns down ("used since … expired or signed out") are listed as not added with the reason; tokens are never in what comes back. codexExchange and claudeExchange now build their sign-in through the same codexLogin / claudeLogin. Also: a Claude sign-in with no ~/.claude yet failed writing .credentials.json (the directory is made now), and the magpie://import dialog was drawn by the subscription import's renderImport, declared later under the same name (renamed renderLoginImport). zh strings added. Tested with new TestImportCodexLogins (a fake token endpoint: a CPA file and a native auth.json added with the new tokens and the file's account id, the live account untouched and reported as there, a Claude file, a spent token and a duplicate refused, a second import "exists", garbage an error) and TestImportClaudeLogins (with no sign-in the CPA file becomes Claude Code's credentials, plan max, active; a spent .credentials.json and a Codex file refused), and a new internal/gui/tests/login-import.test.cjs in Chromium and WebKit, en and zh (both ways in, the texts, what is posted, the outcomes, the account listed). go build -tags nogui (and GOOS=windows, GOOS=linux), go test -tags nogui ./... and the other GUI tests pass on
fe22692 (click-scroll's WebKit case fails after f1a8caf, with or without this change)
providers: ChatGPT and Claude accounts come in from CLIProxyAPI's auth files (#124 Mrhe525: 要是能把那什么cpa的json文件可以导入直接使用就无敌了) — the ChatGPT (Codex) and Claude subscriptions now take the import Antigravity had: Import accounts from a file… under the accounts, and Import from a file instead… while a browser sign-in waits. It reads CLIProxyAPI's files ({type:"codex"|"claude", access_token, refresh_token, id_token, account_id, email, …}), one or a list or {accounts:[…]}, Codex's own auth.json ({tokens:{…}}), Claude Code's .credentials.json ({claudeAiOauth:{…}}) and bare refresh tokens one a line. Each account is checked as a sign-in in magpie is finished: its refresh token traded with the agent's own OAuth client (codexRefresh / claudeRefresh), then the ChatGPT claims or Claude's profile read (plan, tier, account and organization), and it is kept as that sign-in would keep it: Codex's or Claude Code's own if the agent has none, else added and ticked. The refresh rotates the token, so the file's copy stops working; the box says so. A refresh token magpie holds already (saved or the agent's own) is "Already in magpie" and never refreshed; another app's file ("a claude sign-in, not Codex's"), an API key, a token in the files twice and one the vendor turns down ("used since … expired or signed out") are listed as not added with the reason; tokens are never in what comes back. codexExchange and claudeExchange now build their sign-in through the same codexLogin / claudeLogin. Also: a Claude sign-in with no ~/.claude yet failed writing .credentials.json (the directory is made now), and the magpie://import dialog was drawn by the subscription import's renderImport, declared later under the same name (renamed renderLoginImport). zh strings added. Tested with new TestImportCodexLogins (a fake token endpoint: a CPA file and a native auth.json added with the new tokens and the file's account id, the live account untouched and reported as there, a Claude file, a spent token and a duplicate refused, a second import "exists", garbage an error) and TestImportClaudeLogins (with no sign-in the CPA file becomes Claude Code's credentials, plan max, active; a spent .credentials.json and a Codex file refused), and a new internal/gui/tests/login-import.test.cjs in Chromium and WebKit, en and zh (both ways in, the texts, what is posted, the outcomes, the account listed). go build -tags nogui (and GOOS=windows, GOOS=linux), go test -tags nogui ./... and the other GUI tests pass on
fe22692 (click-scroll's WebKit case fails after f1a8caf, with or without this change)
providers: account usage no longer writes its map from two goroutines at once — an account fetched less than a minute ago went into the map the other accounts' fetches were still writing to, which Go answers with a fatal concurrent map writes, not a panic anything recovers from; LoginUsage and Grok's copy of it now write that hit under the lock their fetches use (#119)
panel: a profile saved or deleted shows at once and stays in sight (Image #62: 点击保存没有效果,删除方案也没有效果。你为什么UI UX做这么烂?) — both did their work (profiles.json had the profile), but the page showed nothing of it: every profile action answers with the whole state, which read logins.json (166KB, the accounts' credentials) again for each agent's drift and models, dozens of times — 2.5–4s a state on a real setup — and only then redrew the chips. And with no profiles yet the chip saved came in above + Save current, which the click held where it was on the screen: the hold gave the view room and scrolled it 30px, and the chip went up under the tabs, out of sight. Now the chip saved is there at once, dimmed till magpie answers, the name field closed; × takes the chip away at once; a save or delete refused puts the list back and says why. A view at its top when clicked stays at its top instead of being given room to scroll (what came in above the control moves it down). logins.json is parsed once until it changes (filememo, a copy to each caller): a state went from 2.4s to 0.9s here. Tested with a new case in internal/gui/tests/panel-profiles.test.cjs, Chromium and WebKit, en and zh: magpie answering after 1.5s with no profiles, the chip there and dimmed before the answer, the list unscrolled and the chip below the tabs, × removing it before the answer, a refused save taken off with its reason (fails on main); against a sandbox magpie web in WebKit, the view stays at 0 where main scrolled it to 30. click-scroll passes in both engines; go test -tags nogui ./... (TestClaudeSignedOut flaky under the full run, passes alone), go build for linux and windows
models: a model its maker lists with a thinking switch alone takes no borrowed levels, and the gateway sends it no more than high (#214 Moody-Max: opencode 的 mimo-v2.6 Flash 你这边设置的默认推理强度是 max 导致400报错)
models: a model's own name given by the user keeps its provider after it too (#203 uclort: 如果在供应商模型页自定义模型名称,在 Codex 中就不会显示供应商了) — Entry.Label() showed "name · provider" only for a model's own (vendor's or models.dev's) name; a name the user gave it (provider's model page → Names & levels) dropped the provider entirely, so a renamed model in Codex's picker (and Pi's, ZCode's, OMP's, magpie's own gateway list — everything magpieModels/Label feeds) could no longer be told apart by vendor at a glance. Label() now appends " · provider" (or "routing group") to a custom name too, the same as it does for the vendor's own, and only leaves it off when the name already contains the provider's (case-insensitively), so a name like "Sol on A" isn't doubled to "Sol on A · A". No settings toggle added: the fix makes the provider suffix unconditional (what the issue actually asked for), so a preference to hide/show it would only reintroduce the gap; there's also no existing "model list" preferences surface to hang it on cheaply — Names & levels only holds the name and reasoning-level pickers. Tested with TestModelName (internal/provider/modelprefs_test.go, sandbox HOME): a custom name now reads "My Sol · A" in Label() and in CodexListed(), and "Sol on A" (already containing the provider) is left alone; and TestModelNameAndLevelsReachAgentFiles (internal/agent/modelprefs_test.go, sandbox HOME) checks the generated Codex model_catalog_json and Pi models.json entries carry "GLM · Relay" for a custom name and don't double it to "GLM via Relay · Relay" when the name already says Relay. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... pass except TestUserPath and TestClaudeAccountNamedAsItsLogin, which are load-flaky (pass alone, unrelated to this change) same as the pre-existing rtk.go gofmt / TestConfiguredModelsAdvertiseImageInput flakiness
models: a model's own name given by the user keeps its provider after it too (#203 uclort: 如果在供应商模型页自定义模型名称,在 Codex 中就不会显示供应商了) — Entry.Label() showed "name · provider" only for a model's own (vendor's or models.dev's) name; a name the user gave it (provider's model page → Names & levels) dropped the provider entirely, so a renamed model in Codex's picker (and Pi's, ZCode's, OMP's, magpie's own gateway list — everything magpieModels/Label feeds) could no longer be told apart by vendor at a glance. Label() now appends " · provider" (or "routing group") to a custom name too, the same as it does for the vendor's own, and only leaves it off when the name already contains the provider's (case-insensitively), so a name like "Sol on A" isn't doubled to "Sol on A · A". No settings toggle added: the fix makes the provider suffix unconditional (what the issue actually asked for), so a preference to hide/show it would only reintroduce the gap; there's also no existing "model list" preferences surface to hang it on cheaply — Names & levels only holds the name and reasoning-level pickers. Tested with TestModelName (internal/provider/modelprefs_test.go, sandbox HOME): a custom name now reads "My Sol · A" in Label() and in CodexListed(), and "Sol on A" (already containing the provider) is left alone; and TestModelNameAndLevelsReachAgentFiles (internal/agent/modelprefs_test.go, sandbox HOME) checks the generated Codex model_catalog_json and Pi models.json entries carry "GLM · Relay" for a custom name and don't double it to "GLM via Relay · Relay" when the name already says Relay. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... pass except TestUserPath and TestClaudeAccountNamedAsItsLogin, which are load-flaky (pass alone, unrelated to this change) same as the pre-existing rtk.go gofmt / TestConfiguredModelsAdvertiseImageInput flakiness
models: a model its maker lists with a thinking switch alone takes no borrowed levels, and the gateway sends it no more than high (#214 Moody-Max: opencode 的 mimo-v2.6 Flash 你这边设置的默认推理强度是 max 导致400报错)
provider: a custom provider can list its models from a URL of its own — Xiaomi MiMo's plans are served at their own hosts while the list is at api.xiaomimimo.com/v1/models, so fetching models from the base URL found nothing; More endpoints has a Models URL (模型列表地址), asked with the key and nowhere else, and magpie provider set <id> models.url=… sets it (#67)
providers: NVIDIA and ModelScope are presets (#197: 增加 NVIDIA NIM 和魔搭 ModelScope 作为供应商预设) — NVIDIA NIM at integrate.api.nvidia.com/v1 with a key from build.nvidia.com, and 魔搭's API-Inference at api-inference.modelscope.cn/v1 with a ModelScope access token (not DashScope, which the Qwen presets are), each with its models.dev catalog (nvidia, modelscope) and its logo. NVIDIA's /v1/responses answers 404 for its models but a few, so it has chat completions only; ModelScope's /v1/responses is served (401 without a token, 404 for a path it doesn't know), so it has both. Neither has an Anthropic endpoint here. An entry imported at either base URL is the preset, and the import link pages and README list them. Tested: the presets' endpoints, catalogs and import, a /v1/models answer shaped as each sends it (NVIDIA's embedding models left out, ModelScope's object "" rows kept), and catalog.FetchAt against the live integrate.api.nvidia.com (74 models) and api-inference.modelscope.cn (35) lists
subscription: WorkBuddy AI, the international build, is its own subscription (Workbuddy国际版的Subscription是否也要支持呀?… 你应该新加一个Subscription Provider叫做Workbuddy国际版) — workbuddy-ai, "WorkBuddy 国际版 (WorkBuddy AI)" under 订阅. From WorkBuddy AI.app 5.6.2's product config: the same API as the Chinese build at its own endpoint, https://www.workbuddy.ai (isOversea, externalDomain www.codebuddy.ai / www.workbuddy.ai), signed in with platform=workbuddy-ai; its own account is read, never changed, from workbuddy-desktop-ai.info beside workbuddy-desktop.info in CodeBuddyExtension's auth folder, others kept in logins.json apart from the Chinese build's. Its models are its own (the Fast/Balanced/Primary/Deep tiers, GPT-5.5/5.4/5.3-Codex, Gemini 3.1 Pro/3.5 Flash, GLM-5.3/5.2, Hy3, Kimi-K3/K2.6, MiniMax-M3) until /v3/config is read; credits from the same resource summary. workbuddy.go is now parametrised by a wbSite (CN, AI); token cache keyed by site and uid. Checked live: auth/state at www.workbuddy.ai answers and magpie opens https://www.workbuddy.ai/login?platform=workbuddy-ai (200); chat and usage not tried against a real international account.
subscription: a Command Code plan's 5-hour and weekly windows show on the Usage page and the panel (ARNO: command code plan的五小时额度,周额度等订阅信息没有显示到用量面板里) — the plan's card was read right (billing/credits as command-code 1.66's /usage reads it: credits.{planId, monthlyCredits, purchasedCredits, freeCredits}, windowLimits.{limited, fiveHour, weekly}.{used, cap, resetAt in ms} beside it) but also carried the dollars left as its Balance, and a card's Balance is shown in place of its windows everywhere (the panel, the Usage page, the TUI): the windows were read and never drawn. The dollars are now a Credits window, used of the CLI's own pool (the plan's month — Go $10, Pro $30 or $80 for pro-v1, Provider $15, GOAT $70, Teams Pro $40, Max $150, Ultra $300 — or what is left of it if more, plus bought and free credits), "$spent / $pool"; only a reply with no windows and no known plan is a Balance still. A plan's key on the keyed commandcode preset gets the same windows as a plan card (api.commandcode.ai is a plan quota source; a pay-as-you-go key has none and no card), and its balance card is the dollars left alone rather than "5h 42% · week 18% · $12.30" squeezed into one amount. billing/credits carries no planId (
4b21f62 found it so on a live account), so the pool's month is the plan billing/subscriptions names; an account with no plan and no windows shows what it bought as its Balance
models: a model its maker lists with a thinking switch alone takes no borrowed levels, and the gateway sends it no more than high (#214 Moody-Max: opencode 的 mimo-v2.6 Flash 你这边设置的默认推理强度是 max 导致400报错)
qoder: thinking turned off reaches Qoder as off, a refused usage sign-in no longer lapses the account, only a gone sign-in is a 401, and a tool's images are sent (#162 review follow-ups)
qoder: thinking turned off reaches Qoder as off, a refused usage sign-in no longer lapses the account, only a gone sign-in is a 401, and a tool's images are sent (#162 review follow-ups)
qoder: thinking turned off reaches Qoder as off, a refused usage sign-in no longer lapses the account, only a gone sign-in is a 401, and a tool's images are sent (#162 review follow-ups)
routing: an account out of quota rests until it says it's back, not an hour at a time (关于路由组优先级高的模型异常的展示文案 #147) — ChatGPT's usage-limit refusal reaches the failover put in words, its resets_at only as the Retry-After magpie made of it, and a Retry-After is trusted for an hour at most, so a Plus account out until 21:34 read "59 分钟后恢复" and was tried, refused and benched again each hour; the reset now goes on beside the error for the rest to take as the vendor's own, the window it filled renews next, a bare Retry-After alone keeps the hour, a rate limit and a failure keep their short rests, and a Codex reset spent in magpie brings the account back at once. The Routing page says when as well as how long: "quota used up · back at 10:29 PM · in 3 h 41 min" (额度用尽 · 22:29 恢复 · 还有 3 小时 41 分钟)
routing: a group's member may be a model at an effort of its own, provider/model:low (#189, @luw2007: model + effort 才是完整的组合项) — a member was only a model, reasoning as the agent asked or as the group's classifier picked, so "the same model, cheap and quick" and "the same model, thinking hard" could not both be in a group, nor could a rule send to one of them. A member now may carry an effort (none … max): a request routed to it is asked for that much reasoning whatever the agent asked or effort=auto picked, at the level the model has nearest, while the vendor is asked for the model's own id. The same model at two efforts is two members that rules (use=m:high), stickiness and the trace tell apart; an id whose own last part is a colon (a :free, :7b, :0) stays the model's. The group's reasoning levels are those of its members that follow the agent, and the editor has a reasoning button on each member. Tested with a fake provider through a sandbox magpie: the upstream bodies carry the fixed effort in Chat, Responses and Anthropic shapes, and the routing view shows m1:low / m1:high as two seats, light and dark
subscriptions: signing in to Claude over credentials a logout left behind makes the new account the one Claude Code uses — magpie took the stale keychain item and profile for a live sign-in, so the new account was kept beside it unused while Claude Code said it was signed out, and no Claude provider showed up (#31)
provider: add a Claude or ChatGPT subscription from magpie itself — it opens the vendor's sign-in, takes the account when it comes back and lists it with the others, one click from being in use; magpie accounts add <agent> on the command line (#15)
providers: ChatGPT and Claude accounts come in from CLIProxyAPI's auth files (#124 Mrhe525: 要是能把那什么cpa的json文件可以导入直接使用就无敌了) — the ChatGPT (Codex) and Claude subscriptions now take the import Antigravity had: Import accounts from a file… under the accounts, and Import from a file instead… while a browser sign-in waits. It reads CLIProxyAPI's files ({type:"codex"|"claude", access_token, refresh_token, id_token, account_id, email, …}), one or a list or {accounts:[…]}, Codex's own auth.json ({tokens:{…}}), Claude Code's .credentials.json ({claudeAiOauth:{…}}) and bare refresh tokens one a line. Each account is checked as a sign-in in magpie is finished: its refresh token traded with the agent's own OAuth client (codexRefresh / claudeRefresh), then the ChatGPT claims or Claude's profile read (plan, tier, account and organization), and it is kept as that sign-in would keep it: Codex's or Claude Code's own if the agent has none, else added and ticked. The refresh rotates the token, so the file's copy stops working; the box says so. A refresh token magpie holds already (saved or the agent's own) is "Already in magpie" and never refreshed; another app's file ("a claude sign-in, not Codex's"), an API key, a token in the files twice and one the vendor turns down ("used since … expired or signed out") are listed as not added with the reason; tokens are never in what comes back. codexExchange and claudeExchange now build their sign-in through the same codexLogin / claudeLogin. Also: a Claude sign-in with no ~/.claude yet failed writing .credentials.json (the directory is made now), and the magpie://import dialog was drawn by the subscription import's renderImport, declared later under the same name (renamed renderLoginImport). zh strings added. Tested with new TestImportCodexLogins (a fake token endpoint: a CPA file and a native auth.json added with the new tokens and the file's account id, the live account untouched and reported as there, a Claude file, a spent token and a duplicate refused, a second import "exists", garbage an error) and TestImportClaudeLogins (with no sign-in the CPA file becomes Claude Code's credentials, plan max, active; a spent .credentials.json and a Codex file refused), and a new internal/gui/tests/login-import.test.cjs in Chromium and WebKit, en and zh (both ways in, the texts, what is posted, the outcomes, the account listed). go build -tags nogui (and GOOS=windows, GOOS=linux), go test -tags nogui ./... and the other GUI tests pass on
fe22692 (click-scroll's WebKit case fails after f1a8caf, with or without this change)
provider: Antigravity's usage shows only the models enabled again (antigravity 为什么又显示了没有启用的模型的用量?) — since #150 (
2baad53) magpie offers a family of Antigravity's levels as one model (gemini-3.8-flash), but its quota still names each level's id (gemini-3.8-flash-high), so none of the windows matched what's enabled and chosenWindows, finding none, kept all 27. A window's id is now taken to the model magpie offers for it (AntigravityBase) before it's matched; on this machine, with Gemini 3.8 Flash enabled, 3 of 27 are kept: its high, low and medium
bedrock: GPT models are asked a reply's length as max_completion_tokens, and Claude Code's metadata is left out (#176: 400 Unsupported parameter: 'max_tokens' is not supported with this model; Request metadata contains a value that violates the regular expression) — Bedrock's OpenAI endpoint turns max_tokens away for its GPT models, so the endpoint test and every chat request to it (sent on as it came or built from another API) now ask max_completion_tokens; and Claude Code's metadata.user_id, a JSON string now, is not the plain id Bedrock's Anthropic endpoint checks it against, so it is dropped there. Tested against a fake Bedrock runtime (both fail on main)
provider: Volcengine Ark (火山方舟) is a preset, its Coding Plan, Agent Plan and pay-as-you-go (tcdw: 提供一个选项能够取消自动给 LLM 提供商地址补全 /v1/ 的默认行为) — volcengine, a first-party vendor with a Plan picker: Coding Plan (default) at https://ark.cn-beijing.volces.com/api/coding/v3 for chat completions and Responses and /api/coding for messages (套餐概览 docs/82379/1925114, 快速开始 1928261; its Codex page 2556056 sets wire_api "responses"); Agent Plan, which tcdw is on, at /api/plan/v3 (chat completions and Responses, its Hermes Agent page 2373743) and /api/plan (其他工具 2373746, Claude Code 2373740), with a key of its own; pay-as-you-go at /api/v3 (兼容 OpenAI SDK 1330626, 创建 Response 1569618), which both plans' pages warn bills apart from the plan — no Anthropic endpoint is documented for it. Its models (ark-code-latest, whichever the console picked, then doubao-seed-evolving, doubao-seed-2.1-pro/-lite, doubao-seed-2.0-mini, minimax-m3, glm-5.3, glm-5.3-flash, deepseek-v4.1-flash, deepseek-v4-flash, deepseek-v4-pro, kimi-k2.7-code, kimi-k2.8-preview, kimi-k3, lowercase as the quick-start page gives them) are shown until a list is fetched; no models.dev catalog is tied to it, as its volcengine one has pay-as-you-go's dated ids and volcengine-coding-plan misses the Agent Plan's. Key from ark.volcengine.com/region:cn-beijing/apikey. Their versioned paths go out as written (previous commit). Icon is lobehub's volcengine-color, as the others are; the import page offers its three plans
workbuddy: a chat with no system prompt is sent with one (Jerry lee on Discord: 没有 system 消息时报 first message is not system prompt) — WorkBuddy refuses a chat whose first message isn't the system prompt, which scripts and plain chat clients often send; such a chat now starts with a neutral "You are a helpful assistant.", the rest of the body kept as it was, and one that already starts with a system prompt goes as it is. The comment on WorkBuddy AI's client headers no longer says they fixed "unapproved channel": that answer comes from Claude Code's and Codex's own system prompts (#182). Tested on the account's request body, through the provider as signed in
workbuddy: a chat with no system prompt is sent with one (Jerry lee on Discord: 没有 system 消息时报 first message is not system prompt) — WorkBuddy refuses a chat whose first message isn't the system prompt, which scripts and plain chat clients often send; such a chat now starts with a neutral "You are a helpful assistant.", the rest of the body kept as it was, and one that already starts with a system prompt goes as it is. The comment on WorkBuddy AI's client headers no longer says they fixed "unapproved channel": that answer comes from Claude Code's and Codex's own system prompts (#182). Tested on the account's request body, through the provider as signed in
subscription: WorkBuddy AI, the international build, is its own subscription (Workbuddy国际版的Subscription是否也要支持呀?… 你应该新加一个Subscription Provider叫做Workbuddy国际版) — workbuddy-ai, "WorkBuddy 国际版 (WorkBuddy AI)" under 订阅. From WorkBuddy AI.app 5.6.2's product config: the same API as the Chinese build at its own endpoint, https://www.workbuddy.ai (isOversea, externalDomain www.codebuddy.ai / www.workbuddy.ai), signed in with platform=workbuddy-ai; its own account is read, never changed, from workbuddy-desktop-ai.info beside workbuddy-desktop.info in CodeBuddyExtension's auth folder, others kept in logins.json apart from the Chinese build's. Its models are its own (the Fast/Balanced/Primary/Deep tiers, GPT-5.5/5.4/5.3-Codex, Gemini 3.1 Pro/3.5 Flash, GLM-5.3/5.2, Hy3, Kimi-K3/K2.6, MiniMax-M3) until /v3/config is read; credits from the same resource summary. workbuddy.go is now parametrised by a wbSite (CN, AI); token cache keyed by site and uid. Checked live: auth/state at www.workbuddy.ai answers and magpie opens https://www.workbuddy.ai/login?platform=workbuddy-ai (200); chat and usage not tried against a real international account.
workbuddy: a chat with no system prompt is sent with one (Jerry lee on Discord: 没有 system 消息时报 first message is not system prompt) — WorkBuddy refuses a chat whose first message isn't the system prompt, which scripts and plain chat clients often send; such a chat now starts with a neutral "You are a helpful assistant.", the rest of the body kept as it was, and one that already starts with a system prompt goes as it is. The comment on WorkBuddy AI's client headers no longer says they fixed "unapproved channel": that answer comes from Claude Code's and Codex's own system prompts (#182). Tested on the account's request body, through the provider as signed in