Commit Graph
953 Commits
Author SHA1 Message Date
yetone 181eefdc31 plugins: built-in Devin shows its usage as the Devin plugin does, and a Grok account whose sign-in has expired no longer stops Grok's move (goal: 插件订阅 provider 跟内置的所有逻辑都要一模一样) — the plugin showed a Devin account's day and week quotas, its ACUs and extra balance from GetUserStatus while the built-in showed no usage at all, so routing had no allowance to fail over on until the move; the built-in now reads the same and fills the same windows (1 day, 7 days, ACUs aside), plan end and balance, and a refused key says to sign in again. A move checks each account through the plugin and stopped when one failed; Grok's plugin says an expired sign-in only in words ("Grok's sign-in has expired"), so one dead account kept every Grok account on the built-in. Such an account now moves along untried and unmarked, as a built-in Grok account was never marked; any other failure still stops the move. TestDevinUsage and TestMoveExpiredInWords fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 12:24:45 +08:00
yetone b69fd168c2 remote magpie: its image models reach this magpie, and look-alike models say which provider they are from (Jorben and 莫 on Discord) — another magpie's /v1/models listed chat models only, so its image models were never found here unless their id said "image"; a magpie now asks for them (X-Magpie-Drawers), the other lists them marked kind:image, and they show in Settings → Images and draw through its /v1/images/generations, which asks the model's vendor the way it draws there. Each model also comes with its name and its provider there (magpie_label), so two relays' "Claude Sonnet 5" no longer read the same. An older magpie on either side reads the list as before. TestRemoteMagpieModels fails without the change; TestRemoteMagpieOldList covers an old remote. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; remote-magpie passes. 2026-10-01 12:05:21 +08:00
yetone 51834ba937 migrate: a move installs the plugin versions that match their built-ins (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Command Code 0.1.6 (a Go account lists Go's models), Grok 0.1.4 (grok-4.7's reasoning levels), Qoder 0.1.4 (a refused refresh marks the sign-in), ZCode 0.1.5 (the MCP quota set aside) and Zed 0.1.6 (a model token refused while listing marks the account), all published and installed from npm in a sandbox before this. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 12:05:06 +08:00
yetone 77a545e691 gui: the Update button can be skipped for a version or turned off (Wang Hsiaohi on Discord) — the purple ↑ Update in the panel's header showed whenever a version was out, with nothing to hide it. A × on hover (or a right-click) hides it until a newer version, and Settings → About has an Update button On/Off row with Show again for a skipped version. Only the button hides: magpie still downloads and installs on quit, and Settings → Version still offers the restart. TestUpdateSkip, TestSettingsSaveKeepsWhatItDoesNotSend and update-hide.test.cjs (Chromium, WebKit, en/zh, panel and window) fail without the change. 2026-10-01 11:46:38 +08:00
yetone 4ba51bb627 plugin: a plugin names its own icon and the key field's title and hint (Lemon on Discord) — a plugin's provider took only the market's icon, and an api method's key field always read "<name> API key" with no hint. auth.icon (or package.json "magpie": {"icon"}) as an https URL, fetched once through the public-only icon path, or a data:image URI now comes before the market icon; an api method's label titles the key field as in OpenCode, and a new placeholder hints it, in the window and in magpie plugin login. README has a For plugin authors section. TestPluginOwnIcon, TestPluginOwnIconAndKeyHint and plugin-key-hint.test.cjs (Chromium, WebKit, en/zh) fail without the change. 2026-10-01 11:42:45 +08:00
yetone 49c98f40f5 gateway: a Remote magpie records the agent that asked on the other computer (Jorben on Discord) — requests forwarded to a remote magpie went as magpie itself, so the remote's usage and routing said "magpie" for all of them. The forwarding magpie now names the agent and itself (X-Magpie-Agent, X-Magpie-Via), read only from a magpie User-Agent and stripped before any vendor; the remote records that agent with Via, and Requests shows "Claude Code · via office-mac". It is only a label: models, routing rules and the drift check still go by the real sender. TestRemoteMagpieAgent, TestLastSeenNotVia and usage-ledger.test.cjs fail without the change. 2026-10-01 11:38:37 +08:00
yetone 0bb119f87f docker: sign-ins and plugins survive a restart, and the image checks its own health (Jorben on Discord) — only /config was a volume, while HOME (/home/nonroot) held the agents' sign-ins (~/.codex, ~/.claude…) and the cache held Bun: a new container lost them all, and distroless/static has no libc, so the Bun magpie downloads couldn't start (fork/exec … no such file or directory). HOME and XDG cache/data/state now live under /config (made on start, so an old volume keeps working), the base is distroless/cc (glibc), and magpie healthcheck backs a HEALTHCHECK with no curl needed. Verified in Docker: an old volume carried over, a market plugin installed and stayed across a recreate without downloading Bun again, the container reported healthy. TestHealthcheck and TestMakeDirs fail without the change. 2026-10-01 11:38:37 +08:00
yetone b4c6772677 lan: magpie in Docker shares an address other computers can reach (莫 on Discord) — inside a container the LAN address shown was the container's own (172.17…). MAGPIE_PUBLIC_URL now names the share address; without it, a page opened through the host's IP shows that host, and the Gateway page says it runs in a container and how to set the address. lan-docker.test.cjs (Chromium, WebKit, en/zh) and the serve/lan tests fail without the change. 2026-10-01 11:29:05 +08:00
yetone 0376ddfb55 gui: folding a list shorter than the window no longer flips the view for 4 seconds (#355) — with Reduce motion on, collapsing a list that ends above the window's bottom made hold and fitRoom undo each other every frame: the room was sized from scrollHeight, which never falls below the view, so the blank under the list counted as content. The room is now sized from where the content really ends, and dropped once back at the top. rollup-short.test.cjs (Chromium, WebKit, en/zh, Reduce motion on and off) fails without the change; old-webkit, click-scroll, panel-fold pass. 2026-10-01 11:28:52 +08:00
yetone 7d0f33d702 copilot: a model the plan refuses is remembered and Auto falls back (#371, #256) — Copilot answers a model the account's plan doesn't carry (Student, Free) with 400 model_not_supported; magpie kept offering and picking it. Such a model is now marked refused for that token for 6h, left out of the models listed and of Auto's picks, and the request is retried on the next model (fallback, then default, then non-premium such as gpt-4.1). TestCopilotRefused* fail without the change. 2026-10-01 11:28:41 +08:00
yetone eb89665d98 gateway: Codex's compact reaches xAI, Grok and Copilot, and a web-search turn keeps its client_metadata (#378, #374) — a compact request drops its tools but kept tool_choice "auto", which Grok's subscription, the xAI API and Copilot's /responses refuse; a lone tool_choice is now dropped for those (a relay like #292's still gets the body as sent). A Codex request rebuilt by magpie (web_search) lost client_metadata and text (its output schema); both now go on to Responses upstreams as sent, never to Chat or Anthropic ones. TestGrokBodyDropsLoneToolChoice, TestCodexCompactsThroughXAI, TestCodexSearchKeepsClientMetadata and TestClientMetadataOnlyToResponses fail without the change. 2026-10-01 11:28:41 +08:00
Fanbuz 957baf4925 fix: 修复虚拟双栈地址导致供应商图标获取失败 (#376) 2026-10-01 11:28:06 +08:00
Gabyran abbe314f74 gui: Settings re-reads the WebDAV setup when redrawn; the off-state description names only what is on (#307)
Fixes #305. loadSettings() drops the page's cached davsync view so a refocus shows what magpie webdav set wrote, and the off-state description is built from the view's toggles like the CLI's status. An open sync form is still never rebuilt by a refocus (#104). Merged by hand over a tests README conflict; webdav-settings-refresh, old-webkit, click-scroll (Chromium, WebKit) and go test ./internal/gui ./internal/davsync pass.
2026-10-01 11:25:29 +08:00
yetone b932a70032 gateway: a reply on /v1/messages has an Anthropic id whoever served it (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — a built-in moved onto its plugin (Devin, measured end to end) answered Claude Code with the plugin's OpenAI-shaped chatcmpl-… id where the built-in gave msg_…; the Anthropic replies, streamed and whole, now take msg_ as the chat replies already take chatcmpl-. TestAnthropicReplyID fails without the change. go vet, go test -tags nogui ./internal/gateway pass. 2026-10-01 11:20:00 +08:00
yetone b274ddd28e plugin: a move's check still goes through the account's proxy, now that it reads usage too — TestPluginProxy took the check's last request for its models; the check asks for the models and then the usage, so the test now looks for both among what the account's own proxy saw. renderExcluded's comment goes back over renderExcluded rather than over renderMovable. go vet, go test -tags nogui ./... pass. 2026-10-01 11:15:27 +08:00
yetone f5a2b5d406 gui: a moved provider's editor reads as the built-in's — its model list dated, its agent's own sign-in said, its move a button (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — after a move the list said "magpie's list · Refresh asks the vendor" where the built-in said "vendor list · 9m ago"; it is now dated by when the plugin last listed it, in the editor and in magpie providers. "Command Code itself stays signed in as it is." is kept for Devin, ZCode, WorkBuddy, Kiro and Command Code, whose agents keep their own sign-in through the move; and Move to the plugin / Use the built-in again is drawn as a button, not a line of text. plugin-move.test.cjs fails without the kept line. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; plugin-move, click-scroll, old-webkit, plugin-market, plugin-signin, nokey-editor pass. 2026-10-01 11:15:27 +08:00
yetone a28509e268 providers: a built-in moved before its host was kept shows the vendor's host, not the plugin's id (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — magpie providers and the Providers list showed "commandcode-plan", "zcode", "grok" for moves made before the move kept the built-in's host; those now show the built-in's own (api.commandcode.ai, api.z.ai, cli-chat-proxy.grok.com…), and one whose built-in showed none shows none. TestMovedShowsBuiltinHost fails without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 11:15:26 +08:00
yetone ff94998ad2 accounts: a moved built-in keeps its place in Providers and in magpie accounts (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — with WorkBuddy and WorkBuddy AI both moved, WorkBuddy AI came first when its plugin listed it first, as a moved one placed already didn't count; and magpie accounts put every moved built-in's accounts after all the others. Both now go where the built-in stood. TestMovedAccountsKeepTheirOrder and TestAllLoginsMovedKeepTheirPlace fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 11:15:26 +08:00
yetone cc6fc56b3b cli: magpie plugin list says a built-in runs the plugin's provider rather than "not signed in" (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — after a move back the plugin stays when the user had installed it, and its rows read "not signed in · magpie plugin login kiro", as if broken; they now say "runs on magpie's built-in (2 accounts) · magpie plugin move kiro". go build -tags nogui passes. 2026-10-01 11:15:25 +08:00
yetone 93c3cde024 gui: moving a built-in onto its plugin says what it is doing and why it didn't go, where the user is (记得把整套迁移逻辑做得丝滑优雅精致优美简单方便易用 / 要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — the Plugins tab opened as the page (?view=plugins) stayed blank; Move to the plugin greyed out for up to 18 s, then closed the editor, its error far off in the footer in English, and the button never came back. The button now says "Installing the plugin and checking each account…", a failure is said under it in the reader's language (npm unreachable, every account lapsed, models the plugin doesn't serve) with Try again, and a move that goes through turns the field over in the editor still open where it was, with a toast of the accounts and models it took. Runs on reads "magpie's built-in · or the community Zed plugin", the npm package in its tooltip; a quiet line over the list names the built-ins a plugin can run (Take a look · Not now); a plugin's card offers "Move my Zed accounts (2)", its installed row "Move Zed here" and "Zed: on magpie's built-in" rather than a signed-out plugin, and switching it on again offers the move back in the row. Accounts: a lapsed-only provider shows Signed out, not a green pill; plan chips read "Zed Pro", not "Zed Zed Pro", and a long one isn't cut; the "{agent} itself stays signed in" line is kept for a moved built-in whose agent keeps its own sign-in, named as the agent, and dropped where none is its own. The vendor list's age is said in the reader's language ("8m ago" in the zh UI). plugin-move.test.cjs (Chromium, WebKit, en/zh) fails without each of: the Plugins tab loading on ?view=plugins, Try again, the inline reason, the editor staying open, the progress text, the card's move. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; plugin-move, click-scroll, old-webkit, plugin-market, plugin-signin, nokey-editor pass. 2026-10-01 11:15:25 +08:00
yetone f9e5c8dfe6 cli: magpie --help lists plugin move|migrate and move-back|unmigrate (记得把整套迁移逻辑做得丝滑优雅精致优美简单方便易用) — the commands existed but only magpie plugin help named them, and without their other names. go build -tags nogui passes. 2026-10-01 11:15:24 +08:00
yetone 77c2ad12fa migrate: a move that fails leaves nothing behind and says why, and a move only goes through on an account the plugin proved works (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样 / 记得把整套迁移逻辑做得丝滑优雅精致优美简单方便易用) — a failed move left the plugin it had installed (a Plugins row saying signed out) and hidden plugin:<id> rows in logins.json, and a move checked only that the plugin loaded, so an account the vendor refused or couldn't be reached through moved as if it worked. The plugin a move installed now goes with a failed move, and with a move back when it serves nothing else; the stray rows go; the check reads each account's usage as well as its models, and an account the vendor refuses is marked and moves untried, as a lapsed one does, while one whose vendor can't be reached at all fails the move with nothing changed. A move with every account lapsed says so before installing anything. Failures carry a code (offline, install, lapsed, unserved, account) for the page to say in its language, with the models named, not their ids. A moved provider shows the built-in's API host (magpie providers showed the plugin's id), and Zed's accounts take their model list with them, so the plugin can serve it until it reads its own. TestMoveNeedsAWorkingAccount, TestMoveTidiesItsPlugin, TestMoveProvesEachAccount, TestMovedShowsBuiltinHost and TestZedMoveCarriesItsModels fail without the change. go vet, go build -tags nogui, go test -tags nogui ./... pass. 2026-10-01 11:15:23 +08:00
yetone c641c1da2d usage: a built-in moved onto its plugin keeps one history per account (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — the built-in's calls were kept as going to "dee@example.com" (or "api.factory.ai as dee@example.com"), the plugin's as "kiro as dee@example.com", so Usage split an account's model rows at the move, the older half as if the id had gone elsewhere. A subscription's calls are now told apart by the account they went as, the host only for the rest; records already kept group together with no rewrite. TestSummarizeKeepsMovedAccountTogether fails without the change. go vet, go test ./internal/usage pass. 2026-10-01 11:15:22 +08:00
yetone 94c5b541c5 claude: nothing is sent to Anthropic in Claude Code's name any more, and a Claude account's model test runs Claude Code (the user: 先把伪装代码删掉,测试模型改走 CLI) — claude_cloak.go made magpie's own requests on a Claude sign-in look like Claude Code's (its User-Agent, session and device ids, Stainless headers, betas, a billing header with a fingerprint and xxHash in the body). Generations already ran Claude Code through the bridge; the cloak was left driving the provider test (a direct /v1/messages "hi") and the model list. It is gone: a Claude account's direct requests are refused unsigned, the model test has Claude Code answer "hi" at the model tested (the warm-up's run, --model the one tested, a saved account's sign-in as CLAUDE_CODE_OAUTH_TOKEN), and the model list says it is magpie (User-Agent magpie, only the oauth beta). TestClaudeTestRunsThroughClaudeCode and TestClaudeTestRunsClaudeCode fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 11:12:27 +08:00
cyxer000 7a3df78ff7 agents: omp's model picker lists the models of the providers in its own models.yml (#326)
* agents: omp's model pickers list the models of the providers in its own models.yml

Every role's picker (model, subagents, smol and slow, all made by role())
only looked up the current value's provider on models.dev, so a provider
the user added to omp's models.yml (a company gateway, a relay) showed none
of its models. Its models now come first, grouped by provider, before
models.dev's for the current provider, each value once; magpie's own entry
is left out, and a provider with only discovery, whose models omp learns at
run time, offers none.

* agents: omp's models.yml options are spelled as ownOptions spells them, and a model offered twice keeps its name

Review of the models.yml options: they put the model's name in the label where ownOptions puts it in the note, and dedup kept whichever came first, so a models.yml entry with no name hid models.dev's name for the same model (zai/glm-5 lost "GLM-5"). The name now goes in the note, with the label left empty, and an option offered twice takes the note, icon and group icon from whichever copy has them.

* agents: a provider both in omp's models.yml and on models.dev stays one group in the picker

From #326's review: with the current value's provider in models.yml (say
anthropic, with a model of the user's) and another provider after it, the
provider's models.dev models came after that one, so its group was drawn
twice ("Anthropic → codemaker → zzrelay → Anthropic"; the picker draws a
heading at each change of group). The options are now sorted, stably, by
where each group first comes. TestOmpOwnModels fails without it (Z.AI's
models split in two), and checks the subagents' picker offers models.yml's
models as the model's does.
2026-10-01 11:10:49 +08:00
RandyandClaude e8315b18ed gateway: a relay that searches the web by itself is sent Claude Code's WebSearch as it was sent (#359) (#365)
magpie tells the APIs that search by themselves by their host (api.anthropic.com), so a relay in front of Anthropic's API was given magpie's search instead: the request was parsed and built again, and with no searcher set up in magpie the WebSearch tool was left out altogether. A provider now has Searches (magpie provider set <id> search=yes, or the editor's Web search row for a custom provider): a web search offered on its Anthropic or Responses API goes to it as sent. It counts only on an API the provider has an address for, so a Chat-only one isn't taken as searching, and the editor shows the row only while an Anthropic or Responses URL is filled in. A provider said to search isn't one magpie searches with for other models: that would spend the relay's quota, and a relay serving only Claude Code refuses magpie's own request, which has no metadata.user_id.

TestWebSearchOfARelayThatSearches fails without the change (the request built again, magpie searching with the relay, a Chat-only relay taken as searching); provider-search.test.cjs added (Chromium, WebKit, en/zh), its Chat-only case failing without the row's gate. Checked against real Claude Code 2.1.285 through a sub2api relay in claude_code_only mode: WebSearch returns results with search=yes. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass but for TestPluginProxy, which fails on main (813fdcb) as well here.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-01 11:10:41 +08:00
yetone 5523eb176a provider: a 403 from Factory says it serves a subscription only to Droid, not to sign in again (#242) — the same account's Opus 5.5, GPT-5.5 and GLM answered Droid through magpie every time and refused Grok Build (and Claude Code) every time, on the same models and efforts, with the same headers magpie builds; the old message sent people to check droid and sign in again, which changes nothing. Now it says Factory takes a subscription's requests only from Droid, other agents are refused, and the org's model policy or the plan is what's left when Droid is refused too. TestFactoryForbiddenWithoutOrg fails without the change. 2026-10-01 11:03:55 +08:00
yetone 813fdcb2e5 migrate: a moved built-in keeps its models' reasoning levels, its own account's plan and one own row, and the move checks each account through its proxy (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Command Code's, Grok's and WorkBuddy's models their vendor gives no levels for lost the ones the built-in borrowed from their maker; moved, they borrow them again. Devin's own account moved without the plan the Devin CLI says, which the built-in showed. The agent signing in to another account while moved wrote a new own row for the built-in, which came back beside the one set aside as a second own account; nothing is written while moved, and going back keeps the one. And the move's check of an account asked the vendor directly, not through the proxy its requests take. Found walking Command Code, Grok, Devin, ZCode and WorkBuddy through sandbox moves. TestMovedKeepsEfforts, TestMovedOwnRowNotWritten, TestDevinOwnMovesWithPlan and TestPluginProxy fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 07:04:40 +08:00
yetone 936a6fc710 gateway: a stream the upstream cut off mid-reply ends in an error event, not as if whole (#370) — a relay of a Messages or Responses stream that the upstream dropped, or ended short of its last event, was closed as though complete: no message_stop and no error, which dsh reads as "DeepSeek Messages stream ended before message_stop" (STREAM_CLOSED) and never retries, saying nothing of the cause. The relay now ends such a stream with the protocol's own error event (Anthropic event: error / api_error, which dsh retries; Responses response.failed; a Chat error only when the read failed, since a whole Chat stream may end without [DONE]) naming the provider, and logs the request as failed. A stream counts as whole by its last event's data or its event: name, so a response.completed too big to read (an image's base64) is not taken for a cut one. TestPassthroughStreamSeveredUpstream, TestPassthroughStreamEndsShort, TestPassthroughResponsesEndsShort and TestPassthroughResponsesBigLastEvent fail without the change; TestPassthroughStreamWhole guards a whole stream. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 06:59:02 +08:00
yetone 30236bf035 migrate: moving Kiro back with an API key no longer hangs magpie, a move's lock left by a killed magpie frees itself, and plugins changed in a terminal reach the running app (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Kiro's back put the plugin's API key onto the Kiro provider while holding logins.json's lock; saving the provider syncs the agents, which read the accounts and waited on that same lock, so the move back never returned and every request after it hung. The key is now put back once the lock is let go (a mover's settle). A move killed while hung left migrations.json.lock, refusing every move for 15 minutes as "another magpie is moving"; the lock now holds its magpie's pid and one whose magpie is gone is taken. And magpie plugin add, remove or move in a terminal left a running app with the plugins it had loaded (a moved Kiro gone until restart, "magpie knows no model kiro/auto"): a plugins.json changed by another magpie now restarts the host and takes the providers that magpie was told meanwhile. Found walking Qoder, Kiro and Cursor through sandbox moves. TestKiroMover (hangs), TestMoveLockOfDeadMagpie and TestPluginsChangedElsewhere fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 06:53:29 +08:00
yetone 471956c837 agents: dsh takes magpie as a custom provider of its own, Magpie, not by taking over its DeepSeek one (01huadalang on Discord: 集成进 dsh 会有 bug,会被 dsh 覆写,应该走 dsh 的那个另外的供应商) — magpie rewrote dsh's llm-deepseek entry in every profile to point at the gateway with the key MAGPIE_API_KEY. dsh's Models page showed it as DeepSeek, so a DeepSeek key typed there went into dsh's key store under that name, which dsh reads before .env: the next request carried the DeepSeek key and failed, magpie seemingly overwritten; dsh's own DeepSeek models were gone meanwhile. magpie now adds a magpie provider under llm-pi-ai, as dsh's Add model provider → Custom model API writes one (gateway URL, openai-completions, MAGPIE_GATEWAY_KEY, magpie's models with their limits, images and effort levels), and points the default model at it; llm-deepseek and the user's other providers are left alone, a profile wired the old way is moved on sync with its model and effort kept, new profiles get it, switching to a dsh model or clearing takes it out, and the check says when dsh's key store holds another MAGPIE_GATEWAY_KEY. Verified with dsh 0.2.0-rc.2 in a sandbox HOME against a fake gateway: headless and web requests carry magpie's token, a DeepSeek key saved in dsh's Models page no longer displaces it. TestDshCustomProvider and TestDshMovesOffDeepSeekRow fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 06:34:13 +08:00
yetone e86a34fce1 migrate: moving back keeps an account's expired sign-in marked, and one refused while the move lists its models goes along marked (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — Zed's, Factory's and MiMo's back write the plugin's sign-in as one just made, so an account the vendor had refused through the plugin came back to the built-in showing signed in until its next refusal; the plugin's mark now goes with it. And a models hook saying a sign-in expired failed the move's strict check, so one refused Zed account stopped the whole move where the built-in would mark it and carry on; it is marked and moves untried, as a lapsed one does. Found walking Zed, Factory and MiMo through a sandbox move and back. TestMoveBackKeepsLapse and TestMoveRefusedWhileListing fail without the change. go vet, go build -tags nogui (GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider ./internal/plugin ./internal/gateway pass. 2026-10-01 06:33:27 +08:00
yetone 31b6257c7e usage: an Antigravity account shows a meter for each model family, not for each model (01huadalang on Discord: 谷歌的反重力如果登录多个账号显示得很丑…low mid 和 high 都要显示,这样好臃肿) — Antigravity gives a quota for each model it lists (13 in its recorded list), and the Usage page drew them all, 9–13 meters an account, while the panel and the account rows showed the first 3 or 2 by id. Each window now carries its family (Gemini, Claude, GPT-OSS…), and the Usage page, the panel and the account rows show one meter a family, the most used of its models (the soonest reset on a tie); its tooltip lists every model, and "Every model" swaps the account's meters in place without moving the page. Windows, routing and the model filter are unchanged; cards without families look as before. TestAntigravityQuotaFamilies and quota-families.test.cjs (Chromium, WebKit, en/zh) fail without the change; click-scroll, old-webkit, tray-usages, claude-resets, codex-auto-reset pass. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass. 2026-10-01 06:19:25 +08:00
yetone b00387f9db provider: a Command Code Go account lists the models Command Code has for Go, not ten (01huadalang on Discord: command 账号登录只有十个模型,正确的应该是几十个) — for Go, magpie never asked for a list and showed a table of 10, on the word that Command Code has none; its Provider API's list answers without a key (86 models), and its CLI (1.73.0) offers Go every model its picker shows but the premium ones and those it blocks for Go. A Go account now fetches that list without its key, less the 30 refused, and saves it (56 today); the table, the CLI's whole Go set (57), stands in until then or when the fetch fails. Requests still go to /alpha/generate. TestCommandCodeGoModels fails without the change (10 models). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider pass. 2026-10-01 06:15:04 +08:00
yetone 78c32292e4 plugins: a models hook can say its account's sign-in expired, as a built-in whose model list the vendor refused marked the account (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — a refusal in provider.models was only logged, so an account Zed or Qoder turned away while listing its models stayed unmarked where the built-in marked it. An error thrown with signIn: expired, kept or renewed now tells magpie, which reads it as it does an answer's X-Magpie-Sign-In. TestPluginModelsSayExpired fails without the change. go vet, go build -tags nogui (GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider ./internal/plugin ./internal/gateway pass. 2026-10-01 06:12:11 +08:00
yetone 475a55a702 migrate: built-ins move onto plugins that fail and mark the sign-in as they did — each mover now asks the plugin version that answers with its built-in's status and words and says through X-Magpie-Sign-In when the account's sign-in expired, stayed or renewed, in requests and usage reads alike: Command Code, Cursor, Devin, Kiro, MiMo, WorkBuddy and Zed 0.1.5, Factory and ZCode 0.1.4, Grok and Qoder 0.1.3, each published and installed from npm before this. go vet, go build -tags nogui (GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider ./internal/gateway ./internal/plugin pass. 2026-10-01 06:04:01 +08:00
lyn.eulerandClaude e3b6397abe gateway: Kiro is sent the images a tool gave back, and tool names longer than 64 characters (#369)
A tool result's images (Claude Code's Read of a screenshot or a render) were
dropped on the way to Kiro, whose tool results carry text alone, so the model
saw "(no output)"; they now go with the message that answers the call, as a
user's own images do. And a tool name Kiro doesn't take (longer than 64
characters, as an MCP server's prefixed names can be: Vercel's plugin has
several) was sent as it was, which Kiro turns the whole request down for once
the history holds a call ("Invalid tool use format", REQUEST_BODY_INVALID);
it is cut and told apart by a hash, the same each time, and a call Kiro makes
by that name comes back by the caller's.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-01 05:37:11 +08:00
ArvakandClaude Sonnet 5.5 f1906bca6a gateway: draw and film through a Grok subscription (Grok Imagine) (#360)
* gateway: draw through a Grok subscription (Grok Imagine)

A Grok subscription (SuperGrok, X Premium+) could not draw: Drawers() returned
nothing for it and draw() refused with "Grok (SuperGrok) can't draw: magpie
draws only through an OpenAI-compatible API, and Grok (SuperGrok) has none".
Grok Build's own image_gen / image_edit tools call the backend's Imagine API
at <cli-chat-proxy.grok.com/v1>/images/generations and /images/edits with the
CLI's sign-in, which magpie already uses for chat.

- drawsGrok / grokDrawers: a Grok account draws with grok-imagine-image and
  grok-imagine-image-quality, like a ChatGPT account does with GPT Image
- the Imagine API takes an aspect ratio, not a size or quality: size
  WIDTHxHEIGHT is matched to the nearest ratio it accepts (aspectAmong,
  aspectOf is unchanged); the image is asked for as base64
- an edit is JSON with the images as data URLs
- a plan's images cost nothing extra, so AutoDrawer ranks Grok with ChatGPT
  (ChatGPT still wins a tie on release date)
- provider.grokBase is exported as GrokBase, like CodexBase, for the test

Tested against a real SuperGrok account through the gateway: generation,
16:9, the quality model and an edit. Video is not covered: the Grok CLI's
video tools need zero data retention off or a storage bucket.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gateway: film through a Grok subscription, and a generate_video tool for agents

A Grok account that draws (c6578bc) can make videos too: Grok Imagine's
video endpoints sit behind the same sign-in.

The gateway speaks OpenAI's Videos API, so its SDK works as it is:
POST /v1/videos, GET /v1/videos/{id}, GET /v1/videos/{id}/content. A
request is taken on at once and polled; the id carries the provider and
the vendor's request id, so nothing is kept in the gateway and a restart
loses no video. Status is mapped (pending at 0% -> queued, pending ->
in_progress, done -> completed, failed/expired -> failed, with why).
seconds (1-15, the vendor's limit, which it says when exceeded), size
(-> aspect ratio and 480p/720p/1080p by the shorter side), input_reference
for image-to-video (URL, data URL, {image_url}, or a multipart file) and
reference_images are taken; a model that isn't a Grok video model is
refused by name. /content answers 409 until the video is ready.

`magpie mcp image` lists a second tool, generate_video: it starts the
video, waits (up to 10 minutes), saves the mp4 into the project
(generated-videos/ unless path says where, never over a file) and says
where, so an agent can animate an image it just drew by passing its path.

Checked against a real SuperGrok account: text-to-video, image-to-video
(input_reference), reference_images, both video models, the OpenAI
Python SDK (videos.create/retrieve/download_content), the MCP tool end to
end with two videos at once, and the error paths. Unit tests use a fake
Grok upstream.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gateway: videos follow the Settings' off switch, and an id can't change the vendor's path

Review of the video change:

- Image generation "off" turns off the video a request that names no model
  gets (videomaker, beside drawer), so generate_video says it is off too.
- A video id is put in the vendor's URL with the account's token: its parts
  must be [A-Za-z0-9_-]+ (parseVideoID) and the vendor id is path-escaped, so
  video_grok.x%3Fa=1.1 and %2F answer 404 and ask the vendor nothing.
- The id carries a short mark of the account that started it; after a switch
  the video says it was started with another account instead of the vendor's
  not-found.
- generate_video waits up to ten minutes, and Codex gives a tool a minute:
  magpie-image's Codex entry gets tool_timeout_sec (Goose's timeout) of
  eleven minutes, the user's own value kept.
- grok-imagine-video stops at 720p (the vendor turns 1080p away for it; 1.5
  makes 1080p), so a 1080p size is asked at 720p there.
- A model may be named without its provider (grok-imagine-video).
- "seconds": 6.0 is 6; 6.5 is still refused.
- completed_at is left out: the vendor doesn't say when a video finished,
  and a time made up at each poll changed each time.

Checked against a real SuperGrok account: bare model name, seconds 2.0,
1080p on the base model asked at 720p (the mp4 is 1280x720), 1080p accepted
on 1.5, both malformed ids, no completed_at.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gateway: TestVideoModelByItsBareName asks its two models in a fixed order

It checked the body of the second request the upstream got, but asked the
two models by ranging over a map, so it failed about one run in seven
(Ubuntu CI). Ask them from a slice.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* library: an installed magpie-image gets the longer tool timeout on the next sync

magpie-image already ships, so many users have it in Codex and Goose as it was
written before: no tool_timeout_sec in Codex, and the timeout: 300 every Goose
server was given. syncMCP skipped an entry whose command, args and env were
unchanged, so an upgrade left Codex at its one-minute default, and Goose kept
300 as if the user had chosen it.

- mcpFile.behind says an entry lacks the timeout magpie gives the server now:
  Codex's tool_timeout_sec is missing, or Goose's timeout is still exactly the
  300 magpie wrote. syncMCP rewrites such an entry; merged() doesn't keep that
  300 as the user's. A value the user chose (any other number) is left alone.
- Test: an entry as main wrote it is given 660 in both, one the user set stays,
  and a second sync changes nothing. It fails without the change ("changed []").

Also, from the review:
- seconds is validated once in readFilming: a decimal number that is whole and
  at least 1 (hex, inf, nan, underscores, negatives and 0 are refused), and the
  object echoes 6 for 6.0.
- imagemcp: Run's doc comment is above Run again.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 04:59:22 +08:00
yetone 1fa21850a3 plugins: a plugin can say its sign-in renewed, and what a usage read means for it (要保证插件跟内置的所有逻辑都要一模一样) — a built-in whose sign-in renewed took the lapse mark off whatever the request then met, and a plugin could only clear it with a success: X-Magpie-Sign-In: renewed now clears it on any status. A usage read marked a plugin account whenever its error said to sign in again and cleared it on any clean read, where Qoder's, Kiro's and others' built-ins marked none from usage: auth.usage may now say signIn: expired, kept or renewed, taken as the header is; one that doesn't say keeps the old reading. TestPluginSaysSignIn and TestPluginUsageLapse fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gateway ./internal/provider ./internal/plugin pass. 2026-10-01 04:46:24 +08:00
yetone c9d2015e5d plugins: a plugin says apart what its answer means for the sign-in, so it can fail with its built-in's status (当然要做,要保证插件跟内置的所有逻辑都要一模一样) — magpie took a plugin account for lapsed on a 401 alone, so a plugin had to choose between the built-in's status and its lapse mark: Factory's and MiMo's refused renewals were 401s where the built-in answered 502 and marked the account, and Qoder's 403, Zed's retried 401, Kiro's unreachable refresh and Cursor's expired plan answered other than the built-in so as not to mark one it didn't. An answer's X-Magpie-Sign-In now says: expired marks the account lapsed whatever the status, kept leaves it be; without it a 401 marks it and a success clears it, as before. The agent never sees the header. TestPluginSaysSignIn fails without the change. 2026-10-01 04:36:42 +08:00
yetone 614d51799b routing: the Requests and Accounts columns read in order (the user: 这里排版是不是有问题啊?感觉好乱) — side by side, the accounts ran far on down the page beside an empty column under the requests; they now end where the requests do and scroll like them. An account's models were wrapped in among its numbers (tried, answered, last answered), a code chip mid-line; they now take a line of their own under them. A provider's own row said its model twice (heading and chip) and the provider's name twice (card and row); the row is now its model alone. The two-column rule had never matched at all: .rt-col is a container too, so an unnamed @container query was sized against the column (~446px), not the section; the section's container is now named. The requests list is sized in the frame after a resize, since the accounts beside it now follow its height (WebKit reported a ResizeObserver loop otherwise). routing-columns.test.cjs (Chromium, WebKit, en/zh: bottoms level, accounts scroll, models on their own line, a provider's row its model alone, one column below 860px) fails without the change. routing-steady, -busy, -compact, -shape, -flood, -effort-row, -kind, -served, panel-routing, click-scroll, old-webkit pass; go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/gui pass. 2026-10-01 04:36:01 +08:00
yetone b2bcbdc91a plugins: a moved Cursor, Devin, Command Code, Kiro or Zed fails as its built-in did — their plugins' 0.1.4 answer each error with the built-in's status and words (Zed's and Kiro's failures before the answer as a 429/401/403/402/502 rather than a 200 stream, Command Code's and Cursor's bare errors in Go's status text, Devin's without a doubled prefix), so the gateway fails over, rests and marks lapsed as it did for the built-in. A move now needs 0.1.4 of them, and a magpie serving the gateway updates one moved before. Each version was installed from npm in a sandbox. 2026-10-01 04:28:04 +08:00
yetone b0a0165fc9 usage: a notification when a window is nearly used up or a balance runs low (#368) — Settings has Usage alert (off, or a share of 1–100%, 80 by default) and Low balance alert (off, or an amount, 5 by default), and magpie quota alert sets them from the command line. Each subscription or plan window, other than one set aside, is told once a run when it reaches the share, and a balance once until topped up past the amount again; what was said is kept, so a restart doesn't say it again, and a failed or stale reading neither alerts nor clears. The system's notifications are started only when first needed, so a Mac build that can't have them or a Linux desktop without a daemon still starts, and Settings says when they are refused or unavailable; a click opens the Usage page. TestDueAlerts and friends, TestAlertText and usage-alert.test.cjs fail without the change. 2026-10-01 04:16:28 +08:00
yetone 09e7a3f147 signin: a sign-in whose browser can't reach magpie finishes with the address pasted (Jorben on Discord: ChatGPT sign-in in Docker on a server never finished) — ChatGPT sends the browser back only to localhost:1455, which is the user's own computer, not the server, and magpie's paste field refused every built-in sign-in. Claude, Codex, Devin, Kiro (both steps of an AWS sign-in), Gemini, Antigravity and Zed now take the address the browser ended on, in the app and in magpie accounts add: only this sign-in's own port, path and state, traded through the same handler as the browser's, and once, whichever comes first. TestCodexSignInFromPastedAddress, TestPastedAddressAndCallbackFinishOnce, TestLoopbackSignInsTakePastedAddress and signin-paste-codex.test.cjs fail without the change. 2026-10-01 04:09:16 +08:00
yetone b3f0af602e gui: a moved built-in signs in from where it was, as it did (插件版…保持一模一样) — moved onto its plugin, a built-in left Subscriptions for "From plugins" and asked which of the plugin's ways to sign in where the built-in had one click; ZCode lost its site question (Z.ai or BigModel), Try again after a failed sign-in asked the way again, and Cursor, which kept one account built in, still read "keeps one account" though its plugin keeps more. It now stays under Subscriptions with its hint and site question, takes the plugin's way for the built-in's click (ZCode's for the site picked), tries the same way again, and keeps as many accounts as the plugin does. plugin-moved-signin.test.cjs (Chromium, WebKit, en/zh) fails without the change; plugin-signin, plugin-move, factory-sub, add-sheet, qoder-cn-tile, zed-sub, mimo-sub, commandcode-risk, old-webkit pass (zcode-site fails on the base too). 2026-10-01 04:04:24 +08:00
yetone 7d9f4458da plugins: a moved built-in's sign-in goes as the built-in's did (插件版…保持一模一样) — through its plugin, Devin, Grok and Cursor started with no CLI on the machine and failed ("install Grok Build first") where the built-in installed it; the code Factory's page asks to confirm was only in the instructions' text, not in the box to copy; and the sign-in never said the account was the one in use, so a first account read "added — switch to it any time". The CLI is now installed first as the built-in's was (Cancel stops the installer), the code in the instructions is given to copy, and the account in use says so. TestMovedSignInAsBuiltIn fails without the change. 2026-10-01 04:04:24 +08:00
yetone 04722aa9d5 gui: a moved built-in's editor shows no plugin:// endpoints (插件版…保持一模一样) — Zed moved onto its plugin listed plugin://zed/v1 under Endpoints with a Test button that could only fail; a plugin's provider is reached inside magpie, so neither shows. plugin-move.test.cjs (Chromium, WebKit, en/zh) fails without the change. 2026-10-01 04:04:23 +08:00
yetone 2ca1373db5 plugins: removing or switching off the plugin a built-in is moved onto moves the built-in back first (插件版…保持一模一样) — the provider went with the plugin, its accounts out of sight in plugin-auth.json; now its accounts go back to the built-in, then the plugin is removed or switched off, in the app and with magpie plugin rm|off. Installed marks such a plugin ("Serves Zed", a dot, no stripe) and asks in its row, not a dialog, naming what goes back; only a yes sends it. TestReleasePlugin and plugin-release.test.cjs (Chromium, WebKit, en/zh) fail without the change; plugin-market, click-scroll, old-webkit pass. 2026-10-01 04:04:23 +08:00
yetone f2de03a66b gui: moving a built-in onto its plugin, or back, runs to the end though the page goes (插件版…保持一模一样) — the move ran on the page's request, so closing or reloading the page stopped it halfway, the accounts in neither place until it was run again; it now runs on its own, for ten minutes at most. TestMoveOutlivesRequest fails without the change. 2026-10-01 04:04:23 +08:00
yetone 140b6161bc cli: magpie provider shows a moved built-in as the built-in read (插件版…保持一模一样) — Zed moved onto its plugin said its account came "from plugin's own sign-in" and listed plugin://zed endpoints no one can reach; it now says zed's own sign-in (another plugin's provider its name's sign-in) and leaves the plugin:// URLs out. TestShowMovedProvider fails without the change. 2026-10-01 04:04:22 +08:00