c06a550b73680289d5a94de7a412356d4040297d
315
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
54c3cb06fc |
agent: the default thinking effort set on one of magpie's models in WorkBuddy stays through a sync, as it does in CodeBuddy Code, ZCode, Kimi Code and MiniMax Code (悠悠哥 on Discord: 供应商能设置模型默认的思考强度吗,我的workbuddy每次手动设置了思考强度,还是会被更新覆盖掉)
WorkBuddy's Settings > Models saves an edited custom model whole into ~/.workbuddy/models.json: reasoning.defaultEffort (absent for its Auto), and the vendor of the provider it matches, which for magpie's entries is the "Custom" provider's (WorkBuddy 5.x app.asar: inferProviderIdFromLocalCustomModel, the form's save in ui-docs-viewer). buddyWrite knew its entries by vendor "magpie" only, so a sync kept the edited entry as the user's and added magpie's own with the same id and magpie's default effort; one set by hand under vendor magpie was simply rewritten. buddyWrite (WorkBuddy and CodeBuddy Code) now also knows magpie's entries by their key (magpie-<agent>, or a NAS magpie's key already on its entries), and keeps each model's defaultEffort (or the older reasoning.effort) while the model still offers that level; WorkBuddy's Auto stays Auto. A new model gets magpie's default; a model magpie no longer has is taken out, the edited one included. The same rule (keptEffort) keeps a hand-set default in ZCode's config.json (defaultVariant), Kimi Code's config.toml (default_effort) and MiniMax Code's config.yaml (thinking.defaultEffort), which a sync rewrote too. The composer's own effort pick is WorkBuddy's localStorage (cb-effort:by-model), which magpie never touched. Verified: kept_effort_test.go (TestWorkBuddy/CodeBuddy/ZCode/Kimi/MiniMax KeepsTheUsersDefault*) fail on the old writers, WorkBuddy's with "think/wide twice" and the rest with magpie's default back; pass with this, -race -count=3. go test -tags nogui ./... green under a temp HOME; go vet, GOOS=linux and GOOS=windows builds. Not tried in the real WorkBuddy app: the fixture follows its save code, not a file it wrote. |
||
|
|
1dceda18a1 |
agent: taking magpie out of a running OpenHanako 1.0 puts its agent on one of OpenHanako's own models, as provider/id (#1040: "model: OpenHanako: 400 Bad Request /api/agents/xiaojing/config: models.chat requires provider/id")
OpenHanako 1.0.0-beta's PUT /api/agents/:id/config takes models.chat only as a model it has, with both provider and id (its Uh + requireModelAvailable): no model or an id alone is a 400. Turning magpie off sent back what magpie had stashed, and with nothing stashed (the agent was put on magpie in OpenHanako, or under another agent than the stash key's, as the reporter's xiaojing) that was null, so the reset failed and magpie's provider stayed. A stashed id alone, or a model whose provider has gone, failed the same way. Now, while OpenHanako runs, the reset reads its models (GET /api/models) and puts the agent back on the stashed model when it has it (an id alone found by its id), else on the first model of OpenHanako's own providers; with none but magpie's it says to add a provider in OpenHanako first and changes nothing. The stash is dropped only once the model is back. With OpenHanako closed the files are written as before. TestHanakoLiveBackNeedsProviderID (a fake that answers as 1.0's server does: nothing stashed, an id alone, a provider/id whose id has a slash, a provider gone, only magpie's models) fails on the old code with the reported "models.chat requires provider/id" and passes. Against the real OpenHanako 1.0.0-beta server (from HanaAgent.app's seed, in a sandbox HANA_HOME on port 3519, with a deepseek provider): magpie connects and disconnects; with the stash lost the old code's disconnect gives the reporter's 400 and the new one puts the agent on deepseek/deepseek-chat and removes magpie. Not tried with the desktop app's sealed catalog or on the reporter's machine. go test -race ./internal/agent, vet (darwin, linux, windows) and gofmt pass. |
||
|
|
9d84443086 |
test: a profile with no catalog to write back from is still said (#1037)
yetone's review of #1029 asked for this branch: `canWriteBack` is false when magpie has no catalog, and the loop over the other profiles must then say a profile whose start names one of magpie's models, not pass it over in silence. Setting `canWriteBack` to true and dropping the guard both keep every other test green, so nothing covered it. TestDshCheckSaysWhenNoCatalogAndAProfilesStartIsMagpies is the case: no provider saved (no catalog), web wired with magpie's route and start, and a cli profile stripped of the route with its start still on one of magpie's. It passes here and fails with the guard mutated either way. |
||
|
|
e2f1c6bacf |
agent: OpenHanako 1.0's encrypted provider-catalog.json is never written over; magpie's address and models are kept in its plugin file (owner: "~/.hanako/provider-catalog.json: top level is not a JSON object")
OpenHanako 1.0.0-beta seals provider-catalog.json under a key its desktop app keeps in the keychain ("HANA-SECRET-1.AES-256-GCM.<nonce>.<body>", shared/credential-envelope.ts); its own compatibility note says a reader without the key must refuse a sealed file. With OpenHanako closed, magpie's Sync couldn't read the key out of it, took magpie's entry for changed, and edit.SetJSON failed on the file. Now, when the catalog is sealed:
- Sync and a model pick keep magpie's definition (address, api, models) current in provider-plugins/magpie/providers/magpie.json, where OpenHanako has moved it (everything but the key and headers, which stay in the catalog: wun's overlay), and the key, unreadable, counts as unknown, not changed, in Sync and Check; Check names magpie.json for the address.
- Adding magpie with no plugin file, or taking it out, needs the key in the catalog: with OpenHanako closed it says "open OpenHanako and try again" and changes nothing (taking out refuses before the agent's model is put back). While it runs, its API does it all, as before.
TestHanakoSealed (a catalog sealed the way OpenHanako seals it, a plugin on an old port with an old model) fails on the old code with the reported "provider-catalog.json: top level is not a JSON object" and passes; a copy of the owner's ~/.hanako (sealed by 1.0.0-beta) syncs with no error, its catalog's bytes unchanged and the plugin's address and models current, Check then empty. go test -race ./internal/agent, vet (darwin, linux, windows) and gofmt pass.
|
||
|
|
a0cc4562e7 |
agent: a dsh patch list that lost its route to dsh's own save is written back quietly (#1029)
dsh reads a patch list live and keeps the model its sessions start on across its own saves of the file (its desktop app writes the list anew from a snapshot as it saves), but such a save has been seen to take magpie's route out while keeping the start: the providers beside magpie's stayed, the start still named magpie's, and the row went red "no longer goes through magpie" until Apply again — which the next save reddened again. The start naming a model of magpie's is what says magpie was not taken off: taking it off is the switch's doing and it takes the start with it, and a list whose start is dsh's own or the user's stays as it is. So dshRouteAgain writes the route back beside the user's own where the start survives it, and dshCheck says nothing for the loss where a round can repair it — said with no catalog to write from, it stands as before. yetone's review of #1029: the home layer is only written into where it already holds an llm-pi-ai entry of its own — its config replaces every profile's whole entry (dshOver), so one magpie added there would take the user's own providers out of dsh's list (#804) — and the loop over the other profiles no longer skips itself with no catalog: only a profile the round can write back is passed over, and one taken off magpie for real is still said. A list still on the old wiring is dshMove's, not the round's to write beside. Co-authored-by: wizzy-yang <wizzy-yang@users.noreply.github.com> |
||
|
|
a334fe091c | gateway: a request a proxy or tunnel on this computer forwards to loopback is answered as one from another machine, and needs the gateway key (#1022, znjhahaha: asked for a Cloudflare Tunnel in magpie, with the gateway key checked for requests through it) — local() was the request's address alone, so cloudflared, ngrok, Tailscale serve or funnel, frp's HTTP proxy, or a Caddy or nginx here, all reaching 127.0.0.1:3425 for clients anywhere, were taken for this computer's agents: whoever had the tunnel's address used the user's subscriptions with any token, read /v1/magpie/quotas (the accounts and their balances) and the remote cards, and their usage was counted as local, all without sharing on. reference.md said a proxy here had to sign its clients in itself; a quick tunnel signs in nobody. A request on loopback carrying a forwarding header (Forwarded, X-Forwarded-For, X-Real-IP, Cf-Connecting-IP, True-Client-IP, Tailscale-User-Login), which no agent sends, is now proxied, and local() is false for it, so every check built on local() takes it as from the network: lanGuard refuses it while magpie isn't shared, saying it came through a proxy or tunnel and that Settings → Share on local network and a gateway key let it in; shared, it needs an enabled gateway key and is then sharedWith, as a LAN client is, for the model APIs, quotas, remote cards, concurrency, session route and MCP sign-ins; the Omarchy theme and the Codex title link stay this machine's only, and its usage is no longer marked local. A gateway MAGPIE_ADDR opens to the network stays open to a tunnel as to anyone; one MAGPIE_ADDR keeps on loopback is closed to it. MAGPIE_TRUST_PROXY=1 says a proxy here signs its clients in itself and keeps what it forwards local, as before (listed in agentenv_test's notAnAgent). A tunnel forwarding bare TCP (frp tcp, ssh -R, socat) adds no header and can't be told from an agent: reference.md says to point one at the LAN address with sharing on. Running cloudflared from magpie with a pasted Tunnel Token is not done here: magpie would have to download or run a program it doesn't ship; reference.md says how to serve magpie through a Cloudflare Tunnel today. TestProxiedLoopbackNeedsTheKey (from 127.0.0.1 with cloudflared's, ngrok's, nginx's, RFC 7239's, Akamai's and Tailscale serve's headers: refused unshared, 401 shared without a key, with magpie's token or a wrong key, let in with the key under its identity, a disabled key refused; an agent on loopback let in with any token; MAGPIE_TRUST_PROXY; MAGPIE_ADDR on loopback and open) and TestProxiedLoopbackRoutes (quotas, quotas history, models, Omarchy and /mcp through a tunnel, unshared and shared, with and without the key) fail on main (200 for every tunnel unshared) and pass -race -count=10 with TestLANGuard, TestQuotasOverLAN, TestManagedLANGuard and TestExplicitOpenGatewayAcceptsAllRemoteTokens; go test -race -tags nogui ./internal/gateway (579 s, under -timeout 30m) and go test -tags nogui ./internal/... under a temp HOME pass; go vet (darwin, windows), gofmt -l, linux and windows (nogui) builds pass. No GUI change. Not tried with a real cloudflared (not installed here); its Cf-Connecting-IP and X-Forwarded-For are what Cloudflare documents it sends. | ||
|
|
0fb669175b |
agent: fx is found by its folder, and omp isn't found by Pi's (an fx on PATH is often antonmedv/fx, the JSON viewer; PI_CODING_AGENT_DIR pointed omp at Pi's folder, so a machine with only Pi showed omp as installed) (#1021)
fx no longer has a command to look for: `fx` is a common name, and WSL already went by its folder alone. It is detected when ~/.fx is a folder. omp reads PI_CODING_AGENT_DIR as Pi does, so its folder may be Pi's. ompDir now says when the folder was moved that way (shared), and such a folder alone is no sign of omp: its config.yml, its command, or its own ~/.omp/agent is. It keeps its Bin, so CLIMissing still offers it again. TestFxDetected (an fx command alone, a file named .fx, a .fx folder) and TestOmpDetectedWithPiDir (Pi's folder, the omp command, config.yml, ~/.omp/agent) fail without the change and pass. go test ./internal/agent, go vet, gofmt -l pass. |
||
|
|
588e51ea4c | agent, gui: an agent connected by its config to an address where nothing answers is told, not shown connected (#1013, cjyrainbow: Codex Desktop's WSL environment reads Windows' ~/.codex/config.toml, whose openai_base_url and [model_providers.magpie].base_url the reporter pointed at Windows as WSL sees it, forwarded to 127.0.0.1:3425 by a portproxy of their own; after a reboot the portproxy listened no more and the Agents page still said Codex was connected) — Drift looked only at the config, which was right. Now an agent pointed at an address off loopback (this machine's Codex at an address the user kept, #816, through Agent.reach; every WSL twin under NAT, at Windows as the distro sees it, through Agent.Gateway) has that address tried from here (reach.go: GET /v1/models, any HTTP status an answer, 2 s timeout, in the background, at most every 20 s per address, never waited for), and one that doesn't answer while the gateway answers on loopback is a new drift, "unreachable", with Addr and the advice in Detail: under WSL (or on Windows) magpie with Settings › Share on local network on, or the user's netsh interface portproxy, which may need setting up again after a restart; Windows' firewall allowing the port for the WSL network; on Windows 11, networkingMode=mirrored; elsewhere, the forward to start again or gateway.URL() to point at. An answer not had yet, and a gateway down on loopback too, are no drift. The config is never changed for it, except where the address is one a WSL distro reached Windows at before (distro.Was, the last 4 kept in wsl.json as a re-probe finds Gateway changed) and the distro reaches Windows at another now: Drift's Move names that one, and Reapply moves the config there (Agent.move; Codex's codexMoveGateway rewrites openai_base_url and magpie's own provider's base_url, only where they begin with the old address). An address no distro was seen at is the user's own and is never offered a move. In the GUI the row's line says "{agent} is set up, but nothing answers at the address it reaches magpie by" in red with the advice as its tooltip, and its pill is "How to fix" (an i, not the reapply arrow), which opens the advice in a dialog (too long for the status line; it wraps and can be copied) and sets nothing — or "Use <address>" (the existing "Use {url}" string) when there is a Move, which reapplies; two new t() strings with zh, ja and de. The probe runs from Windows, not from inside WSL, so a firewall keeping WSL out isn't seen; the advice names it. TestKeptAddressNotAnswering (the reporter's case: Codex at 172.28.96.1, unreachable with the address and loopback in the detail; no drift once it answers, none with the gateway down too), TestKeptAddressProbeNeverWaitedFor, TestKeptAddressMovedWithWSL (Move from a Was address, Reapply rewrites both URLs, Check empty and no drift after; a 10.0.0.7 address is told but never moved) and TestWSLAgentAddressNotAnswering (Codex in a NAT distro told, with Share on local network, portproxy and firewall; a mirrored one never probed) fail without the Drift change and pass; TestLoopbackNotProbed, TestReachProbe (httptest: a 401 is an answer, a closed port none) and TestWSLWas pass. TestMain stubs reachProbe so no test dials out. -race -count=10 on them; go test -tags nogui ./internal/... under a temp HOME, go vet (darwin, windows), gofmt -l, linux and windows (nogui) builds pass. agent-unreachable.test.cjs (the window at 560px and the tray panel at 440px: the red line and tooltip, How to fix's dialog posting nothing, Use <address> posting the reapply, no sideways scroll) fails on main and passes on Chromium and WebKit in en, zh, ja and de; agent-connect, agent-layout, agent-field-names, aside-native, gui-de, gui-ja and i18n-keys pass on both. Not tried on a real WSL NAT setup (the Windows test box didn't answer ssh). docs/subsystems/agent-wiring.md says it. | ||
|
|
c5a54bc74b | agent: VSCodium's magpie group is written for the provider its installed Copilot Chat declares, OpenAI Compatible (customoai) on the Marketplace's 0.48.1 (#1014, xybio: VSCodium's Chat picker showed only Auto) — VSCodium has no Copilot Chat of its own, and the last Marketplace Copilot Chat it can install, 0.48.1, declares no Custom Endpoint provider (its contributes.languageModelChatProviders: copilot, copilotcli, claude-code, anthropic, xai, gemini, openrouter, openai, ollama, customoai, azure), so the customendpoint group magpie wrote in chatLanguageModels.json was never read. magpie now reads the package.json of the github.copilot-chat installed under ~/.vscode-oss/extensions (vscodeChatOf: the one extensions.json lists, else the highest github.copilot-chat-<version> folder not in .obsolete) and writes customendpoint when it is declared, customoai when only that is: each model's window as maxInputTokens beside maxOutputTokens (0.48.1 adds them up), and the token as x-api-key, since 0.48.1 drops an Authorization in requestHeaders and its apiKey has to be a ${input:} secret; the gateway already takes a key from x-api-key, past an empty Authorization, so Usage still counts VSCodium. No extension found, or a package.json that can't be read, keeps customendpoint as before; one declaring neither is said in Notice and Check. Writing the group removes magpie's group for the other provider in the default's and each profile's file, and Disconnect removes both; the user's own groups, under either provider, stay. Sync (at magpie's start and on catalog changes) rewrites a group written for the other provider, and Check reports it until then. VS Code and Insiders, whose chat is built in, keep customendpoint unchanged. The Notice says 0.48.1 registers its providers only while signed in to GitHub with a personal Copilot plan (its BYOK registration is gated on the Copilot token's individual/BYOK flag). TestVSCodiumCopilotChatOpenAICompatible (the real 0.48.1 provider list installed in a temp ~/.vscode-oss: Connect writes customoai with x-api-key magpie-vscodium and no Authorization or contextWindow, the user's groups kept, Check empty; a Custom Endpoint group an older magpie wrote is flagged and rewritten by Sync; a Copilot Chat declaring customendpoint switches it back; one declaring neither is reported; Disconnect removes both) and TestVSCodeChatInstalledWithoutExtensionsJSON (folder scan, .obsolete, unreadable package.json) fail on main, which writes customendpoint, and pass; -race -count=10 on the VS Code tests, go test -tags nogui ./internal/..., go vet, gofmt -l, linux and windows (nogui) builds pass. No GUI change. Not tried with a real VSCodium. docs/subsystems/agent-wiring.md and docs/reference.md say it. | ||
|
|
5361a2178e | agent, gateway: Cursor Private Inference's row has an effort, which magpie's gateway asks its requests for (#1003, gjf7) — other agents' rows pick a reasoning effort that magpie writes into their config, but Cursor Private Inference reads only its base URL and key from the environment, and its app offers no Reasoning control for most of magpie's models, so there was no way to set one short of a routing group with a member fixed at a level, which the reporter used as a workaround. Its row now has an effort field like the others': the default (as Cursor asks) first, then every level the models it lists have (cursorLocalEfforts: their levels in MemberEfforts order, low/medium/high where a model reasons with levels unknown, none at all when nothing it lists reasons or it isn't connected). The pick is kept in magpie, settings' agentEfforts by agent (provider.AgentEffort / SetAgentEffort, a level of MemberEfforts or "" for none), and the gateway's candidate loop asks a cursor-local request for it with withFixedEffort, fitted to the model's levels and bounded by a group's, on a model that reasons (the new Provider.Thinks: levels, its list's Reasoning, or models.dev's). Precedence: a group member's fixed effort, then a :effort suffix, then a routing rule's turn pick, then the agent's pick, then the request's own; the pick wins over a level Cursor sent, as Cursor sends its own default as often as a choice, and Default hands the choice back to Cursor. A model that doesn't reason and every other agent's requests go as they came. The Settings page's save carries agentEfforts over as it does fastPicks (TestSettingsSaveKeepsWhatItDoesNotSend's fixture names it). In the window the field sits under "Settings" rather than "New sessions" for this agent alone, as it applies to every request from then on, not to what a session starts on; no new t() strings (Settings, effort and default are translated in zh, ja and de already). TestAgentEffortOnCursorLocal (Chat and Responses with and without a level of their own, Claude on Messages made to think, OpenCode's request untouched, a non-reasoning model untouched, a member fixed at low kept, the pick taken off) fails without the gateway change and passes; TestCursorLocalEffort covers the options (none with no reasoning model, the union of levels, the low/medium/high fallback, none while disconnected), Apply and a bad level refused; both pass -race (-count=20 gateway, -count=10 agent). cursor-local-effort.test.cjs (the window at 560px and the tray panel at 440px: the field under Settings, the slider's five stops with Default first, a pick posting the field, no sideways scroll) passes on Chromium and WebKit in en, zh, ja and de; claude-effort, subagent-effort, agent-field-names, agent-connect, agent-layout, agent-tweak-fold, agent-models, cursor-local-launch and i18n pass on both. go test -tags nogui ./internal/..., go vet, gofmt -l, linux (nogui) and windows builds pass. Not tried with the real Cursor Private Inference: whether it sends a reasoning level of its own on models it shows a Reasoning control for is unverified, which the precedence makes moot. docs/subsystems/gateway-routing.md and agent-wiring.md say it. | ||
|
|
a6d32e9abf |
provider: one GroupIDOf reads a group's id from a ref, in place of five copies of the same CutPrefix(TrimSuffix(TrimSpace(ref), "[1m]"), GroupPrefix) — #1000 (6f391bc8) taught the [1m] mark to each group lookup by pasting the expression into AutoStandIn, GroupFinder and DisabledGroup in group.go, groups_cli.go and the agent's reseat.go; a sixth lookup would have to find and copy it again. Behaviour is unchanged. go test ./internal/provider/ ./internal/agent/ and the root package pass, as do the gui and gateway Group/1m tests; build and vet pass.
|
||
|
|
efd501ec74 |
agent: add Reasonix Studio with separate Executor and Plan models (#427)
* feat(agent): add Reasonix Studio 2.x integration
* agent: select Reasonix planner independently from executor
* agent: restore Reasonix when its credential file was initially absent
* agent: preserve Reasonix planner choices and model controls
Keep configured and saved native planner references selectable, and retain full model picker behavior and role labels. Exercise native planner restoration and both browser layouts. Separate temporary CLI first-launch work from version and intentional timeout assertions.
* test(gui): follow the current Reasonix connection flow
* test(gui): open connected Reasonix details before checking roles
* agentenv: isolate Reasonix configuration in test sandboxes
* gui: restore Reasonix roles without disconnecting the other
* gui: Reasonix's role labels read in every language, and its confirm names them
The review found four things on the Reasonix roles' strings.
"executor" and "planner" are field labels like any other, so t() translates
them; they had no i18n entry in any language, so the zh, ja and de windows
showed the English key. They now read 执行模型/规划模型, 実行モデル/計画モデル
and Executor-Modell/Planner-Modell, beside model/effort/thinking in each block.
The confirm said "neither Executor nor Plan" — names that appear nowhere in
the UI — and its zh text said "Executor 和 Plan", while the fields are labelled
executor and planner. It now names the labels, and each language names its own
translation of them (zh 执行模型和规划模型, ja 実行モデルも計画モデル, de vom
Executor- bzw. Planner-Modell).
Both new strings had a zh entry and no ja or de entry, which is what turned
gui-ja and gui-de red: every zh key needs its ja and de with the same
placeholders. "restore the previous {field} selection" also started at column
0 in i18n.js; it is indented with the lines around it now.
reasonix-roles.test.cjs asserts the rendered label, so its zh expectations name
执行模型/规划模型; the en ones stay as they were, the en label being the key.
Its en assertions on the confirm's first sentence are unchanged, and the
reworded second sentence is not asserted.
Verified on this head: gui-ja passes 3/3 and gui-de 3/3 (every zh string has
its ja and de, same placeholders and tags); reasonix-roles.test.cjs passes 8/8
in Chromium and WebKit, en and zh, window (1000px) and panel (440px); go vet
./... and the four builds pass. The rest of internal/gui/tests was run and
compared against a clean main worktree at
|
||
|
|
6f391bc8f2 |
provider, agent, cli: a routing group's id carries Claude Code's [1m] mark (#1000)
wherever a ref is read back from an agent's settings An agent on a routing group is shown, on the Providers page, on each of that group's providers — the row's middle holds one button per agent, and `uses` is what the Routing page's "n agents routed through it" counts. A group whose model has a 1M window is written into Claude Code's settings with the mark Claude Code itself puts on it (`group/auto-deepseek-v4-1-flash[1m]`), and GroupFor takes the mark off for the gateway; the rest did not. So the group was not found, and an agent Claude Code had on it was listed on none of the group's providers, while every request it made went through them. Four places read such a ref back: - provider.GroupFinder, which the Providers page asks (agentUses) - agent.sameModel, which moves an agent whose group is gone to the same model from a provider still serving it — the mark left on made the comparison against AutoGroupOf find nothing, and the agent stayed on a group nobody serves - agent.groupNamed, which says whether two values of a field reach the same group, one of them read back from the agent's own settings - the CLI's groupUses, which lists "← agent" beside a group: the marked id was filed under one no group has, and Claude Code was on no row Each takes the mark off, as GroupFor does. A group's own members are not touched: those are the group's structure, never a ref from an agent's settings, and a [1m] there is a model's, not the group's. group_uses_test covers the page (a group found with and without the mark, and the row naming the agent on the group's provider); applied_test covers groupNamed; reseat_test covers the move (the marked group ref finds the same model elsewhere, and a group that is nobody else's still gives nothing); groups_cli_test covers the CLI, with Claude Code's settings.json as the reporter's reads. Each fails without its fix. go build, go vet pass; go test ./internal/agent/ passes, and the failures that remain are the ones a clean origin/main has too (Aside's symlinks, ~/.codex dependence, VSCode's launchers, a flaky Zed case). Co-authored-by: wizzy-yang <wizzy-yang@users.noreply.github.com> |
||
|
|
a711f428ad |
agent: CodeBuddy Code (Tencent's CLI, codebuddy/cbc, the China edition too) is an agent that uses magpie's models (#980, yujianbinm) — the CLI (npm @tencent-ai/codebuddy-code 2.161.4, one package for codebuddy.ai and codebuddy.cn, CODEBUDDY_INTERNET_ENVIRONMENT=internal) takes models of one's own from ~/.codebuddy/models.json ($CODEBUDDY_CONFIG_DIR), the format WorkBuddy reads, hot-reloaded, and starts a session on settings.json's "model". magpie now writes its models there as WorkBuddy's are written (vendor magpie, key magpie-codebuddy, url the gateway's /v1/chat/completions), keeping the user's own entries, their order, a model turned off and availableModels (magpie's ids added when the list is there), and sets "model" to the pick, the user's other settings kept; one of CodeBuddy's own models, or default, takes magpie's models out again. A new models.json is {"models":[…]} as CodeBuddy's docs give it. workbuddyWrite becomes buddyWrite(agent), the same writer for both. CODEBUDDY_CONFIG_DIR joins agentenv.Vars; cliupdate knows the npm package. TestCodeBuddyCode (user's model, availableModels and settings kept; magpie pick; sync; back to own; default) and TestCodeBuddyCodeNewFile fail without the agent ("no codebuddy agent") and pass; -race -count=5 on the CodeBuddy/WorkBuddy tests. End to end, the real CLI 2.161.4 under a temp HOME with CODEBUDDY_INTERNET_ENVIRONMENT=internal and no CodeBuddy sign-in: magpie codebuddy magpie/mock/pro against a sandbox gateway on 3462, then codebuddy -p with no --model, answered through the gateway from a mock upstream, and magpie's usage counted it under CodeBuddy Code (the CLI sends "CLI/2.161.4 CodeBuddy/2.161.4", so it is told by its key); magpie codebuddy default took it out. A bare-list models.json is read by the CLI too. Not tried with a real CodeBuddy account or a real model vendor. go test ./internal/agent/ ./internal/agentenv/ ./internal/usage/ ., go vet, darwin, linux (nogui) and windows builds pass.
|
||
|
|
a7c4e5ddde |
provider: a Codex account the Codex app still sends for keeps Codex signed in — spend cap or overage with credits, limit_reached alone; a hold survives a failed read (#996)
* provider: a Codex account at a spend cap or past its overage with credits left keeps Codex signed in, as the Codex app's composer still sends for it #966 held such an account (and one whose limit_reached alone was true), following the readiness check Tht in ChatGPT.app 26.930. Tht has one caller, the work-credit-restriction-recovery poller; it isn't the composer's gate. The composer's submit is disabled only by hP (search "n.rate_limit?.allowed!==!1||Jpe(n)||SIe(") and hardBlocked (search "hardBlocked:r.rate_limit?.allowed===!1"): both read rate_limit.allowed alone and let credits (has_credits or unlimited) through before any spend cap or overage. So the app sends for that account, and holding it gains nothing: CodexUsedUp only moves Codex to magpie as provider while Codex is on a magpie model, whose turns already reach magpie through openai_base_url. It only takes the app out of ChatGPT (durable threads, remote control), the loss #966 set out to stop. codexHeld is back to that gate: held when allowed is false, no credits, and not a workspace within its overage (no spend cap, an ordinary reason). Its comment names the build, both gates and why Tht isn't one, each with a string to search for. The spend-cap and overage subcases now expect not held; new subcases pin allowed as the only trigger. Five subcases fail on the merged codexHeld. agent-wiring.md and reference.md say the same. Follow-up to #966. * provider: a Sci, Law, FinServ or Enterprise trial/view-only workspace within its overage keeps Codex signed in codexHeld lets a workspace with no credits through while it is within its overage, as the Codex app's composer does, but the workspace list came from codex-rs's PlanType alone. The app's own plan_type switch for its usage banner (ChatGPT.app 26.930.61225, app-initial) also names sci, law, finserv, enterprise_cbp_trial and enterprise_cbp_view_only, so such an account read held and magpie took Codex off its ChatGPT sign-in while the app still sent. They are added; the comment cites that switch. guest there is a personal plan and stays out, and a plan not listed still counts as no workspace (held). Verification: "Law without credits, within its overage" in TestCodexUsedUpIsWhatTheAppHolds fails without the list change (CodexUsedUp = true, want false) and passes with it. * provider: a held Codex reading with no window at 100% stays held through a failed read A reading kept for when /wham/usage fails dropped Held whenever none of its windows read 100%. That is right for a hold that came with a used-up window which has since started again, but a hold with no window used up (the backend says not allowed, no credits, every window under 100%) has no reset to wait for: the first 503 after it read not held and put Codex back on its ChatGPT sign-in for an account its app sends nothing for. Held is now cleared only when the saved reading had a window used up and none is after the passed resets are applied; any other hold stands till a fresh read. Verification: TestCodexHeldWithNoWindowUpKeptTillAFreshRead (the real Pro reply, credits spent, its window at 40%: a good read, then a 503) fails without the change ("CodexUsedUp = false on the kept held reading, no window used up") and passes with it. TestCodexHeldKeptOnlyTillTheReset still passes; both share their setup now. -race -count=10 passes. * agent, gui, docs: Codex's sign-in notes say magpie steps in while the Codex app blocks the account, not whenever it is used up #981's sign-in notes said magpie becomes Codex's provider "while its account is used up". Since codexHeld, a ChatGPT account at 100% with credits left keeps Codex signed in; magpie steps in only while the Codex app blocks the account (out of allowance, no credits left), and the notes now say that, Always ChatGPT's to match, in English, Chinese, Japanese and German. agent-wiring.md's sign-in line and reference.md's Codex paragraph say when the app holds an account as codexHeld reads it: OpenAI no longer allows it and it has no credits left, a workspace still within its overage excepted; at 100% with credits Codex stays signed in. Verification: codex-signin-always (Chromium and WebKit, en/zh/ja/de, 1100 and 440px) 8/8, i18n-keys 1/1, gui-ja 3/3, gui-de 3/3. git grep finds the old English notes nowhere under internal/gui/tests or internal/gui/assets. |
||
|
|
b0aef80f6c |
claude: Claude Code on a magpie model is told the longest reply the model can write, CLAUDE_CODE_MAX_OUTPUT_TOKENS (H20 on Discord: DeepSeek through WorkBuddy stopped at "Claude's response exceeded the 32000 output token maximum")
Claude Code 2.1.291 asks a model it doesn't know (any but Claude's own names) for 32000 tokens at most (dte: default dv=32000, upper dN=128000), whatever the model can write, and raises that error when the reply stops on max_tokens; only CLAUDE_CODE_MAX_OUTPUT_TOKENS raises it (n4t, capped at the upper limit). A reasoning model's thinking counts toward the reply, so DeepSeek V4 on a long turn ran into it with plenty of quota left. magpie's translation doesn't make it worse: max_tokens goes upstream as Claude Code sent it (clamped only for a group's member, to its own limit), and finish_reason "length" maps to max_tokens as it should. Now, as magpie writes CLAUDE_CODE_MAX_CONTEXT_TOKENS, it writes CLAUDE_CODE_MAX_OUTPUT_TOKENS with Claude Code's model: the least known reply limit of the models it runs on (main, tiers, subagents; WorkBuddy's DeepSeek V4 is 128000), since one value serves them all. It is left out when the main model is a Claude one, which Claude Code knows; when one of the others has no known limit; and when the least is no more than 32000. A value the user set, before magpie or over magpie's, is theirs and never touched; magpie's is taken out when Claude Code goes back to Claude's models or off magpie. TestClaudeOutputTokens fails on the old code (mid: "") and passes; go test ./internal/agent/, -race -count=5 on the Claude context/output/compact tests, go vet (darwin, windows) and darwin/linux(nogui)/windows builds pass. Not tried with a real Claude Code on DeepSeek. |
||
|
|
94db53a847 | goose: the model is read and written in the layout Goose's config is in, active_provider and the providers map as well as GOOSE_PROVIDER/GOOSE_MODEL (a follow-up to #987: the reporter's Goose 1.53 has active_provider: magpie and its model in providers.magpie.model) — Goose 1.53 reads active_provider before GOOSE_PROVIDER and the active entry's model before GOOSE_MODEL (get_active_provider/get_active_model, crates/goose/src/config/providers.rs at v1.53.0), and on every write migrates the flat keys into the map and, once a map is there, deletes them (migrate_provider_config/cleanup_legacy_provider_keys, config/migrations.rs; load_write_config, config/base.rs). magpie read and wrote only the flat keys: on a migrated config it showed no model, its Check and the restart notice never saw magpie in use, and a model picked in magpie went beside a map goose read first, so goose kept its own and deleted magpie's at its next write. Now a config with a providers map or active_provider is written as goose's set_active_provider writes it (active_provider, providers.<id>: enabled, model, configured), other providers' entries and the user's other keys and comments kept, stale flat keys removed; an old config keeps the flat keys, which an older goose reads and a newer migrates. Taking Goose off magpie clears active_provider and the flat keys as goose's clear_active_provider does, and drops magpie's own entry with its custom provider. TestGooseModelInEitherLayout (the reporter's layout with another provider; switching to a native model, to magpie's and back off; a stale flat key beside the map; the old layout left old) fails on the old code ("model = \"\", want magpie/zhipu/glm-5.3") and passes; go test ./internal/agent/ ./internal/edit/, -race -count=5 on the Goose/pick/effort tests, go vet (darwin, windows) and darwin/linux(nogui)/windows builds pass. Not tried with a real Goose Desktop. | ||
|
|
8005f17551 |
provider, codex: a ChatGPT account at 100% with credits left keeps Codex signed in (#966)
* provider, codex: a ChatGPT account at 100% with credits left keeps Codex signed in — magpie becomes its provider only while the Codex app itself holds the account magpie made itself Codex's provider (model_provider = "magpie", #540) whenever any of the active ChatGPT account's windows read 100%. A Pro account at 100% on its week with credits left keeps working in the Codex app: the backend answers every turn (about 490 200s in one day's usage log at 100%), yet magpie moved Codex off it, and as a provider without requires_openai_auth the app lost its ChatGPT state ("Sign in to ChatGPT to start a durable thread", no remote control). CodexUsedUp now reads codexHeld, which mirrors the Codex app's own composer block (ChatGPT.app webview bundle, hP and its helpers): the backend says the account isn't allowed (rate_limit.allowed = false), it has no credits (has_credits / unlimited), and it isn't a workspace still within its overage (overage_limit_reached = false, no spend cap, an ordinary rate_limit_reached). Window percent never decides it. Where the app goes by what /wham/usage can't show (a statsig gate, the gpt-reserve fallback, Team's reserve experiment that ignores overage), it counts as held: a wrong move costs ChatGPT extras, a missed one every turn. A reading kept through a failed read (keepLast) stops saying held once its used-up window has started again, as its usage does. NextLogin's account rotation and its thresholds are unchanged. A user stuck on the old move (codex.out = 1) is put back beside the sign-in by the next Sync, as #540 already does once the account has room. Verification: - TestCodexUsedUpIsWhatTheAppHolds (real /wham/usage replies, ids replaced) fails on the old trigger: 7 subcases, "CodexUsedUp = true, want false". Each workspace guard (spend cap, reason, overage, plan list, Team reserve) was mutated and fails its own subcase. - TestCodexHeldKeptOnlyTillTheReset fails without the quota_last change. - The real Pro account (7-day window 100%, ~28k credits, allowed false): CodexUsedUp true before, false after. - go vet ./...; go build (darwin), GOOS=linux -tags nogui, GOOS=windows; go test -tags nogui ./... under a sandbox HOME; -race -count=20 on the new and related Codex tests. - Not run: a live Codex app turn on an account truly held (none at hand), Windows/WSL runtime, GUI suites (no GUI change). * provider: a Codex account at a spend cap or past its overage is held, credits or not; limit_reached counts as not allowed codexHeld let credits through before the spend cap, so a Team seat at its cap (spend_control.reached, allowed false) with has_credits read not held and magpie left Codex on an account the backend refuses. The app's readiness check (26.930.61225, app-primary) returns limited on spend_control.reached or credits.overage_limit_reached before it looks at credits, and treats rate_limit.limit_reached like allowed false; held now reads the same. A spend cap with the rate limit still allowed (a workspace's included usage) stays not held. |
||
|
|
09b235d8b3 | goose: the model list offers Goose's own providers only when this Goose is set up with them (#987, EZN7L2C3: a Goose on magpie alone listed OpenRouter and its hundreds of models, which magpie's show/hide list doesn't have and so couldn't hide) — Goose's model Options always added anthropic, openai, google and openrouter, whatever Goose had, with their models.dev lists. They now come from gooseConfigured, which reads what Goose itself records (block/goose source): the providers map's entries marked configured (set_active_provider, config/providers.rs), legacy <name>_configured markers (migrate_provider_config, config/migrations.rs), active_provider / GOOSE_PROVIDER, and a native provider whose API key is in the environment, which Config::get_secret reads before the keyring. A key only in Goose's keyring isn't read (magpie stays out of the keychain), but every provider Goose has been set up with has its configured entry. No other agent passes providers it doesn't have: Crush passes its config's providers, the rest none. TestGooseOffersOnlyItsConfiguredProviders (the reporter's config: magpie alone → no native groups; a configured entry and a legacy marker → those two, not an unconfigured openai or openrouter; OPENROUTER_API_KEY set → OpenRouter) fails on the old code with all four offered and passes. go test ./internal/agent/, -race -count=5 on the Goose tests, go vet (darwin, windows) and linux/windows builds pass. Not tried with a real Goose Desktop. | ||
|
|
da5f7a8115 |
wsl: background work never touches a WSL distro that has stopped since it was last seen running — it booted it again (TJHHHH on Discord: while they were repairing WSL, magpie kept starting it). Detection already listed only running distros, but the running set was kept for a minute (wslListAge), agents built while a distro ran kept its home and read its files from their Get/Sync/Check/Reached/Routed closures later, the sessions listing kept a distro's Running for 30s+ and read its \\wsl.localhost files, and wslrun.Find reused a tool it had found in a distro for a minute and ran wsl.exe -d on it. Any wsl.exe -d or \\wsl.localhost read boots a stopped distro, so a wsl --shutdown was undone within a minute, and the distro, running again, kept it going. Now wsl.exe -l --running -q (it starts nothing; UTF-16 with or without BOM, or UTF-8 under WSL_UTF8) is asked again after 3s, and a distro not in it is not touched: a WSL agent's Get gives the last values seen, Sync/Check/Reached do nothing and Routed/Joined/Beside/Added say no; sessions aren't listed or read from it (wslCold says cold); wslrun.Find (Claude subscription sign-in and usage) doesn't reuse or run a tool there; the Library gives skills to no target in it and its MCP probe says it isn't running. An explicit action still starts it: setting a WSL agent's field, a Set on a running agent, resuming a WSL session. TestWSLStoppedSinceNotOpened (agent, sessions), TestWSLStoppedSinceNoTarget (library) and TestFindStoppedSince (wslrun) fail without the change and pass; TestUp covers the -l --running parse. On the Windows box (10.0.0.132) a test binary that had seen magpie-fedora running ran Values/Check/Reached/wslDistros/wslAgents/sessions.List/wslrun.Find against it for 4 rounds while it was stopped: it stayed Stopped and wsl -l -v was unchanged; wslrun and agent WSL tests pass there. No distro was terminated for the check (magpie-desktop is in use, the stopped ones aren't ours). go test ./... (nogui), -race on the WSL tests, go vet, windows and linux (nogui) builds pass.
|
||
|
|
5839e08189 |
dsh: the gateway key also goes into dsh's own key store, which the desktop app reads (#969, pan17: in the DeepSeek Harness desktop app every turn failed with llm-pi-ai: no credential for provider route "magpie"; its profile resolves MAGPIE_GATEWAY_KEY, which is not set — store MAGPIE_GATEWAY_KEY through the credentials service (the web Models page writes it) or export it (MISSING_CREDENTIAL), while magpie showed dsh connected) — the route was in the desktop profile (magpie writes every profile; the GUI names only the first, profiles/web, which read as "magpie wrote the npm dsh's config"), but magpie put the key only in ~/.dsh/.env. In dsh 0.2 the .env files are layers the product CLI's boot (dsh-app-boot) loads; dsh-credentials-local resolves inherited environment > $DSH_HOME/.credentials.yaml > project .env > home .env, and "a composition the product CLI did not boot has only the inherited environment as its layer". So the desktop app never saw the key. magpie now also writes refs.MAGPIE_GATEWAY_KEY into ~/.dsh/.credentials.yaml, dsh's own store, which its Models page writes and every dsh reads. Details: a new store is made owner-only, version 1 (dsh refuses a store other users can read); the user's other refs, records and comments are kept; a different key of the user's under magpie's name is left alone (the check already reports it); a store in dsh's pre-release flat layout isn't touched; going back to dsh's own models takes magpie's key out. The check now asks for Apply again when the store lacks the key; a dsh wired by an earlier magpie gets it from the catalog sync magpie runs as it starts. The output of a fresh write and a merge into a store with refs, a grant record and a comment both parse with dsh 0.2.0-rc.2's own parseCredentialsDocument. TestDshDesktopGetsTheKeyFromItsStore (web+desktop profiles as on the reporter's machine) and TestDshStoreOfTheUsersOwn fail without the change and pass; -race -count=5 on the dsh tests. Fixtures that stood for "wired" with .env alone now hold the store too. Not tried with the real desktop app (not installed here; the reporter is on Windows); they're asked to confirm. go test ./internal/agent/, go vet, darwin, linux (nogui) and windows builds pass.
|
||
|
|
c0c3b20b95 |
agent test: OpenHanako's magpie provider names relay/glm-4.6 GLM-4.6, as the catalog names it — e0bf3e61 names a model its provider's list gives only by id as the other providers name it, and the sandbox catalog has zai's glm-4.6 as "GLM-4.6", so TestHanakoFiles and TestHanakoLive (macOS and ubuntu CI on 85fd35ab) still expected "glm-4.6 · Relay". Their expectation and the plugin fixture of a current provider now say "GLM-4.6 · Relay". No app change. go test ./internal/agent/ passes.
|
||
|
|
7f45f45cb5 |
codex: a sign-in kept on ChatGPT when its account is used up (#981)
Codex's sign-in, ChatGPT, keeps Codex signed in to ChatGPT with magpie's models beside its own, but once the ChatGPT account is used up magpie makes itself Codex's provider (#540): the Codex app sends nothing at all on an account it knows is out, a magpie model's turn included. That move went unsaid, and a user who chose ChatGPT expected Codex to stay there. ChatGPT now says it, and a third way, Always ChatGPT (login "chatgpt"), never makes the move, by a pick or by Sync; choosing it puts a Codex moved before back beside the sign-in. What it costs is said beside it: the Codex app may send nothing till the account has room, while Codex CLI's turns still reach magpie by the base URL, its routing and other accounts serving them. |
||
|
|
2f15623e35 | feat: add VSCodium to vscode kinds (#926) | ||
|
|
ac478f5884 | agent: Zed is told which models think (#964) — magpie wrote Zed's openai_compatible available_models without reasoning_effort, and Zed offers its thinking switch and levels only for a model whose reasoning_effort is set and not "none" (open_ai_compatible.rs, default_thinking_reasoning_effort), so no magpie model showed it (jarod). A model that thinks is now written with reasoning_effort: high, or the strongest of its own levels when all fall short of high; only minimal–high are written, since max and none came after Zed 0.233 and a value a Zed doesn't know is a settings error. The gateway fits the level Zed sends to the model's own. A model that doesn't think gets no field, and a reasoning_effort the user set (none included) is kept through Sync and model changes. TestZedReasoningEffort fails without the fix (10 errors) and passes -race -count=20; go test ./internal/agent/, vet, windows/linux builds pass. | ||
|
|
2533ccdaf9 | gui: a model with a fast mode is sent fast, or not, from its row in the agent's picker (#954, Gyarados4157) — Fast was only a routing group member's (Group.Fast), so a GPT on a ChatGPT account picked straight in Codex or OpenCode could only go fast through a group of one. A model provider.CanFast says has a fast mode (a ChatGPT account's GPT, an OpenAI key's Responses GPT/o-series, Cursor's -fast, Claude Opus on an Anthropic key) now has a bolt beside its star in the agent's picker and in the same picker opened from a provider's row; a click posts /api/agent-fast and switches it for that agent (settings FastPicks, by agent, "<provider>/<model>", moved by a provider rename and kept by the Settings page's save) without changing the pick or closing the picker; a refusal puts it back. The field shows a bolt after a model sent fast. The gateway asks for the vendor's fast mode (service_tier "priority" / speed "fast", as for a group member) on that agent's requests for that entry, every account and key of it, not its fallbacks, another agent's requests or the same model at another provider; the Routing trace marks the try Fast. A routing group is never switched: its members say it. zh/ja/de added. fast-pick (window and 440px panel, en/zh/ja/de, Chromium and WebKit; the bolt only on models with a fast mode, the post, the refusal, the provider row's picker) passes and fails without the fix; TestFastPick (gateway, Chat/Responses/Anthropic in, fails with the gateway hunk off), TestSetFastPick, TestFastPickOptions and the settings-keep test (fails without the carry) pass with -race; i18n-keys, gui-ja, gui-de, favorite-star, agent-models-picker, claude-picker-fold and click-scroll pass; go test ./internal/gui/ ./internal/provider/ ./internal/agent/ ./internal/gateway/ ./internal/settings/ pass. | ||
|
|
6f1da2c585 | agent: DeepSeek Harness's own models say they go straight to DeepSeek (#955) — #834 had every agent's own models say which way they go, Codex's, Grok Build's and Antigravity CLI's custom ones; dsh's three (DeepSeek-V41-Flash, V4-Pro, V4-Flash) still said nothing (EZN7L2C3: DeepSeek Harness 未看到同样的直连入口). Since dsh 0.1.5 magpie is a custom provider beside dsh's own llm-deepseek row, which is left to dsh, so its own models go to DeepSeek on its own key: they now say direct, DeepSeek. Before 0.1.5 magpie took that row over, so nothing is said there. TestOwnModelsSayTheirPath fails without it. go vet, go test ./internal/agent ./internal/gui, GOOS=windows build pass; agent-own-path, agent-path-same and claude-picker-fold pass in Chromium and WebKit, en and zh (no GUI code changed). | ||
|
|
17f790b145 |
agent: five package-level twins nothing calls are gone — isCodexGateway, ccProviderJSON, fxProvider, hermesProvider and writeMiniMaxEntry
Each was a one-line wrapper of its *At function for the local gateway. The callers moved to closures of the same name over the agent's own address (at.v1(), at.gw()), which shadow the package function, so the wrappers were reached by nothing, tests included (deadcode -test). Their doc comments now sit on the *At functions, and isCodexGatewayOn has its own again: it had been left above codexKeptGateway. go vet (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./internal/agent under a temp HOME pass. |
||
|
|
ab47042ba8 |
agent: Codex on one of magpie's models beside its ChatGPT sign-in stays connected when Codex writes one of its own models back, and connecting keeps the model it was on through magpie where it can't join (#940, xybio)
Picked "via magpie" on a ChatGPT-signed Codex, a magpie model is set beside the sign-in: openai_base_url to the gateway, the magpie table, no model_provider. When Codex itself (its /model, or a Codex app still running on its old pick) then wrote one of its own models back (gpt-6.1-sol), Wired found nothing to say magpie had put it there, so the row read Not connected while its config still pointed at magpie's gateway. Set so, Codex keeps a codex.beside mark (gone when magpie becomes its provider, on a pick of its own through the picker, or on disconnect), and Agent.Beside makes Wired true on it; the model written back is still told as a change from outside (#750's drift stays). Connecting where Codex can't join (its ChatGPT allowance used up, or a model_provider of its own) went to the first of magpie's other models (Grok 4.7, "magpie doesn't serve the model it was on"), since Codex's field leaves out magpie's codex/* models: Agent.OwnVia now finds the model it is on as magpie serves it on the user's ChatGPT account (codex/gpt-6.1-sol), and Connect keeps it there. TestCodexOwnPickAfterMagpieModelStaysConnected and TestCodexConnectKeepsItsOwnModelThroughMagpie are new; with the change taken out the first reads the reported config (model gpt-6.1-sol, openai_base_url, [model_providers.magpie], no model_provider) as not connected and the second connects to fake/m1 "first". Tested in temp HOMEs only. go vet, go build (GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass (internal/proc TestUserPath timed out waiting on a login shell under load once, and passes on its own). |
||
|
|
58ecd4454c |
davsync, agent: sync turned off and on again to the same server keeps what changed here, and an agent shown only routing groups connects on the first (#939, aiirobyte)
Off deleted sync-state.json, so turning sync on again made this computer a new one: the first sync brought the server's whole setup in over it, and every agent changed while sync was off went back to the server's older fields. Off now keeps what this computer last synced (dropping only the error, notice, undo and usage state), so it resumes: changes here go up. TestOffAndOnAgainKeepsChangesHere fails without it (Codex's model went back to "gpt-old"). The Agents switch said "magpie has no models Codex can use" when the only models shown to the agent were routing groups (its own ChatGPT account's are left out of Codex's list), while `magpie codex group/folia-1` worked. Connect now falls back to the first routing group. TestConnectOnARoutingGroupAlone fails without it. |
||
|
|
1330b9660e |
agent: VS Code Insiders is an agent of its own, its Chat given magpie's models in Code - Insiders/User as VS Code's is in Code/User (wani on Discord)
vscode.go's agent is now one per build (vscodeKind): VS Code as it was, and VS Code Insiders, id vscode-insiders, its User folder Code - Insiders beside Code on the Mac, Windows and Linux, its binary code-insiders. Insiders' chat names itself GitHubCopilotChat as Stable's does, so it has no UA of its own: its models send the token magpie-vscode-insiders, which the gateway reads first, and its requests are its own in Usage while Stable's stay Stable's. Connecting one writes nothing of the other's. TestVSCodeInsiders fails without the change (unknown agent "vscode-insiders"). go vet, go build (GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. |
||
|
|
5115f0eea6 |
feat: configure Zed-compatible Agent paths (#922)
* feat: configure Zed-compatible agent paths * fix: honor custom Zed bundle paths * fix: classify Zed path overrides correctly --------- Co-authored-by: yetone <yetoneful@gmail.com> |
||
|
|
88b87ac13f | fix(agent): reject Cursor launchers in VS Code detection (#931) | ||
|
|
b2a3449021 |
gui, agent: the models picked for a WSL agent are the ones its picker, its own menu and the gateway list, and a model taken out leaves the picker at once (#927, cjyrainbow)
Claude Code · WSL Ubuntu's model list kept what it picked under its row's id, claude@wsl:Ubuntu, while everything that lists models for it reads claude's: the default-model picker (claudeViaMagpie), the models written into its settings and the gateway's /v1/models for its key (magpie-claude). The line said 2 models; the picker and Claude Code's own menu had them all. Agent.ListsFor is the id a row's models are kept and read under, the Windows twin's for a distro's agent, and the Agents page's model list and line use it (Codex · WSL's order too). Closing the list wrote the hidden set and refreshed the state afterwards, but the click that closed it was the one on the model picker, which opened on the row's old options. The row's pickers now drop what was taken out as the list closes; what was put back comes with the state. TestAgentModelsWSL (FakeWSL distro with Claude Code) and agent-models-picker.test.cjs (Chromium, WebKit, en, zh) are new and fail without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass; the agent-models*, click-scroll, codex-model-order and agent-model-groups GUI tests pass. |
||
|
|
116ca957cc |
agent: a provider's Compact at reaches Codex's on-disk catalog (#876) (#924)
Where Codex compacts a long conversation reached the gateway's /models (provider.CodexListed sets catalog.Model.Compact) and Claude Code (claude.go calls provider.CompactSet), but not the list magpie writes for Codex on disk: magpieModels left Compact unset, so codexcat fell back to the threshold for every model. A threshold the user set on a model or on its provider did nothing while Codex read that list — the case where Codex is not signed in to ChatGPT and model_catalog_json is what it reads. magpieModels now carries it, as the gateway's list and Claude Code already do. provider.CompactSetIn is compactSet for a caller holding the settings and the group finder already, as EffectivePriceIn is for prices: this loop runs for hundreds of models at magpie's start. TestCodexCatalogCompactAtReachesDisk fails without the change (204800, want 100000) and passes with it: relay's "*" and then relay/glm-4.6's own threshold reach the file, with the model's window kept as the maximum, and the whole window comes back when they are cleared. Co-authored-by: TryWorld2026 <tryworld@users.noreply.github.com> |
||
|
|
7b96b59205 |
agent: Alma is told what each of magpie's models costs, so its usage page counts their cost (#919, Magixyne)
Alma looks a model's price up by its id on models.dev and found none for magpie's (think/gpt-5.5), so its own usage page counted every call through magpie at $0, "unpriced". almaCaps now sends the model's price, as magpie's usage pages count it (EffectivePrice: the user's price, else the provider's list price at its price rate, else the maker's), as the capabilityOverrides' pricing Alma reads: input, output, cacheRead and cacheWrite in USD per million tokens (Alma's own pricing shape, read from its app bundle), the 5-minute cache write for cacheWrite. A model magpie has no price for says nothing of it. TestAlmaToldPricing is new and fails without the change; go vet, go build (GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. |
||
|
|
fbaf27bce1 |
agent: dsh's web search goes through magpie while dsh is on one of magpie's models (Packing1 on Discord)
dsh's web search is a plugin of its own, web-search-deepseek, which signs each search to DeepSeek's Anthropic API with DEEPSEEK_API_KEY, so a dsh that reaches its models through magpie alone failed every search asking for that key. While dsh starts on a model of magpie's, magpie writes a marked web-search-deepseek entry in each profile: the gateway's /v1 as baseURL, MAGPIE_GATEWAY_KEY as apiKeyEnv and that model. The gateway answers the plugin's web_search_20250305 request as Anthropic does, by the model's own search or magpie's (web_search_tool_result blocks). A row of the user's, or a DEEPSEEK_API_KEY dsh has in .env or its key store, is left as it is. The entry goes with dsh's own model or Disconnect, and moves with the gateway's address. The row's Notice says when magpie has nothing to search with. TestDshSearchThroughGateway is new and fails without the change. Tested end to end in a sandbox (own HOME and DSH_HOME, gateway on 3493, the real DeepSeek account): real dsh 0.2.0-rc.2 headless called web_search, got eight results through magpie, and answered https://golang.google.cn/blog/go1.26; magpie dsh default took the entry out of all three profiles. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. |
||
|
|
11a2d359a1 | agent: VS Code's magpie models and default model go in each of its profiles too, so a window on another profile lists them in Chat (TJHHHH on Discord) | ||
|
|
71d2da1f42 |
catalog, usage, gateway, gui: a price can have a long-context tier and a 1-hour cache write, and the price boxes take decimals (PAMI on Discord)
OpenAI bills gpt-6-astra, gpt-6.1-sol and gpt-6-luna over 272K input at 2× input and cache and 1.5× output, for the whole request. A price now has tiers: `magpie model price <m> 10,50,1,12.5 --tier 272k 20,75,2,25` (--tier can repeat). A tier applies when input + cache reads + cache writes is over its size, so 272,000 is not over and 272,001 is. The built-in prices take tiers only where models.dev lists one (cost.tiers of type "context"), and it does list one for those three. The ledger, each request and OTLP are costed at the tier the call reached. Session and day totals sum many calls, so they stay at the base price.
Anthropic bills a cache write kept for an hour at 2× input and one kept for 5 minutes at 1.25×. Its usage reports which is which in cache_creation.ephemeral_5m/1h_input_tokens. The gateway (API and Claude subscription) and Claude Code's transcripts now keep that split. A price's fifth part is the 1-hour write. When it isn't given, it is 2× input: that is Anthropic's rule, and only Anthropic reports 1-hour writes. Claude list prices carry the 2× too. cache_write_tokens is still the total, and the usage CSV adds cache_write_5m_tokens and cache_write_1h_tokens at the end. The app shows the split on a request's Cache and on the usage tooltips. Old prices and old records, which have no 1-hour part, read as before. The sessions cache (18) and the call cache (calls-v8) are re-read once.
In the GUI, the price boxes were number inputs and dropped what they didn't parse ("0,25"). They are now decimal text boxes that take both "0.25" and "0,25". A model's price row has a 1-hour box (empty means 2× input) and a quiet Long-context price row. That row shows the list's tier greyed in, or sits behind a link when the list has none. A bad size is refused by name. en/zh/ja/de.
Tests:
- New: TestPriceTiers, TestCostSplit, TestPriceJSON, TestModelPriceTiers, TestModelPriceTierCmd, TestAnthropicCacheWriteSplit and TestClaudeCacheWriteSplit. The ledger CSV test expects the new columns.
- model-price-tiers.test.cjs is new; model-price-edit and provider-price-rate are updated. All of them fail against the old assets and pass in WebKit. Chromium couldn't be launched here.
- go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
|
||
|
|
528d6c12dc |
agent, gui: Codex left on model_provider = "magpie" with no [model_providers.magpie] loads its config again, and connecting with nothing in magpie says what to do, in the reader's language (Tystem on Discord) — the Codex app said "无法加载 config.toml … Model provider magpie not found", magpie showed Codex as not connected on "deepseek-flash", and its switch answered only "Codex can't be connected to magpie", in English. Codex looks model_provider up in [model_providers.*] as it loads config.toml (codex-rs: "Model provider {id} not found"), so the key without the table keeps it from loading any config, a new thread too, not just an old one. magpie never writes one without the other: it takes model_provider out whenever it steps out as the provider (disconnect, Unwire, a model of Codex's own, Join) and keeps the table so the threads started on magpie still open. That is unchanged, and TestCodexSteppingOutLeavesNoHalfState now covers it. Another tool or a hand edit had taken the table away. Sync, at magpie's start and whenever the catalog changes, now writes the table back whenever the config names magpie as its provider, at the top or in a profile, and has no table, whether Codex is connected or not. Check reports that state too. A config magpie can't parse is still only reported, never written to. A Connect that finds none of magpie's models because no provider or subscription has been added answers agent.NoModelsError, "Add a provider or subscription in magpie first, then connect Codex". /api answers it with code no_models and the agent's name, which app.js says through t() (en/zh/ja/de). docs/reference.md no longer says the table goes with a native model. TestCodexProviderWithoutTable (top and profile: Check says so, Sync writes the table) and TestConnectWithNoModels in internal/agent, TestAgentConnectNoModels in internal/gui, and agent-connect-no-models.test.cjs (WebKit, en/zh/ja/de: the toast in the reader's language, the switch back off, no scroll, no native dialog) are new and fail without the change; the latter's en case passes either way, as the server's message is English. Chromium wouldn't launch on this machine for this test or the existing agent-connect.test.cjs. TestCodexSyncAddsBaseURLToProvider now expects the table added to a hand-made provider that had none. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. In the first full run, internal/gui's TestProvidersAnswerWhileListsComeIn and internal/proc's TestUserPath failed under load; both passed alone and in the second full run.
|
||
|
|
3fad3d21eb | agent: Cline's advice on Windows names both the desktop app and the CLI/VS Code extension (#898 follow-up) — since #898 Running answers yes on Windows, where it can't ask, so Cline's first branch (its desktop app) always won there and one using the VS Code extension was never told to reload its window. On Windows the advice now says both. TestDshClineAdviceOnWindows (run by the Windows CI job) checks it; go vet (darwin, GOOS=windows) and go test ./internal/agent under a temp HOME pass. | ||
|
|
bf9bd9c935 |
agent: an agent's "restart it" advice reaches Windows, where a model picked in magpie looked like it had done nothing (#898)
Running() answers whether a process whose command line matches a pattern is
alive, by asking pgrep -f. Windows has no pgrep, so it answered no for every
pattern — and every one of its 25 call sites is the advice an agent's own
lists need after magpie changed what that agent reads at start ("Codex builds
its model list at start-up — restart the Codex app", "New dsh sessions start
on this; one already open keeps its model until you pick another in it"), each
inside a Notice closure, so nothing else rides on the answer. On Windows that
advice was never given: the Agents page wrote the file, said nothing, and a
model picked in magpie looked like it had done nothing at all until the agent
was restarted by hand.
claudeRunning and Pencil's own check already answer yes where a machine can't
be asked (#353, and Pencil's detect), which is why only the other 23 call
sites were silenced; Running now says the same for the same reason, so the one
path every agent's Notice goes through stops disagreeing with the two that
worked around it. No pattern is no question, and still answers no: nothing
asks it, but a caller that passed none isn't told a process is there.
Asked the machine for real would mean a second pattern set: the pgrep-style
patterns don't describe Windows at all — `(^|/)codex( |$)` does not match
C:\…\OpenAI\Codex\bin\…\codex.exe, and `(^|/)dsh( |$)` does not match
"C:\…\DeepSeek Harness.exe" "…\dsh\…". Cost was not the reason either way:
Notice runs only after a pick or a connect/disconnect (gui/api.go:688, :857;
main.go:410, :440), never from GET /api/state and never per render.
Windows only: the pgrep branch is untouched, so the answer elsewhere is what
it was. Nothing else changes — every other caller (connectinfo.go's Stale,
which sizes how many copies are out of date) uses the lowercase running var
and its own GOOS check, not this.
TestDshRunningOnWindows and TestDshAndCodexAdviceOnWindows fail without the
change (the first: "a pattern that may match was answered no"; the second:
"dsh says nothing about an open session") and pass with it, run under the
Windows job's own selector: that job compiles the whole suite but executes
only `go test ./internal/agent -run '^Test(Dsh|KeepDsh)'`, so both are named
TestDsh… to be among the tests a real Windows actually runs, rather than
skipping on Linux and macOS and never being selected on Windows. go vet
./internal/agent passes and go build passes for linux, windows and darwin
(darwin with -tags nogui). go test -tags nogui ./... has the same failures it
has on
|
||
|
|
eafb1845e8 | agent, gui: Codex's row has a square for the model it writes its memories with (Yc on Discord) — Codex sums each thread up with gpt-5.6-luna and consolidates its memories with gpt-5.6-terra (medium), whatever model it runs, and nothing in magpie could change that. Codex reads both from config.toml's [memories] extract_model and consolidation_model (codex-rs/config/src/types.rs; the defaults are memory_extraction_preferred_model / memory_consolidation_preferred_model in model-provider). Codex's row now has a memories square beside the subagents' one, opening the app's model picker: a model picked writes both keys, Default removes them (and a [memories] table left empty), the user's other [memories] keys stay. A model of magpie's is offered and taken only while Codex runs through magpie, as for subagents, and is removed when Codex steps out of magpie or is disconnected, since Codex could no longer find it. Unset, its title says what Codex uses. en/zh/ja/de. TestCodexMemoriesModel and TestCodexMemoriesUnwire (temp HOME) fail without the change; codex-memories.test.cjs (WebKit, en and zh: the square and its unset title, no square with nothing to pick, the model picker with Default first, a pick posted and lit, Default posts "", no select, no scroll, no left border) fails without it. subagent-effort, click-scroll, codex-titles, agent-start-pick and desktop-tiers pass in WebKit. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. | ||
|
|
6fe8169502 |
gateway, agent: Codex draws with its native image tool through magpie (#870)
* fix(agent): give magpie's provider table the actor-authorization header
Codex offers its built-in image tool (image_gen.imagegen) — and the other
OpenAI-provider tools — only to a provider it reads as the OpenAI actor:
ModelProviderInfo::uses_openai_actor_authorization(), i.e. a provider that
does not require OpenAI auth but carries x-openai-actor-authorization.
magpie writes [model_providers.magpie] but carried neither signal, so a
Codex on magpie was never given the tool and could not draw through a
Codex backend such as sub2api's.
Write http_headers = { "x-openai-actor-authorization" = "magpie" } into
that table. magpie does not forward the header upstream: codexHeader
rejects anything naming "authorization".
Test: TestCodexMagpieProviderCarriesActorAuthorization.
* gateway/provider: let a Responses-only relay (Codex backend, sub2api) draw
A provider added as 'OpenAI Responses' only (what a Codex backend such as
sub2api speaks) has no chat base, so Drawers() returned nil before ever
looking at the vendor's list, draw() refused with 'can't draw', and
drawImages() posted to an empty base. The vendor's own /v1/models does list
its image models (sub2api serves gpt-image-2.5), and they are meant to be
picked in Settings → Images.
- Drawers: a Responses-only provider draws when it has any OpenAI-shaped
base, not only a chat one.
- draw: refuse only when neither Chat nor Responses is set.
- drawImages: fall back to the Responses base when there is no chat base.
- List: the add form's 'Fetch models' keeps the vendor's image models so
one can be picked while the provider is added.
Tests: TestResponsesOnlyProviderDraws, TestListOffersImageModels.
* fix: route Codex native images through its turn provider
---------
Co-authored-by: yetone <yetoneful@gmail.com>
|
||
|
|
387afd0779 | gateway: Cursor Private Inference asking with another key of magpie's is shown its own model list (mamba on Discord) — after v0.1.980 its Reasoning control was still missing and models taken out on its row still listed: a key typed in its Open configuration or a model's settings ("magpie") overrides magpie-cursor-local, and its User-Agent (Cursor-CLI/…, cursor-agent-exec's default) made it the cursor agent, which is shown the plain list. cursor-agent talks to Cursor's backend only, so a Cursor User-Agent at the gateway is now taken as Cursor Private Inference's: its /models carries api_types and capabilities and follows its row's model picks, and its use is counted under it. Its notice now says to leave both the base URL and the key empty (or use magpie-cursor-local) and to quit the app, not only its window, as it keeps the model list it was first given. TestCursorLocalModels fails without the change. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. | ||
|
|
602d4ec6cc |
agent: magpie's default compaction window gives way to one the user set in Claude Code (#876, hisiling)
On a non-Claude model magpie wrote CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 into ~/.claude/settings.json's env even when the user had set autoCompactWindow there, or modelSettings.<model>.autoCompactWindow (where /autocompact saves it). Claude Code takes the env first, so the user's own window was overridden: in a sandbox, autoCompactWindow 900000 showed 900k in /context, and 272k once the env was added. magpie now leaves the env out when the user set either one for the main model. A size the user typed in magpie, for every model or for a model or its provider, is still written, because the user chose it there.
Codex is unaffected. Codex's model_info.rs applies model_context_window and model_auto_compact_token_limit from config.toml over the catalog, and magpie never writes or removes either key.
TestClaudeCompactYieldsToOwnSetting fails without the change ("autoCompactWindow: "272000"; want """).
|
||
|
|
1aaac93b52 |
provider, gui: Compact at — where Codex and Claude Code compact a long conversation, set per provider or per model in its editor and to any size in Settings → Long conversations (#876, hisiling)
Until now the only choice was 272K for every model or Full window for every model. The 272K default stays as it is (
|
||
|
|
e77a373792 | agent: every agent in a WSL distro under NAT is given the LAN sharing key, not "magpie" (whqtian on Discord) — v0.1.972 gave it to omp only, and OpenCode in WSL was still written "apiKey": "magpie" with Windows' address, which the gateway takes from loopback only. keyAt(gw) is the key for a gateway URL: the LAN sharing key when the URL isn't this machine's and sharing is on, else gateway.Token; OpenCode (and MiMo Code), Crush, Pi/omo, Codex, Claude Code, Kimi Code, Hermes, Grok, MiniMax Code, Droid, Morph, dsh, Qoder/Qoder CN and omp write it, their Checks compare against it (an old "magpie" there shows as drift), and ourKey still sees a LAN-keyed config as magpie's. TestGatewayKeyBeyondLoopback fails without the change. go vet ./..., go test -tags nogui ./internal/agent ./internal/gui ./internal/gateway ./internal/access ./internal/provider under a temp HOME, GOOS=linux/windows go build pass. | ||
|
|
31626c1543 | agent: an omp in WSL under NAT gets the gateway's network key, and one bun installed is found and asked its version (whqtian on Discord: a WSL omp connected only once auth: none was swapped for the key by hand, and Kimi K3's max still read xhigh) — a distro not in mirrored networking reaches the gateway at Windows' address (172.23.80.1), where a request without a named key is turned away, yet magpie wrote auth: none there too. Its models.yml now carries the key the gateway is shared on the network with (access.LANSecret: the default LAN key, "" while nothing is shared or it is off) in place of auth: none; an omp on loopback stays keyless. The probe asked omp its version only when sh -l found it on PATH, but bun's and nvm's installers put theirs on PATH in ~/.bashrc, which sh -l doesn't read: the version stayed unknown and max was offered as xhigh, as for an omp older than 16.4.0. It now also looks in ~/.bun/bin, ~/.local/bin, ~/.npm-global/bin, ~/.volta/bin, pnpm's, nvm's, mise's and asdf's folders, and runs --version with the command's folder on PATH, where bun's #!/usr/bin/env bun finds bun. TestOmpKeyBeyondLoopback (NAT address shared or not, loopback, localhost) and TestWSLProbeFindsBunOmp (the probe script run with a bun-installed omp off PATH; fails on the old script: no bin, no version). go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass but for TestSavedButSignedOutClaudeServed, which timed out once under load and passes 3 of 3 alone. | ||
|
|
4a97acf12a |
refactor(agent): separate Aside provider connection from model settings (#827)
* refactor(agent): connect Aside as a native provider with verified settings * fix(agent): offer explicit offline Aside model and disconnect actions Offer Save for next start after an initial runtime read failure, using a structured API error instead of saved runtime state. Keep refusals and failed readbacks on the live error path. Add a separate offline disconnect confirmation that restores the checked file plan, clears restore records and removes the provider last. Bind the confirmation to its preview revision and roll back ordinary write failures. Cover offline API and browser flows, cancellation, stale plans, missing CLI, restore metadata and native provider spelling. Keep state file-only. |