Commit Graph
Select branches
Hide Pull Requests
main
Select branches
Hide Pull Requests
main
-
46c56021a4
usage: DimAgent's quota is asked through its own provider's proxy, like every other subscription's (per-provider proxy landed beside #226)
yetone
2026-09-30 02:15:43 +08:00 -
125da38943
providers: each provider, a signed-in account like Codex's among them, goes through its own proxy — the one in Settings, none, or its own address (#237 xosi: 目前似乎是全局设置代理的,codex和国模供应商无法单独区分设置代理) — magpie had one proxy, Settings' (or the environment's, or the system's), for every request it made, so Codex, which needs a proxy from China, and a vendor at home, which is slower or refused through one, couldn't both be right. A provider now has a proxy, "" (following the global one, as before and the default), "direct" (none, whatever Settings says) or an http://, https:// or socks5:// address (host:port meaning http), kept as proxy in providers.json only when set, and for an account provider in its picks entry beside its models. It is checked on save by the same rule Settings' proxy is (settings.CheckProxy, now shared), so ftp:// and the like are refused. netproxy.With puts a provider's choice on a request's context and netproxy.Func honours it; netproxy.Dispatch sends such a request through a clone of the transport kept for that proxy alone (at most 32, cleared past that), since a transport shares an HTTP/2 connection by host whatever proxy dialled it; loopback is never proxied. The gateway's client and http.DefaultClient go through Dispatch, and every request made on a provider's behalf carries its choice: the gateway's turns (attempt, forward, draw, decide, the Codex backend), and the model lists, balances, plan and subscription usage, tests, sign-in, Codex's warm-up and reset, while the Claude subscription's and warm-up's CLI runs get the account's proxy in their *_PROXY (netproxy.EnvWith). The provider editor, a key provider's and a signed-in account's alike, has a Proxy row after Fallback: Global proxy | Direct | Custom (全局代理 | 直连 | 自定义), the address field beside the options only for Custom, its room kept otherwise so a pick never changes the centred dialog's height and moves nothing, and a line under it saying what the pick does; Save posts proxy ("" , "direct" or the trimmed address), a save without it keeps the old one, and Custom with no address is refused before anything is posted ("Proxy: type its address, like http://127.0.0.1:7890"). No border stripes; en and zh strings together. Tested with a new internal/gateway/proxy_test.go in a sandbox HOME: with a global proxy set, provider A's turns and model list go through its own fake proxy, B's (direct) straight to the vendor, C's (following) through the global one, none through another, and A set back to following goes through the global one; ftp:// is refused (fails with With neutered). netproxy_test.go's TestWith covers Func, Dispatch's per-proxy transports and EnvWith. A new internal/gui/tests/provider-proxy.test.cjs in Chromium and WebKit, en and zh: a key provider opens on Global proxy with no address, Custom unfilled is refused and nothing posted, a padded socks5 address saved trimmed, Direct saved as "direct" and Global as ""; Codex's account opens on Custom with its address and saves Direct with its models; every pick leaves the option within 1px; no left border. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./..., provider-proxy, click-scroll, old-webkit, balance-fix and add-sheet pass (click-scroll's WebKit case failed once alongside the others and passes alone, twice). Not tried against a real proxy or the real Wails window.
yetone
2026-09-30 01:23:54 +08:00 -
9992678c64
zcode: an account with no GLM Coding Plan uses ZCode's Start Plan (体验套餐) where ZCode does, with ZCode's own token, and shows its allowance (#236 tasselx: ZCode 3.14.4, magpie 0.1.410, a trial-only account, the free quota not read and every request refused with rate_limit_error "ZCode: [1113][Insufficient balance or no resource package. Please recharge.]"; #225 xiaozhu1337: ZCode ships a built-in trial package) — magpie sent every ZCode account to the Coding Plan's endpoint, api.z.ai/api/anthropic, with the account's zcode-api-key, and read its allowance only from /api/monitor/usage/quota/limit. That key is billed to a Coding Plan, or else to the account's API balance, so an account with only the Start Plan got 1113. ZCode itself (ZCode.app 3.14.3's zcode-builtin.json providerRules and host/index.js: resolveCurrent, buildZaiStartPlanBalanceUrl, hasActiveStartPlan, isZaiStartPlanIdentity, normalizeStartPlanExpiry, normalizeZaiStartPlanBalanceLimits, pollPendingOAuth) serves the Start Plan (account:zai-start-plan and account:bigmodel-start-plan) from zcode.z.ai/api/v1/zcode-plan/anthropic, keyed by its own session token (the credential store's zcodejwttoken, the
tokenits sign-in's poll hands back, which magpie dropped), and reads what is left from /api/v1/zcode-plan/billing/balance?app_version= with that token. Now, in a new zcode_start.go: an account keeps ZCode's token beside its key (zcodeKey.JWT; zcodeOwn reads zcodejwttoken, and user_info from oauth:bigmodel:user_info when there is no Z.ai one; an account with the token and no coding plan key is an account). Whether it is on the Start Plan is asked of /api/biz/subscription/list (a Coding Plan: as before; none: the Start Plan; can't tell: the Start Plan if its balance has an active one), kept 10 minutes (1 when unsure); with no token it is the Coding Plan as before, asking nothing, and with no key it is the Start Plan. On the Start Plan a request is moved to zcode-plan/anthropic, signed with the token (x-api-key and Bearer) and ZCode's own headers (User-Agent ZCode/<ver>, X-ZCode-App-Version, X-Title, HTTP-Referer, X-Platform), refused with "sign in to ZCode again" once the token has run out; its models are the Start Plan's (zcodePlanID account:zai-start-plan for ZCode's config, zcodeModels less GLM-5.3 before it is read). Its allowance is the balance as ZCode reads it: a plan still "active" past ends_at (by server_time) is expired and the buckets of expired plans left out; the plan's name (Start Plan) with Until its end and Renew off; a window per bucket, named by its show_name (or its models, or Credits), Used and "used / total" from total/used/remaining units (numbers or strings), ResetsAt its expires_at, Span from its entitlement's period (daily/weekly/monthly) or period_start..end, and matched to the bucket's models. Signing in from magpie no longer refuses an account with no Coding Plan: with an active Start Plan it is added on that (its key kept too, so a Coding Plan bought later is used), and refused only when the Start Plan is over as well ("this Z.ai account has no GLM Coding Plan, and ZCode's Start Plan has ended or was never started — subscribe at z.ai/subscribe, then add it again"). Accounts with a Coding Plan are routed, signed and metered as before. The provider keeps the name ZCode (#225 asked for "GLM Coding Plan"; ZCode names these plans "Z.AI Individual Coding Plan" and "Start Plan", and the plan line already shows "GLM Coding <Level>"). Tested with a new internal/provider/zcode_start_test.go against a fake Z.ai/zcode.z.ai in a sandbox HOME, no real tokens: ZCode signed in with only its token (BigModel user_info) is an account, its provider served at zcode-plan/anthropic, a request signed with the token and ZCode's headers, the Start Plan's models and plan id, its allowance (Start Plan, Until, Renew off, GLM-5.1's window 25%, "250000 / 1000000", reset, a day's span, matching GLM-5.1 only); an expired plan and one active past its end give none; with a key and the token, no Coding Plan moves the request (query kept) to the Start Plan and it reaches it with the token, a Coding Plan keeps it on api.z.ai with the key and no ZCode headers, no token asks nothing, an expired token is refused; a sign-in with only the Start Plan is added on it, with a Coding Plan on that plan, with neither refused naming both. TestZCodeAccounts passes unchanged. go build -tags nogui (and GOOS=linux, GOOS=windows), go vet and go test -tags nogui ./... pass (TestUpdateCLI, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin and TestUserPath timed out once each under load beside other builds and pass alone). Not tried with a real Start Plan account: the endpoint's acceptance of the token as x-api-key/Bearer and whether it needs ZCode's headers, the balance's real field values and period strings, what subscription/list returns for a trial-only account, and ZCode 3.14.4 (read from 3.14.3) are from ZCode's code, not seen on the wire.
yetone
2026-09-30 01:08:33 +08:00 -
12d9195a2c
usage: a currency of ¥ CNY chosen before a restart is the one costs show in, not $ (#212 follow-up, L1cardo: after a restart the setting still showed RMB but the costs were back in USD) — the Go side sends the exchange rate the cny choice converts at in two places: inside /api/settings' answer (settingsJSON.fx) and, in /api/state, beside the settings rather than in them (stateJSON.FX, filled only when cny is chosen). At start the page runs applyPrefs(state.settings), which set the currency to cny from the settings but looked for the rate only as s.fx, inside them, where /api/state never puts it; fx.rate stayed 0, and fmtCost converts only when the rate is above 0, so every cost (the Usage page, the requests ledger, sessions) was drawn in $. Picking ¥ CNY in Settings worked, because the save's answer is /api/settings' and carries fx, and the Settings row reads the stored setting, so after a restart the two disagreed. Nothing was wrong with what was stored: settings.json keeps currency, and internal/fx reads its cached rate from disk; the TUI and CLI call fx.Get directly and were never affected. Now load() passes state.fx to applyPrefs, which takes the rate from there or from the settings' own fx, ignores a rate of 0 (what /api/state sends for usd, which never looks one up), and redraws the costs when the rate arrives or changes while cny is chosen, not only when the currency changes. Tested with a new case in internal/gui/tests/currency.test.cjs, in Chromium and WebKit, en and zh: /api/state faked as the Go side sends it after a restart (settings with currency cny, fx beside them), the Usage page opened straight away shows ≈¥88.85 (12.34 at 7.2), Settings shows ¥ CNY picked, and Usage still shows ¥ after it; without the fix it shows ≈$12.34 in all four. currency, old-webkit, usage-ledger, click-scroll and settings-groups tests pass (58); go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass in a sandbox HOME. Not tried in the real Wails window.
yetone
2026-09-30 00:52:53 +08:00 -
30431e0450
usage: a Grok id named at a reasoning effort is priced as its model (#224 Aimer779: calls made with Grok Build show no cost on the Usage page) — the Usage page, the Requests ledger and the Sessions page price a model by its models.dev id, and xAI lists grok-4.7 and grok-4.6 ($2 / $6 / $0.5 cache read per million in the cached catalog) but not the ids Grok's models are spelled at an effort: grok-4.7-low, -medium, -high, -xhigh (Cursor's ids, and grok-4.7 at the levels Grok's own model list gives it). Such an id is the same model at another setting, so when its own id has no price it is now looked up as the model it is an effort of (provider.pricedNames: grok-<version>[-…]-minimal|low|medium|high|xhigh|extra-high, with or without a path, any case), in Provider.ListPrice and MakerPrice (the gateway's ledger and summary) and in the Sessions page's priceOf (through provider.PricedName). An id priced by its own name keeps that price; what is recorded is unchanged. Only Grok's ids are read this way, and -max is not stripped. Left unpriced, as no catalog in use lists them and no price is made up: fast ids (grok-4.7-fast, grok-4.7-low-fast, … which cursor_models.go treats as models of their own), grok-4.7-build and grok-4.7-build-fast (Grok Build's own models, from its model list and sessions) and grok-4.7-mini; they keep the + as before. Tested with a new TestGrokEffortListPrice (MakerPrice and the Grok account's ListPrice price grok-4.7 at each effort, in upper case and with x-ai/, and grok-4.6-minimal, at grok's price; the fast, build, mini and -max ids, gpt-6-astra-high and cursor-grok-4.7-high stay unpriced) and TestPriceOfGrokEffort in internal/sessions (the same for a session's models, grok-4.7-build, grok-4.7-mini and the fast ones unpriced); TestSubscriptionListPrice unchanged. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked and TestCodexVersion hit their 5s waits in the full run and pass alone). Not tried against the reporter's usage log or in the GUI.
yetone
2026-09-30 00:45:50 +08:00 -
4455c7f998
gui: the Windows tray panel no longer shows a faint close X over its Settings gear — its caption buttons are hidden there (#238 mintonight: a blurry red X at the panel's top-right corner, just above and right of the gear) — Wails v3 makes a frameless window on Windows WS_OVERLAPPEDWINDOW and, for the shadow and round corners, extends DWM's frame into the whole client area; the panel left its close button at the zero value, ButtonEnabled, which sets WS_SYSMENU, so DWM drew the caption's close X into that extended frame at the window's top right, and with the panel translucent (BackgroundTypeTranslucent, WS_EX_NOREDIRECTIONBITMAP, a see-through webview) it showed through the page, faint, over the gear. It was not the page: #winclose is removed everywhere but Linux's window, and is a grey pill in the header row. The panel's options move into panelOptions(goos, theme), unchanged, and on Windows its close, minimise and maximise buttons are ButtonHidden, which takes WS_SYSMENU and the min/max boxes off, so DWM has no caption buttons to draw; DisableFramelessWindowDecorations stays off, so the shadow and Windows 11's round corners stay. The Mac and Linux get the same options as before (their button states left at the zero value), and the main window is not touched. Tested with a new internal/gui/panel_options_test.go (!nogui): on windows the three buttons are hidden and the panel still frameless, translucent, off the taskbar and decorated; on darwin and linux the buttons are as before and URL, backdrop and corner radius unchanged; run on the Mac and compiled for Windows (GOOS=windows go test -c). go build -tags nogui (and GOOS=linux), GOOS=windows go build and go test -tags nogui ./... pass (TestUserPath, TestClaudeSignedOut and TestClaudeAccountNamedAsItsLogin timed out once at a load average near 60 and pass alone). Not tried on a real Windows desktop: that DWM stops drawing the X without WS_SYSMENU is from the Win32 custom-frame behaviour and Wails' code, not seen.
yetone
2026-09-30 00:45:21 +08:00 -
5353ed54fe
gateway: a vendor refusing the client outright (WorkBuddy's 400 "Illegal API invocation from an unapproved channel") hands a group's request to its next member (PR #232 Y-C-Fan: a routing group stopped at a WorkBuddy member refusing Claude Code's chats) — retryable let a 400 fall over only on quota or unserved-model words, so this refusal, which is of the provider and not the request, went back to the agent. A new refusedWords (unapproved channel, illegal api invocation) joins them. The request body is not changed: a chat opening with Claude Code's own system prompt is still sent as it came. Tested with a new case in TestGroupFailsOverAnUnservedModel's retryable table (false before) and the gateway package.
yetone
2026-09-30 00:38:10 +08:00 -
ac1f522154
library: skills in the user-wide ~/.agents/skills are found, one row with the agents that link or junction to them, and brought in by a link that leaves the shared folder as it is (#227 Aimer779: the Library's "In your agents" never looked in ~/.agents/skills, the shared folder many agents read and where the user keeps skills, many as symlinks to D:\aimer-skills; a skill junctioned from there into Pi and ZCode showed as Pi's with "differs in ZCode") — foundSkills walked only each target's own skills folder, and realDir used filepath.EvalSymlinks, which since Go 1.23 leaves a Windows junction (a mount point, reported as irregular, not a symlink) as it is, so two junctions to one folder were two skills, and the Link check (ModeSymlink) took a junction for a folder of its own, one ImportSkill would move. Now foundSkills reads ~/.agents/skills first (hidden entries, ones without a SKILL.md, ones the library has or linked, skipped as for the agents' folders), a skill there carrying shared (its entry) and the agents whose entry resolves to the very same folder; a byte copy in an agent's folder is still another ("differs in"), as copies were. realDir follows links a part at a time itself (symlinks, and on Windows junctions through os.Readlink, relative targets, links to links, a loop or a missing part giving the path back), and a new linked() (symlink, or junction on Windows) replaces the ModeSymlink checks in foundSkills and ImportSkill. ImportSkill links the library to a skill in the shared folder (Source folder), real folder or link alike, and never moves it; the agents' links to it are replaced by the library's as before; an agent's own folder still moves in. No target writes to ~/.agents/skills: targets.go gives no agent a user-wide ~/.agents/skills (only projects.go's project .agents/skills), so it stays a place skills are found in. The page: a found skill in the shared folder says "shared in ~/.agents/skills/<name>" (共享于) above any "linked from", its Bring in title says it stays where it is and can be given to any agent; the shared path is revealable. Tested with TestSharedAgentsSkills (sandbox HOME/USERPROFILE: a symlink in the shared folder to a folder elsewhere, real folders there, Pi and Codex links to one of them, absolute and relative, Gemini's link to the shared link, a Codex copy, a folder with no SKILL.md and a hidden one; one row each with the right agents, shared, link and others; importing all three leaves the shared folder and the folder elsewhere as they were, the library's entries links to them, the agents' links now the library's, Codex's copy untouched, nothing found after), TestRealDir (absolute, relative, a linked parent, .., a loop, a missing path, linked, within) and TestJunction (mklink /J, Windows only, skipped here), and a new internal/gui/tests/shared-skills.test.cjs in Chromium and WebKit, en and zh. go build -tags nogui (and GOOS=linux, GOOS=windows), go vet for windows and go test -tags nogui ./... pass (TestClaudeSignedOut flaked once, passes alone); the GUI suite passes but for login-import (passes alone) and panel-profiles in WebKit, which fails on main the same under this load. Not tried on real Windows, with real junctions, or in the real window.
yetone
2026-09-30 00:21:13 +08:00 -
6df6de1010
sessions: the indexing show no longer comes back on every visit — the index is written after the read, in the background, and not counted as indexing (Image #15: 这个还是会出现无数次)
yetone
2026-09-30 02:07:40 +08:00 -
b713d5cd99
gateway: Gemini behind a proxy on this machine or the LAN is asked for its thoughts too, and a proxy that turns the fields away is asked as before (X @saoyan25: 本地反代的是谷歌AI studio的provider … agent客户端没看到思考链的内容,请求响应体也没看到reasoning_content) — v0.1.413 asked for Gemini's thoughts only when the provider's host was generativelanguage.googleapis.com, and Ben's AI Studio goes through a reverse proxy on his machine, so nothing changed for him. Now a request translated to a Chat upstream is taken for Gemini's OpenAI-compatible API (Request.GeminiCompat, set in forwardTranslated) when the host is AI Studio's, or when the model's id names Gemini and the host is localhost, a .local name, or a loopback or private address, a proxy there being most often one in front of Google; a relay elsewhere (OpenRouter, aihubmix …) and other local models are asked as before. As such a proxy may not pass thinking_config on, a 400 naming extra_body, thinking or include_thoughts marks the provider unfit for it (as the cache key is) and the request is sent again at once with reasoning_effort, as before v0.1.413, and so are its next ones — which also guards AI Studio itself, whose taking of the fields hasn't been tried live. Tested with TestGeminiCompat (AI Studio, 127.0.0.1, localhost, 192.168.x, [::1], .local, a local non-Gemini model, OpenRouter, aihubmix) and TestGeminiThoughtsThroughLocalProxy (Claude Desktop's request, thinking adaptive at effort high, to a fake proxy on 127.0.0.1: extra_body.google.thinking_config include_thoughts and thinking_level high with no reasoning_effort, the <thought> block reaching the client as a thinking_delta and no tags; a proxy answering 400 Unknown name "extra_body": asked again with reasoning_effort high, the reply whole, the next turn not sent the fields), which fail on main; TestAIStudioThinking now sets the flag. go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass.
yetone
2026-09-30 01:58:30 +08:00 -
db4d68894d
gateway: retry OpenRouter mandatory reasoning without disabling it (#240)

Allen Caoandheyallencao
2026-09-30 01:26:57 +08:00 -
9d4f420d27
sessions: the search sits at the right of Latest sessions, the list it filters, and the indexing show waits for a real index run instead of replaying on every catch-up read (Image #14: 搜索会话的输入框不应该在这里吗; 会播放两次或甚至多次正在为你建立索引的那个百分比动画) — the search box moves from the filter bar into the Latest sessions heading's right, and it stays visible with an empty-state line when nothing matches, so the query can be cleared. Every reload or range change re-read the few session files agents had written to since the last read; each short read counted as indexing and replayed the percentage show. The show now appears only once indexing has run for 0.7s, and once it has shown it runs to the end. Tested in sessions-overview.test.cjs, Chromium and WebKit, en and zh: the search inside the heading at its right edge and centred on it, filtering, no-match and Escape; a 0.4s run shows no hero and a 2s one shows exactly one (fails with the delay at 0). menu-scroll's outside click now lands on the filter bar. menu-scroll, old-webkit, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass.
yetone
2026-09-30 01:10:08 +08:00 -
317bc70875
sessions: one index run a page shows once, a spinner, a session-shape chart that reads, and every session counted in it (Image #9–#13: 排版不对…土土的; 这个原点的动画可以换成别的吗; 这个图我着实没看懂; 出现之后又消失又出现…跑完之后突然又出现; 为什么只有5个会话)
yetone
2026-09-30 00:04:33 +08:00 -
2f00db65e6
feat: add DimAgent subscription support (#226)

Ethan Xuandyetone
2026-09-30 01:11:29 +08:00 -
27c90c8d88
panel: profiles open from Profiles at the Agents tab's foot, on its left, instead of a tab of their own (Image #65: tray window 中方案不应该有一个单独的 tab,在 Agents tab 左下角有个方案即可) — the tray panel's tabs are Agents and Routing; the footer's left corner, on the Agents tab only, has Profiles (方案) with how many there are, and a click opens them upward over the list as a popover (the .pop look and fade, reduced motion kept): the chips, + Save current and its name field as before, the popover scrolling within itself when there are many, sized between the tabs and the footer. A click outside, Escape (the name field's Escape closes the field alone first), another tab or a profile applied closes it; the list under it is never moved. A remembered Profiles tab opens Agents. fit() no longer measures the profiles. Tested: panel-profiles.test.cjs reworked (no Profiles tab; the popover above the button at the foot's left and under the tabs, the count shown; scrolling to + Save current inside it, the field whole and nothing moving; Escape, outside click, Routing tab and applying a profile close it; Profiles only on Agents) and old-webkit.test.cjs opening it before Save, in Chromium and WebKit, en and zh. Screenshots looked at in light and dark, en and zh. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the GUI suite pass (click-scroll, panel-profiles, panel-routing and routing-kind failed under --test-concurrency=4 and pass alone). Not tried in the real Wails window.
yetone
2026-09-30 01:05:38 +08:00 -
7103ba1997
provider: a model can be told to accept images (#228)

drunkduckdrownandyetone
2026-09-29 09:52:07 -07:00 -
0f33e53454
gateway: AI Studio's Gemini is asked for its thoughts, so Claude Desktop sees it think instead of waiting in silence (X @saoyan25: Claude Desktop through magpie took 20 s for 你好, 首字 19983 ms, where another client showed Gemini's first output in 2 s) — a request translated to Google AI Studio's OpenAI-compatible endpoint carried only reasoning_effort, and Gemini gives its thoughts there only when asked (extra_body.google.thinking_config.include_thoughts), so the whole think was silence: nothing for the client to show and the first token counted only at the answer. Now a client that asked to see the model think (Anthropic thinking enabled/adaptive, and the like) has AI Studio asked for its thoughts at its effort as thinking_config — thinking_level for Gemini 3 and later (xhigh and max as high), thinking_budget 1024/8192/24576 for 2.x, Google's own steps — in place of reasoning_effort, which Google won't take beside it; with no effort named, the model's own level. A request with thinking disabled (Claude Desktop's title requests) is sent reasoning_effort minimal, the least Gemini 3 thinks (it can't stop), where before it thought at its default. Other hosts get what they did. As the endpoint's way of giving thoughts isn't documented, both are read: reasoning_content, as before, and a leading <thought>…</thought> in the text, the tags split anywhere across chunks, which the chat decoder now passes on as thinking (the rest as the answer; text that only starts like the tag, or has one later, stays text). Tested with TestAIStudioThinking (each effort, 2.5's budget, no effort, off, an effort without thinking, another host), TestChatThoughtTags (whole, split, one byte a chunk, a leading newline, <b>, a lone <, a tag later, never closed) and TestChatThoughtTagsToAnthropic (a thinking block ahead of the text, its first delta written as soon as it came, no tags left), which fail on main; go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass (three internal/provider tests timed out under the parallel run and pass alone). Not tried against the live AI Studio API: no key for it here.
yetone
2026-09-30 00:46:39 +08:00 -
f18d4e886b
providers: the add sheet as A3 — a small green dot after an added name and a grey count past one account, short names with the full name, plans and host in the title, bolder section heads with more room, and a vendor's global and China presets one row marked Global · China whose editor picks the region (keeping the key typed); the editor now grows out of the row clicked on a spring and folds back into it on close, iOS-like, with reduced motion respected (A3; 点击这些供应商,弹出表单Modal以及关闭表单 Modal 的时候,要有一个极其优雅丝滑的动画,类似于iOS的动画) — the list's scroll is restored before the modal closes so the fold lands on the row. Tested with add-sheet.test.cjs in Chromium and WebKit, en and zh, recording the open and close keyframes, and the whole GUI suite (login-import flakes under load on main too).
yetone
2026-09-30 00:24:52 +08:00 -
9272fd1f62
gui: the session indexing view's narrow layout has a fallback where container queries are missing (#220) — the indexing show's @container (max-width: 420px), added beside the old-WebKit work, had no @supports not fallback, so old-webkit.test.cjs failed on it; macOS 12's WebKit (Safari 15) now stacks it by the window's width, as the Routing page's container queries do. old-webkit.test.cjs passes.
yetone
2026-09-29 23:30:12 +08:00 -
641b01aa95
settings: the warm-ups and WorkBuddy's check-in sit under one row of tabs, Codex | Claude Code | WorkBuddy, the picked one's rows alone in the card (#124 Mrhe525: asked for the service groups as tabs in the heading's place instead of three stacked groups) — since v0.1.406 the Settings page ran on after Preferences with three groups under their own headings, CODEX (Warm up on reset, Daily warm-up), CLAUDE CODE (the same two) and WORKBUDDY (Daily check-in). Now one section follows Preferences whose heading row is the tabs, in the headings' small capitals, the picked one darker on a soft pill (a tablist labelled "Warm-up and check-in" · 预热与签到), and the card under it shows only that service's rows, unchanged: same rows, same settings keys and /api/settings posts, the same status lines (last warm-up, today's check-in) and tooltips. The WorkBuddy tab is there under the condition the group had (an account signed in or the check-in on). The tab is remembered per viewer (magpie.warmTab, localStorage in try/catch, like the panel's tab), Codex to begin with and whenever the remembered WorkBuddy tab is not there. A click on a tab moves nothing: the card is its own height, and a shorter one picked at the page's very end gets room kept at the view's foot by the page's "where the reader is" hold; Left/Right/Home/End move along the tabs (roving tabindex), the tab reached clicked so the key is held the same way. A pill drawn while its card was hidden measured nothing, so its thumb is put under the picked option, still, when the card is shown. No border stripes; one new string, en and zh together, none left unused. Tested with internal/gui/tests/settings-groups.test.cjs rewritten, in Chromium and WebKit, en and zh: the tabs in order where a heading would be, after Preferences and before Local network, Codex's rows alone to begin with and each tab its rows alone, the status on the lines, no left border; with the page scrolled, each control posts codexWarmup, codexWarmAt (On, a new time, Off), claudeWarmup, claudeWarmAt and workbuddyCheckin as before and a tab posts nothing, the page left where it was; the pills' thumbs under their picks on a card shown later (fails without the fix); the arrows, Home and End; at the page's very end, WorkBuddy's shorter card and Codex's taller one by click and key leaving the tabs within 1px; the tab remembered across a reload; no WorkBuddy tab with no account, Codex's shown when WorkBuddy's was remembered. Screenshots looked at in light and dark, en and zh, at 900 and the window's narrowest 560. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the GUI suite (128) pass; click-scroll and usage-ledger failed once while go test ran beside them and pass alone. Not tried in the real Wails window.
yetone
2026-09-29 22:52:18 +08:00 -
b881932ec2
gui: the page runs in macOS 12's WebKit — no regex lookbehind, the few newer built-ins filled in, fallbacks for container queries, subgrid, :has and color-mix (#220 uclort: on macOS 12.7.6 the app opened blank, the panel showing its tab bar, a "Profiles + Save current" heading and "Open magpie · Quit" and nothing else, no button working) — the page runs in the system's WebKit, on macOS 12 Safari 15.x's, and app.js's snippet highlighter had a regex lookbehind, (?<==)\S+ (app.js:2167), which WebKit only parses from Safari 16.4: a SyntaxError in a literal fails the whole file, so app.js never ran (and routing.js and library.js, which lean on it, threw), leaving index.html's static markup; library.js had two more, (?<![\w-]) in the SVG greying (library.js:162-163). The highlighter now finds a value after "=" by hand (the character before the match, a sticky \S+) and the greying checks the character before a colour name in its replace callback, each giving the same output as before (compared on shell, curl, Python and Node snippets and an SVG with attribute and style colours, data-fill and x-color left alone). Built-ins newer than Safari 15.0 in use, routing.js:307's findLast and library.js:1042's structuredClone (15.4), are filled in by a new compat.js loaded before the other scripts (with at, findLastIndex and Object.hasOwn), each only where missing; structuredClone there is a JSON copy, enough for the API's plain data. Styles: a selector list with :focus-visible (15.4) drops the whole rule before 15.4, so the agent handle's hover, an account's Remove and protocol buttons showing on hover, the protocol menu's hover and the editor's focus ring are split from their :focus-visible (or it is dropped where :focus covers it); .acc:has(.aq) and the panel's .profiles:has(> .chip-input) hint (15.4) are classes set by app.js (with-aq, naming); the Routing page's container queries and the request list's subgrid (16) get @supports not fallbacks by the window's width (the tray panel is narrower than all of them) and a row's own columns; and the panel's --panel-tint, a color-mix() (16.2) in a custom property that would have gone invalid and painted the panel clear (tintPanel then handing the system white under dark text), falls back to --bg, as the floating status and Library's sticky head do. Other things newer than 15.0 are left as cosmetic: inert (15.5) on the folded agent list, accent-color, the rest of color-mix() on hover and focus shades, .quota's subgrid rows. The build: Go 1.25 and later run on macOS 12 and up only (the linker stamps 12.0 as the oldest), so Info.plist's LSMinimumSystemVersion and the Makefile's -mmacosx-version-min go from 11.0 to 12.0 and the site says macOS 12+ rather than 11+. Tested with a new internal/gui/tests/old-webkit.test.cjs: every assets/*.js parsed with the Babel parser Playwright bundles, failing on a regex lookbehind, v flag or modifiers (in a literal or new RegExp("…")), a class static block, decorators, import attributes or using (checked against the old highlighter's regex and a static block, and not on named groups, lookahead, ??= or private methods); a grep for built-ins newer than 15.0 (at, findLast, structuredClone, hasOwn, toSorted and co., Set methods, groupBy, withResolvers, Iterator helpers, popover …) unless compat.js fills them in (fails on main's routing.js:307 and library.js:1042 without it); compat.js the first of the page's scripts and every script loaded; the CSS with no :has(), no :focus-visible in a mixed list, no nesting or dvh, and @container, subgrid columns and a color-mix() custom property only with an @supports not fallback in the same file; and, in WebKit with those built-ins deleted, the panel's tabs, Save current's name field and every window page drawn with no page error. Documented in internal/gui/tests/README.md. go build -tags nogui (and GOOS=linux, GOOS=windows), go build (darwin GUI), go test -tags nogui ./... and the whole GUI suite (130 tests) pass. Not tried on a real macOS 12 or Safari 15: the old engine's parse is judged by the parser, not run.
yetone
2026-09-29 22:58:15 +08:00 -
6546033109
kiro: removing kiro-cli's or the Kiro IDE's own sign-in hides it in magpie, and it shows again when Kiro signs in anew (Discord 碳碳双键: 本机没安装 kiro,但旧的 Kiro 数据还在,magpie 列出一个"Kiro account"(登录已失效,请重新添加此账号),添加新账号并设为首位后,旧的数据无法移除) — magpie reads Kiro's own sign-in from kiro-cli's database or ~/.aws/sso/cache and lists it among the accounts it signed in; Remove on it said "that is kiro-cli's or the Kiro IDE's own sign-in; run
kiro-cli logoutor sign out in the IDE", which with neither installed left a dead account there for good. The same refusal was shared by every agent whose own sign-in magpie only reads (forgetSideLogin: Kiro, Devin, Grok, Copilot, ZCode, WorkBuddy and WorkBuddy AI, Command Code, Gemini CLI, Antigravity). Now Remove on the agent's own sign-in hides it: logins.json's entry for it keeps a Hidden mark, and it is left out of the account list and so of the gateway's tries (the first and the ones behind it both come from that list); the agent's files are never deleted or written. It shows again when that sign-in changes: for Kiro, by a mark that is a digest of where the sign-in is (kiro-cli's row or the IDE's file) and its refresh token (never the token itself), so a newkiro-cli loginor IDE sign-in, to any account, brings it back; for the others, which have no such mark, when it is another account. Removing it while it is first is allowed now, the next account being put first (saved, so it stays first when the own one comes back); removed when it was the only one, the agent has no account left. One of magpie's own accounts is removed with its home as before, and still can't be while it is first. The error and its per-agent sign-out hints are gone. Logins carry own, and the accounts list shows Remove on the agent's own sign-in also while it is first, its title "magpie stops showing and using Kiro's own sign-in; its files are left as they are, and it shows again when Kiro signs in anew" (magpie 不再显示和使用 Kiro 自己的登录;它的文件保持原样,Kiro 重新登录后会再次出现). Tested with a new internal/provider/side_logins_hide_test.go in a sandbox HOME: an expired kiro-cli sign-in ("Kiro account") behind a Google one magpie signed in is removed, stays gone across reads, isn't the account used nor among AlsoOn, kiro-cli's database byte-for-byte and mtime as it was, the refresh token nowhere in magpie's files; a new kiro-cli sign-in brings it back behind the one first; put first and removed, the next is first; one of magpie's removed takes its home and leaves the other's; the only account removed leaves Kiro with none; Grok's CLI sign-in hidden with auth.json untouched, back once it is another account. The agents' tests that expected the refusal (Command Code, Copilot, Devin, Grok, WorkBuddy, ZCode) no longer do. A new internal/gui/tests/own-signin-remove.test.cjs in Chromium and WebKit, en and zh: kiro-cli's sign-in behind another and alone first both have Remove with that title and post login/forget, magpie's first account has none. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the whole GUI suite (138) pass. Not tried with a real Kiro, kiro-cli or the Kiro IDE.
yetone
2026-09-29 22:59:52 +08:00 -
cf6b82f70d
usage: a Codex, Copilot or catalog-less relay call is priced at its maker's list price, as a Claude account's is (#224 Koohoko: on the Usage page only Claude models show a cost; gpt-6-astra and gpt-6-luna through Codex's ChatGPT account and gemini-3.8-flash and gpt-6-luna through Copilot show tokens and no cost, and the Codex row reads ≈$153+) — the Usage page and the Requests list price a call by the models.dev ids of the provider it went to (Provider.Catalog), and only the Claude account had one (anthropic): Codex's and Copilot's accounts, like Grok's, WorkBuddy's and a relay added without a catalog, have none, so every call they served was counted unpriced although models.dev lists all these models (openai: gpt-6-astra $10/$50, gpt-6-luna $0.1/$0.5; google: gemini-3.8-flash $0.75/$3.75, per million, in the cached catalog). A call is now priced by its provider's catalogs first, as before, else by the first maker among the vendor presets that lists the model (the same makerCatalogs the reasoning levels borrow from), and a call whose provider has gone since is priced by its maker too. The model is matched by its own key first, then without a path or case (a relay's openai/gpt-6-astra is gpt-6-astra), a Bedrock geography, a (variant) or :tag, as the reasoning levels already are (new catalog.PricedBy, Provider.ListPrice, provider.MakerPrice); a model no maker lists (a Cursor or Kiro alias, a relay's own name) stays unpriced and keeps the + as before, no price is made up. Both the Usage summary and the Requests ledger go through the one pricer, so they agree. Tested with a new TestSubscriptionListPrice: calls to codex (gpt-6-astra, gpt-6-luna), copilot (gemini-3.8-flash, gpt-6-luna), claude (claude-opus-5-5) and a relay with no catalog (openai/gpt-6-astra, mystery-1) each get their maker's price in the ledger and the summary, mystery-1 alone unpriced (fails on main: all seven unpriced); TestLedger and TestSummarize unchanged. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... pass (TestAddSecondOfPreset failed once in the full run with a fourth provider and passes on its own and in the package run; it doesn't touch pricing). Not tried against the reporter's usage log or in the GUI; the Sessions page's own pricing (internal/sessions) was not looked at.
yetone
2026-09-29 22:34:40 +08:00 -
98efd30a2e
routing: requests coming in leave the routing groups where they are on the screen, a group being edited too (Jerell.OvO on Discord #general: on the window's Routing page the request log refreshes live and each refresh scrolls the page up, so the routing groups further down can't be read or edited; a switch to pause it or moving the log to another tab was suggested) — every request redraws what sits above the groups (the stage gains or loses agent and account rows, the story a line, the requests and accounts lists theirs), and app.js's backToReader kept the view's scrollTop as a fixed number, putting it back after any scroll the reader didn't make: WebKit has no scroll anchoring to make up for it and in Chromium backToReader undid the browser's own, so the groups slid up under the reader as the parts above shrank and down as they grew. In WebKit the accounts list, emptied and refilled every second for its countdowns, also made the view scroll on its own. Now a view can name a part of itself to keep in place (keepInView): its position on the screen is taken when the reader leaves it (their own scroll, or a click held), and backToReader follows it wherever the parts above take it, from a ResizeObserver so before paint, with the browser's own anchoring off for that view so the two don't fight; a view at its top stays at its top. The Routing page keeps the groups in place while they're in the upper half of the view or a field in them has the focus, else the lists, and with neither (the stage in sight) nothing moves. The accounts list is patched, keeping each row that's unchanged (rows with a button are always replaced, so their handler is the new one), not emptied and refilled. No live-update switch was needed and no new strings; no border stripe. Tested with a new internal/gui/tests/routing-steady.test.cjs in Chromium and WebKit: with the view wheeled down to the groups, one open in its editor and text typed into its name, six requests streamed in from the trace (new agents and accounts, failures and retries): the part above the groups really changed (more than 50 px), the section's head, the editor and the last group stayed within 1.5 px on the screen on every layout and scroll seen (WebKit scrolls by whole pixels while the parts above are laid out in fractions), the view's scrollTop moved by just what grew above them, and the field kept its focus and its text and went on taking typing. On origin/main it fails in both engines (the head moves 233 → 171 px). go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the whole GUI suite (126 tests) pass. Not tried in the real window with a live gateway.
yetone
2026-09-29 22:34:54 +08:00 -
09abaeecf0
fix(gui): 版本检查进行中保留检查按钮 (#223)

SadWoodandyetone
2026-09-29 23:43:40 +08:00 -
ae9b3ecdcf
decide: send a gateway's own Jev name; ambiguous unfetched is 400 (#207)
drunkduckdrown
2026-09-29 08:26:25 -07:00 -
86a2b8cf47
providers: qianfan personal, team and pay-as-you-go endpoints (#211)
Austie
2026-09-29 23:25:56 +08:00 -
41841997d6
sessions: a fast index with an animated first run, searchable model and folder pickers, and the overview's session shape, tool use, top skills and heatmap by messages or output tokens (v: 感觉我们的Session可观测性页面也要做的这么的丰富、优雅、简洁…; 顶多第一次打开顶多需要个1分钟,然后之后都是靠索引能够极快的打开; 一定一定要特别炫酷的一个动画; Image #6/#7/#8)
yetone
2026-09-29 23:11:38 +08:00 -
a4a9893969
providers: the add sheet is quiet rows under four headings, an added provider marked in green, and a custom provider one line at the foot (Image #24: 这个页面看起来有点乱糟糟的,能否变得特别简洁优雅? — design A of four) — the sheet was framed tiles of two lines each (name and host or plans), the added ones faded with a check, sections named "Relays · many vendors behind one key" and the like, and a dashed Custom tile among the local ones. Now each section is a bold name and a faint word on what it is (Subscriptions · sign in, no key; Vendors · the makers' own APIs; Relays · one key, many vendors; On this machine; Removed from magpie · still signed in, 订阅 · 登录即可,无需密钥 …), and under it rows three to a line, 36px, with no border: the icon and the name, the host, note or plans in the row's title. A provider already added is no longer faded: "✓ Added" (已添加) in green at the right, a subscription "✓ Signed in", "1 account" or "2 accounts" (2 个账号), its title saying a click switches or adds an account; a removed one "Add it back". The custom provider is "+ Custom provider · any OpenAI or Anthropic compatible URL" (自定义供应商 · 任意 OpenAI / Anthropic 兼容的 URL) under a hairline at the foot, hidden while searching as the "Nothing called…" line takes its place. Clicks do what they did (a preset's editor, an added one's page, a sign-in); the header, search and Import… are unchanged. Unused zh strings removed. No border stripes. Tested with a new internal/gui/tests/add-sheet.test.cjs in Chromium and WebKit, en and zh (fails on main): the headings and words in order, no row with a border, a second line, overflow or fading, three vendors on a line at 900px, Added in green on Anthropic and OpenRouter and none on OpenAI, "2 accounts" on Claude, the host in OpenAI's title, the foot line and its hint, searching "deep" leaving DeepSeek alone and no foot, and a click opening DeepSeek's editor. The whole GUI suite passes (four files flaked under the parallel run and pass serially), go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. The regions (Kimi and Kimi China …) are not merged into one row.
yetone
2026-09-29 22:24:02 +08:00 -
c86179d75a
tests: usage-ledger checks the export's own request, not whichever came last — the Requests test waited for the export POST to be the last request asked, paused 150 ms and then read the last request again, so a GET the page sent in between (a refresh) was checked instead, and WebKit's zh run failed "every page is exported" about one run in six (seen reviewing PR #211 on plain main). It now takes the last POST and also checks there was just one. 15 WebKit runs in a row pass (one in six failed before), and the file passes in Chromium and WebKit.
yetone
2026-09-29 22:04:57 +08:00 -
dc4363acb6
fix: a picker's own button closes it on a second click (#218)
Liken Tan
2026-09-29 21:39:04 +08:00 -
1e9f3a46cb
usage: a Requests tab lists every request of the period, one row each, and exports them as CSV, from the window and from magpie usage --csv (Discord 范不着: a per-request ledger as sub2api keeps, to set beside the upstream's own bill) — the Usage page summed calls by agent and model, and the log did not keep the model the agent asked for or the one the reply said answered, so a single request could not be checked against a vendor's bill. usage.Record now keeps Requested (req, the model id the client sent, a magpie alias or group as it came) and Served (served, the model the vendor's reply named, only when it named one); every usage.Append in the gateway fills them (the relayed and translated paths, Codex's backend, image draws, decide), and records written before load as they did, without them. The swap test the Routing page uses (a dated or pinned name is the same model) moved to usage.Swapped so the ledger marks the same rows. usage.Ledger gives the period's records newest first with each one's list-price cost, the agent it is counted under and whether the model was swapped, filtered by agent, failures alone and a search over the models, provider, account, session and effort; usage.WriteCSV writes them (time, agent, requested_model, provider, host, model, served_model, swapped, effort, input, output, cache write, cache read and reasoning tokens, cost_usd, duration_ms, ttft_ms, status, error, session, kind). The Usage page gets a third tab, Requests (请求), between Overview and Sessions, under the same period picker: an agent filter, All/Failed, a search field and Export CSV over a table of time, agent, requested model, provider · account, model sent, served model (amber with the Routing page's sentence when another model answered, plain on a dated name, a dash when the reply named none), effort, in, out, cache write, cache read, cost, duration and status, a failure's row marked by a red dot and bold code; the totals over it and 100 rows a page with Newer and Older. GET /api/usage/requests serves a page, POST /api/usage/requests/export saves all the filtered rows to Downloads (never over an earlier file) and opens the folder, GET /api/usage/requests.csv hands a browser the file in magpie web. The table scrolls sideways in its own box; at the window's narrowest the words after the head's cost give way so the three tabs fit without the page scrolling sideways. No border stripe; clicks move nothing (the pager held where it was). en and zh strings together. Tested with TestLedger (order, cost, swap, a dated name, an old record without the fields, each filter, the CSV exactly), TestServedModelRecorded and TestServedModelUnnamed (a request through a fake upstream answering with another model records Requested fake/sol, Model sol and the served name; one naming none leaves Served empty), TestUsageCSV (magpie usage --csv, a period, a stray argument refused), TestUsageLedgerRoutes (paging, totals, filters, export to Downloads twice) and a new internal/gui/tests/usage-ledger.test.cjs in Chromium and WebKit, en and zh, light and dark: the tabs and columns, the swap tag on the swapped row only, the 429 row, Older and Newer over 130 requests with the pager left where it was, the agent, Failed, search and period filters asking the server from the first page, Export CSV posting the filters with no page and saying where the file went, at 560 wide no tab scrolling the page sideways and the cost uncut, no left border. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the whole GUI suite (122 tests) pass. Not tried in the real window through Wails or against a live gateway's log.
yetone
2026-09-29 21:11:44 +08:00 -
5a54338744
panel: the Routing tab gets a small stage, agents → magpie → where their requests went, each new request a dot flying there and back, and its rows line up (the user: 这个页面有点乱,另外这个在Tree的window中能否也能有一个比较迷你版的路由动画,直观、优雅。) — the tab listed eight two-line rows with an agent's colour dot and a status dot (both green for Pi), the times left in their cells so the right column ran jagged. Over the list there is now a stage, 104px, in a card: up to three agents that asked lately on the left (icon and name), magpie's bird in the middle, up to three accounts they went to on the right (provider, then the account or key in grey, and a dot for how its latest try there went: green, grey pulsing while answering, red when it failed), joined by thin curved wires. Each place keeps its spot while it stays; a newcomer takes the place of the one heard of longest ago. A request as it comes plays as a dot in its agent's colour: along its agent's wire into magpie, out on the wire to the account it was routed to (the wires it travels lit in that colour, the hub's ring pulsing), waiting there, pulsing, while that answers; a try that failed turns red and comes back to magpie for the next account, and the answer flies home green, or red when nobody could answer. At most four play at once, from the same trace the list is drawn from (no new endpoint); nothing plays with reduced motion or while the tab isn't shown. The rows: the agent's colour dot is gone (its icon says who), the second line starts with a faint → (accent when moved, red when failed, pulsing while waiting) instead of a second dot, and the time (now 24-hour, 20:50:53) and the tokens · time taken are both right-aligned; six rows instead of eight, the panel about as tall as before. No border stripes, nothing scrolls. Tested in panel-routing.test.cjs, Chromium and WebKit, en and zh: the stage lists Codex and Claude Code, Relay·team and Claude·ann@example.com with their status, four drawn wires, the hub, everything inside it and above the list, no left border; every row's time and totals share one right edge and each row has one mark; and, with motion allowed, a request coming once the tab is shown makes a dot fly, the wire lit and the hub busy, the dot waiting beside the Claude account while it answers, turning green and gone after, the row's tokens updated. The whole GUI suite passes (login-import, a timing test of an import's passing status, failed once under concurrency and passes alone), go build -tags nogui (and GOOS=linux, GOOS=windows) and go test ./internal/gui pass. Checked by eye with a fake live trace in light and dark (a 429 on Relay failing over to DeepSeek, then a Claude request). Not tried in the real tray panel.
yetone
2026-09-29 21:16:56 +08:00 -
04dc3bca72
panel: a Routing tab shows the gateway's latest requests, and the allowances' tab is named Allowances (v on Discord #feedback: the panel's Usage tab and the window's Usage page mean different things, and the panel had no routing) — the tray panel had Agents, Usage and Profiles, its Usage being each subscription's allowance while the window's Usage counts requests and Routing shows where they went. The panel's tab now reads Allowances (额度; the window keeps Usage/用量, and the tab's id and remembered choice are unchanged), and a new Routing tab (路由) lists the gateway's last 8 requests as they come, from the trace the Routing page already long-polls (/api/gateway/trace, which routing.js polled in the panel too; no new endpoint): each a two-line row with the agent's dot, icon and name and the model it asked for, the time, then where it went (provider · account or key), the model that try sent and, when the reply named another model (the new served/swapped fields), the amber "served gpt-6-luna" (实际 …) tag of the Requests list, beside the tokens and time taken; a failed one says its status and why (429 · rate limited), one under way who is answering. Over them, today's calls and tokens (the Usage page's /api/usage?period=today, read when the tab is shown and after a request ends, at most every 5 s) and Open Routing. A click on a request opens the window's Routing page on it: POST /api/window/main takes req beside view=routing (mainView lets only a positive id through), and the page, loaded so, picks that request as a click in its list would (its story told) and drops req from the address. The panel's height counts the tab as its tallest, as the others; the tab redraws keeping the panel's scroll, the click moves nothing, no border stripe. Tested with a new internal/gui/tests/panel-routing.test.cjs in Chromium and WebKit, en and zh: the Routing and Allowances labels, four fixture requests listed newest first with agent, model, "Relay · team" and "Claude · ann@example.com", the served tag on the swapped one only (not on a dated name), the 429 row, times, no row overflowing, today's 42 calls · 1.2M tokens, a click with the panel scrolled asking for view=routing&req=100 and leaving the scroll as it was, Open Routing, and the window opened on ?view=routing&req=101 with that request picked and its story shown; and TestMainView. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the whole GUI suite (102 tests) pass. Not tried in the real tray panel or with a live gateway: the window opening on the request is tested in the page, not through Wails.
yetone
2026-09-29 20:21:38 +08:00 -
1cd7b4d086
settings: the warm-ups and WorkBuddy's check-in sit under Codex, Claude Code and WorkBuddy headings, with short lines (#124 Mrhe525: a tidier Settings page) — after Open at login, Dock and Proxy the Preferences list ran on with Codex warm-up, Codex daily warm-up, Claude Code warm-up, Claude Code daily warm-up and Check in to WorkBuddy daily, each with a long grey sentence, one flat list. They now come after it in three groups under small headings as Local network has: Codex (Warm up on reset · 窗口重置时预热, Daily warm-up · 每日定时预热), Claude Code (the same two) and WorkBuddy (Daily check-in · 每日自动签到), the rows' names no longer saying the service. The lines say the purpose alone ("One tiny request starts the next window at once", "Starts each account's 5-hour window at this time every day", "Claims each signed-in China account's check-in credits once a day"), the last warm-up (上次启动于17:01) and each account's check-in (今日已签到 +1…) still after them; the rest (06:00 gives three by 21:00, sent through Claude Code, 签到 as in WorkBuddy) moved into the lines' tooltips with the fine print already there. The WorkBuddy group, heading and all, shows only while an account is signed in or the check-in is on, as the row did; a daily warm-up's time field was already there only while it is on. Same settings keys and the same /api/settings posts; en and zh strings replaced together, the old ones removed. No border stripes. Tested with a new internal/gui/tests/settings-groups.test.cjs in Chromium and WebKit, en and zh: the headings in order after Preferences and before Local network, each group's rows and none left in Preferences, the status on the lines, no left border; with the page scrolled, each segment (Weekly, Weekly and 5-hour, daily On, a new time, Off, check-in On) posts codexWarmup, claudeWarmup, codexWarmAt, claudeWarmAt and workbuddyCheckin as before with the page left where it was, the time field coming and going with its On/Off; no WorkBuddy group with no account. All internal/gui/tests/*.cjs pass (108), go build -tags nogui ./... and go test -tags nogui ./... pass.
yetone
2026-09-29 20:21:42 +08:00 -
d2112865ef
dsh: a profile DeepSeek Harness makes after magpie set it up gets magpie's model too (Discord 莫: 模型为什么WEB版本有,桌面版就只有官方自带的?) — every dsh profile has its own patch list (~/.dsh/profiles/<name>/cordis.patch.yml), and picking a model in magpie writes the ones there then. The desktop app's is made the first time it opens, so opened after the web's was set up it had none of magpie's entries and listed DeepSeek's own models alone, while Check read the web's (the first) and said all was well. Now Check looks at every profile and says which has none of magpie's models (the row's unwired mark, whose fix sets the model again in all of them), and the sync run at start-up and on each catalog change gives a profile with no llm-deepseek entry at all the model the wired one has; a profile with an llm-deepseek of the user's own is left as it is (reported, until the model is set again). Tested with a new TestDshProfileMadeLater (the web profile set, then a desktop template and a profile with the user's own entry: both reported, the sync fills the desktop's and leaves the user's untouched, setting the model again clears it; fails on main). go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... pass. Not tried with the dsh desktop app itself.
yetone
2026-09-29 19:59:20 +08:00 -
c816c9f828
routing: a request answered by another model than the one asked for is marked, from the model the vendor's reply names (v on Discord #feedback: 模型路由情况其实有的时候还是想知道的,比如之前 openai 会把 sol 降智到 luna 这样的,现在只有走 s2a 和 newapi 这些网关服务才能明确看到) — magpie read the tokens off each reply but not the model it says answered, so a vendor serving sol's requests with luna looked, on the Routing page, like sol answering. The reply's model is now kept as it goes by, never delayed or changed: on a relayed reply by the usage sniffer, which already reads every event and JSON body (Chat Completions' model in its JSON and each chunk, the Responses API's response.model in response.created/completed and model in its JSON, Anthropic's message.model in message_start and model in its JSON); on a translated one from the decoders' start event, which already carried it (Chat, Responses, Anthropic, and Gemini Code Assist's modelVersion), streamed or collected; on a Claude subscription's from the CLI's message_start; and on Codex's own models relayed to the ChatGPT backend. It goes in Usage.Served (and so the request log), each try's Served and the route's (the last try's, when it answered), with Swapped when it is another model than the one the try sent the vendor. Vendors mostly echo a dated or pinned name, which is the same model: the names are compared without a maker or path prefix (openai/, models/, Bedrock's us.anthropic.), case, and a version at the end — a date (-2025-08-07, -20250929, -06-05), a build number (-0613, -001), Bedrock's -v1:0, -latest, -preview — so gpt-5 answered as gpt-5-2025-08-07 or claude-3-5-sonnet-latest as claude-3-5-sonnet-20241022 is not marked, while gpt-5 as gpt-5-mini, gpt-5 as gpt-5.1, claude-opus-4 as claude-opus-4-1-… and deepseek-v3 as deepseek-v2 are. A reply that names no model marks nothing. The Routing page's Requests list puts an amber "served luna" (实际 luna) tag by the answer, its title saying what it means, and the request's story a line "requested sol · served luna" (请求 sol · 实际 luna) with the same sentence; no border stripe, and picking the request leaves the page where it is. Cursor, Devin, Kiro and Qoder name the model they were asked for themselves, so they are never marked.
yetone
2026-09-29 19:34:53 +08:00 -
729f6bdbdf
models: a model its maker lists with a thinking switch alone takes no borrowed levels, and the gateway sends it no more than high (#214 Moody-Max: opencode 的 mimo-v2.6 Flash 你这边设置的默认推理强度是 max 导致400报错)
yetone
2026-09-29 19:31:09 +08:00 -
6aa99ef1c0
usage: costs show in RMB at a live rate, when Settings picks it (#212 L1cardo: 添加费用用人名币结算的选项,最好是能根据实时汇率来计算) — the Usage page, the tray panel, the TUI and the CLI all price a call in USD, magpie's list prices being dollars; Settings now has a Currency row (after Usage shows) offering $ USD, kept by default, and ¥ CNY, which converts every one of those at display time only — usage.Totals.Cost and the JSONL records it comes from stay in USD always, and a vendor's own balance (already in its own currency, a relay's ¥) is never touched.
yetone
2026-09-29 19:22:45 +08:00 -
55a0c35f30
agents: Antigravity CLI (agy) takes magpie's models (Discord Str: asked to use agy through magpie) — agy is on the Agents page like the others. Picking one of magpie's models writes ~/.gemini/antigravity-cli/settings.json: modelProvider "gemini", an entry per magpie model under customModelsConfig.customModels (magpie/<ref> → {apiProvider: API_PROVIDER_GOOGLE_GEMINI, modelName: <ref>}) beside the user's own, and model. Every other key is kept, and "as installed" gives back the user's model and modelProvider and takes magpie's entries out (the file byte-identical to before). agy reads its endpoint and key only from GEMINI_API_KEY / GOOGLE_GEMINI_BASE_URL, and in agy 1.2.13 a custom model chosen by settings or its picker fails ("model is empty"): only --model resolves it. So the agent has a Launch command, GEMINI_API_KEY=magpie GOOGLE_GEMINI_BASE_URL=<gateway> agy --model '<model>' (a $env: form on Windows), which magpie agy prints as its notice and the row's new square copies in one click (tooltip shows it; no scroll; zh strings). Check says unwired when the entry is gone or modelProvider isn't gemini. Tested with TestAgy (set/switch/drift/restore, a user's custom model kept, a file with nothing of the user's left as it was) and agy-launch.test.cjs in Chromium and WebKit, en and zh (fails without the app.js change); end to end in a sandbox HOME with agy 1.2.13 against a sandbox gateway on 38472 and a fake OpenAI upstream: magpie agy magpie/fakeup/echo-1 wrote the settings, agy (interactive and --print) answered from the upstream through the gateway, magpie agy default restored the file. Not tested: the Windows command, a real Gemini upstream; agy's requests show in usage as google-genai-sdk (its UA), and its title requests for gemini-3.1-flash-lite-preview get a 404. go build (nogui, linux, windows) and go test -tags nogui ./... pass; the GUI suite passes but for click-scroll's WebKit case, failing already
yetone
2026-09-29 19:14:34 +08:00 -
13309b07fa
panel: a profile saved or deleted shows at once and stays in sight (Image #62: 点击保存没有效果,删除方案也没有效果。你为什么UI UX做这么烂?) — both did their work (profiles.json had the profile), but the page showed nothing of it: every profile action answers with the whole state, which read logins.json (166KB, the accounts' credentials) again for each agent's drift and models, dozens of times — 2.5–4s a state on a real setup — and only then redrew the chips. And with no profiles yet the chip saved came in above + Save current, which the click held where it was on the screen: the hold gave the view room and scrolled it 30px, and the chip went up under the tabs, out of sight. Now the chip saved is there at once, dimmed till magpie answers, the name field closed; × takes the chip away at once; a save or delete refused puts the list back and says why. A view at its top when clicked stays at its top instead of being given room to scroll (what came in above the control moves it down). logins.json is parsed once until it changes (filememo, a copy to each caller): a state went from 2.4s to 0.9s here. Tested with a new case in internal/gui/tests/panel-profiles.test.cjs, Chromium and WebKit, en and zh: magpie answering after 1.5s with no profiles, the chip there and dimmed before the answer, the list unscrolled and the chip below the tabs, × removing it before the answer, a refused save taken off with its reason (fails on main); against a sandbox magpie web in WebKit, the view stays at 0 where main scrolled it to 30. click-scroll passes in both engines; go test -tags nogui ./... (TestClaudeSignedOut flaky under the full run, passes alone), go build for linux and windows
yetone
2026-09-29 19:23:12 +08:00 -
0b7781ba18
providers: Claude Code is signed in to its next account once the one it is on is spent, as Codex is, and an account keeps its own affinity, backoff and usage across a switch (#208 Cyb3rN8: Claude Code 用自己的模型时,登录账号额度用满不会自动切换 · #209: Smart 选中的账号和 Claude Code 登录账号互不同步…切换登录账号还会把按 id 记的状态串到另一个账号上) — Claude Code on its own model asks Anthropic as the account it is signed in to, never through magpie, so with A spent its sessions got "You've hit your session limit" while the gateway had moved on to B. The Codex watcher (KeepCodexOnAnAccountWithRoom, now KeepOnAnAccountWithRoom, same place: started by the magpie serving the gateway, a minute in and every 5 minutes) now looks at Claude Code too: when the account it is signed in to is spent and another ticked account's allowance is known and has room, it is signed in to that one through SwitchLogin (credentials and .claude.json's oauthAccount), as Use on the Accounts page does; not when the Claude account was removed from magpie. The rule for #209: the sign-in follows Smart, not the other way round — it moves at the share Smart counts an account spent (provider.SpentShare = 98, which the gateway's usedShare now is), for Codex too (it moved at 100%). The Routing field of a Codex or Claude subscription with several accounts says so: routing picks the account for what comes through magpie; the agent on its own uses the one it is signed in to, moved on at 98%. Sessions already running keep the account they started with until restarted, as SwitchLogin says; routing Claude Code's own model through the gateway (the codexFailover way) is not done here. #209's second part was a real bug: the account the agent is on is the candidate "claude" and the others "claude@user", so a switch handed "claude" from A to B — a conversation A answered was kept on B and traced kept=true, and the Least used tokens and the backoff count followed the id. who(), routed.used and routed.failures are now keyed by restKey (provider@user, as rests already were); rest itself (group members, the trace) is unchanged. Tested with new TestClaudeSwitchedWhenSpent (sandbox HOME, keychain off, a fake usage endpoint: 97% stays, 98% signs Claude Code in to b — credentials and .claude.json — a stays ticked; on b it stays; both spent it stays), TestCodexSwitchedWhenUsedUp still passing, new TestLoginSwitchKeepsEachAccountItself (fails on main: the conversation went to b, kept) and new internal/gui/tests/routing-signin.test.cjs in Chromium and WebKit, en and zh; login-import and routing-kind GUI tests pass. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... pass (TestUserPath, TestClaudeAccountNamedAsItsLogin, TestClaudeIdentityServedWhileAsked and TestCodexVersion timed out at 5s under the full run's load and pass on their own). Not tested against real Claude accounts or a running Claude Code.
yetone
2026-09-29 18:53:51 +08:00 -
3676d2e713
providers: an account signed in while magpie runs is listed with its vendor's models (#204 uclort: 首次添加 WorkBuddy 模型显示不全,点击刷新后模型全部显示) — a signed-in account's own list was fetched only at start-up, for the accounts there then (and once: a fetch that failed was not tried again). WorkBuddy AI signed in afterwards, in magpie or in WorkBuddy itself, showed magpie's built-in list ("magpie's list · Refresh asks the vendor", 14 models) until Refresh fetched /v3/config's 20-odd. Now provider.FetchNew, which start-up uses in place of its loop, also runs before the providers are listed (providersState, 8s each): each ready account with no fetched list is asked for it, so the first listing after a sign-in is the vendor's. One that fails is asked again after 10 minutes, not on each listing; one fetched is not asked again. Tested with a new TestFetchNewAccount (a fake WorkBuddy /v3/config: nothing asked with no account; a WorkBuddy auth file written while running lists the built-in models, then the vendor's three after FetchNew; not asked again; a 502 asked once over two listings, then again after the retry time). go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... pass (TestUserPath and three Claude sign-in tests timed out once under load, pass on their own and on a rerun of the package). Not tried in the GUI with a real WorkBuddy account
yetone
2026-09-29 18:51:53 +08:00 -
559ce02147
models: a model's own name given by the user keeps its provider after it too (#203 uclort: 如果在供应商模型页自定义模型名称,在 Codex 中就不会显示供应商了) — Entry.Label() showed "name · provider" only for a model's own (vendor's or models.dev's) name; a name the user gave it (provider's model page → Names & levels) dropped the provider entirely, so a renamed model in Codex's picker (and Pi's, ZCode's, OMP's, magpie's own gateway list — everything magpieModels/Label feeds) could no longer be told apart by vendor at a glance. Label() now appends " · provider" (or "routing group") to a custom name too, the same as it does for the vendor's own, and only leaves it off when the name already contains the provider's (case-insensitively), so a name like "Sol on A" isn't doubled to "Sol on A · A". No settings toggle added: the fix makes the provider suffix unconditional (what the issue actually asked for), so a preference to hide/show it would only reintroduce the gap; there's also no existing "model list" preferences surface to hang it on cheaply — Names & levels only holds the name and reasoning-level pickers. Tested with TestModelName (internal/provider/modelprefs_test.go, sandbox HOME): a custom name now reads "My Sol · A" in Label() and in CodexListed(), and "Sol on A" (already containing the provider) is left alone; and TestModelNameAndLevelsReachAgentFiles (internal/agent/modelprefs_test.go, sandbox HOME) checks the generated Codex model_catalog_json and Pi models.json entries carry "GLM · Relay" for a custom name and don't double it to "GLM via Relay · Relay" when the name already says Relay. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui -count=1 ./... pass except TestUserPath and TestClaudeAccountNamedAsItsLogin, which are load-flaky (pass alone, unrelated to this change) same as the pre-existing rtk.go gofmt / TestConfiguredModelsAdvertiseImageInput flakiness
yetone
2026-09-29 18:53:57 +08:00 -
be32242fda
providers: Bedrock's list offers Claude Sonnet 5.5 (#176 KylinDC: 模型列表还是不全,比如没有sonnet 5.5 … Sonnet 5.5: global.anthropic.claude-sonnet-5-5) — Bedrock's runtime has no list to ask, so its preset's models are the list; global.anthropic.claude-sonnet-5-5, the inference profile id KylinDC gave, is added after Opus 5.5. Any other id can still be typed in the provider's model editor. go build -tags nogui, go test ./internal/provider ./internal/gateway pass
yetone
2026-09-29 18:42:11 +08:00 -
3ba6ff7d2e
providers: ChatGPT and Claude accounts come in from CLIProxyAPI's auth files (#124 Mrhe525: 要是能把那什么cpa的json文件可以导入直接使用就无敌了) — the ChatGPT (Codex) and Claude subscriptions now take the import Antigravity had: Import accounts from a file… under the accounts, and Import from a file instead… while a browser sign-in waits. It reads CLIProxyAPI's files ({type:"codex"|"claude", access_token, refresh_token, id_token, account_id, email, …}), one or a list or {accounts:[…]}, Codex's own auth.json ({tokens:{…}}), Claude Code's .credentials.json ({claudeAiOauth:{…}}) and bare refresh tokens one a line. Each account is checked as a sign-in in magpie is finished: its refresh token traded with the agent's own OAuth client (codexRefresh / claudeRefresh), then the ChatGPT claims or Claude's profile read (plan, tier, account and organization), and it is kept as that sign-in would keep it: Codex's or Claude Code's own if the agent has none, else added and ticked. The refresh rotates the token, so the file's copy stops working; the box says so. A refresh token magpie holds already (saved or the agent's own) is "Already in magpie" and never refreshed; another app's file ("a claude sign-in, not Codex's"), an API key, a token in the files twice and one the vendor turns down ("used since … expired or signed out") are listed as not added with the reason; tokens are never in what comes back. codexExchange and claudeExchange now build their sign-in through the same codexLogin / claudeLogin. Also: a Claude sign-in with no ~/.claude yet failed writing .credentials.json (the directory is made now), and the magpie://import dialog was drawn by the subscription import's renderImport, declared later under the same name (renamed renderLoginImport). zh strings added. Tested with new TestImportCodexLogins (a fake token endpoint: a CPA file and a native auth.json added with the new tokens and the file's account id, the live account untouched and reported as there, a Claude file, a spent token and a duplicate refused, a second import "exists", garbage an error) and TestImportClaudeLogins (with no sign-in the CPA file becomes Claude Code's credentials, plan max, active; a spent .credentials.json and a Codex file refused), and a new internal/gui/tests/login-import.test.cjs in Chromium and WebKit, en and zh (both ways in, the texts, what is posted, the outcomes, the account listed). go build -tags nogui (and GOOS=windows, GOOS=linux), go test -tags nogui ./... and the other GUI tests pass on
fe22692(click-scroll's WebKit case fails after f1a8caf, with or without this change)
yetone
2026-09-29 18:33:42 +08:00 -
7c24e3ab29
panel: a profile is saved by clicking Save, and the name field opens beside it without moving the list (上边缘被裁了 · 每次点保存方案,它都会自动向下滚动一下 · 我点保存并没有反应,这个功能没做完吗?) — in the tray panel's Profiles tab, + Save current put a name field at the head of the list and focused it: the list scrolled to it (drawn up under the tabs, 2px away, the page's focus ring over the field's own accent border cut off at the top), and with a long list it went out of sight above the button. Only Enter saved: a click on the button took the focus from the field, whose blur removed it, so the click found nothing and the next click opened it again. Now the field opens beside the button (in the window at the row's end, in the panel on the button's line under the chips, the button as tall as the field so the line keeps its height), focused without scrolling, with one ring; the button reads Save (保存) while it is open, a click on it saves as Enter does (the press keeps the field's focus), an empty name keeps the field open, Escape closes it and gives + Save current back, as does the list redrawn after a save. Tested with a new internal/gui/tests/panel-profiles.test.cjs in Chromium and WebKit, en and zh: 30 profiles scrolled to the end, the field opened beside the button with the list unmoved, field and button in the view, no outline, Save saving, an empty name, Enter, Escape (fails on main); the other GUI tests pass
yetone
2026-09-29 18:29:29 +08:00 -
6646a37170
ui: simplify LAN sharing address display (#198)
Ns2Kracy
2026-09-29 18:27:01 +08:00 -
36b0d23fe7
providers: a provider of the user's saved on a subscription's id is moved off it, so the subscription is listed once signed in (LingYi_Stu on X: workbuddy 国内版本无法使用呢 — "凌一 已登录,但 magpie 无法列出它,请反馈此问题", magpie 0.1.384, WorkBuddy 5.6.2 signed in, the international build listed with 3 accounts beside it) — before WorkBuddy's plan was a subscription (v0.1.261), a provider added as "WorkBuddy" was saved with the id workbuddy, and All() skips an account whose id a saved provider has: the sign-in said done, logins.json kept the account, and the Providers page listed only the user's own provider, the card still saying Free · Pro. Now start-up (agent.MoveOffAccountIDs, beside RenameLegacy) moves each provider with a base URL saved on a subscription's id to the next free one (workbuddy-2) the way a rename does: routing groups, fallbacks and the agents on its models follow; the old id is not kept as one it Was, being the subscription's. Rename allows it for such a provider alone, a subscription still keeps its id, and Find takes an id before a name, so workbuddy is the subscription and not a provider called WorkBuddy. Tested with a new TestWorkBuddyUnderUsersOwnID (the sign-in done and unlisted, then both listed after the move, a fallback moved, the subscription refused a rename), and end to end in a sandbox HOME with a WorkBuddy auth file and providers.json: main lists the custom WorkBuddy alone; with the change magpie providers lists workbuddy-2 (its fallback moved) and WorkBuddy signed in as 凌一. go test -tags nogui ./... (TestUserPath failed once under load, passes on its own), go build for linux and windows
yetone
2026-09-29 18:17:09 +08:00 -
dbe2f01e7c
routing: the requests sit right under the stage, sized to stay in sight with it (Image #23: 这一块离那个路由动画距离太远了,用户点击了这里都不知道发生了什么) — the groups and the providers with several accounts or keys came between them, so a request picked in the list played on a stage scrolled out of sight above. They now come after the requests, and the list is as tall as leaves the stage in view above it (216–420px), scrolling on its own rather than the page. Picking a request no longer sends the list back to its top in WebKit (it was emptied and refilled, which reset its scroll). click-scroll.test.cjs brings a row into the list before picking it, as a reader would; Chromium and WebKit pass
yetone
2026-09-29 18:10:56 +08:00 -
fe2269288c
agents: each agent's CLI shows its version and updates in one click (#202 JoeyMa-zh: Agents 页面增加"更新 CLI"功能 — 检查各 agent CLI 的版本并一键更新) — the Agents page now puts each CLI's installed version after its name (read with
<cli> --version, 10s timeout, no stdin) and, when a newer one is out and magpie can tell how it was installed, a small "Update to x.y.z" pill. How it was installed comes from the resolved binary: the vendor's own installer first (Claude Code's ~/.local/share/claude/versions → claude update, Codex standalone → codex update, ~/.opencode/bin → opencode upgrade), then a global node_modules (bun, pnpm, else npm, run asnpm install -g --prefix <prefix> pkg@latestwith that prefix's npm, and only where the prefix is writable), then a Homebrew formula or cask allowlisted per agent (brew upgrade [--cask]); anything else shows the version alone, with no button. The latest comes from the npm registry orbrew info --json=v2(no auto-update), cached 6h (15min after a failure); installed versions are cached by the binary's path, size and mtime. GET /api/agents/cli waits at most 3s and says pending, and the page asks again; POST /api/agents/cli/{id} runs the update (one at a time per agent, 10min cap), reads the version again and says so if it did not change, or gives the command's last lines when it fails. The pill goes busy in place and the page never scrolls; not in the tray panel. zh strings added. Tested with a new internal/agent/cliupdate_test.go (version order and parsing; npm/bun/pnpm/brew formula and cask/native installs, a read-only prefix, a local node_modules and unknown binaries in a fake PATH; an update against a fake registry and updater) and a new internal/gui/tests/cli-update.test.cjs in Chromium and WebKit (versions, pills only where magpie can update, row heights, an update with the view scrolled, a failing one, zh). No real update was run. go build (nogui, GOOS=windows, GOOS=linux), go test -tags nogui ./... and the GUI tests pass
yetone
2026-09-29 17:46:41 +08:00 -
c9e47f043e
balance: a custom provider's balance token can't be left where it is never asked, and new-api's refusals say what to do (TJHHHH: OK了,希望有防呆机制 — a new-api relay's System Access Token pasted beside a Balance URL of /api/usage/token, no New-Api-User header, and the Usage page never showed a balance) — new-api has two balance queries: /api/usage/token tells a key what is left on it and takes the key alone, and /api/user/self tells the account's quota to the access token with the user's id in New-Api-User. A token beside the first could only ever be turned down, and the second without the header answered {"success":false,"message":"无权进行此操作,未提供 New-Api-User"}, which a 401 showed as JSON and a 200 read as a field missing. The editor now says so under the token as soon as it is there: beside …/api/usage/token, or with no Balance URL at all (the token field is now offered on every custom provider, not only one with a URL saved), a warning with one button, Use https://<site>/api/user/self, which sets the URL, sets the Balance field to $data.quota / 500000 when it was empty or named /api/usage/token's fields, and opens More endpoints to show it; once the URL is /api/user/self and no New-Api-User header has a value, it asks for New-Api-User = your user ID (shown in the site's personal settings) until one is typed. The URL is not switched behind the user's back: what the editor shows is what is asked, and the field typed for /api/usage/token's reply wouldn't read /api/user/self's anyway. Behind it, Balance never sends a token to /api/usage/token and says what to set instead; a reply of success:false with a message, at a 401 or a 200, is that message as the error, with the header to add put in front when it is about New-Api-User (not provided, format error or mismatch, in new-api's Chinese or English); and /api/user/self with an empty field reads the quota, $1 to 500000. The Balance field has a Check balance button (POST /api/provider/balance) that asks as the form has it, before a Save, the saved provider with the form's URL, field, headers and token; it and the Usage page's card say either fix plainly, the relay's own words on hover. A JWT beside new-api's URL is not warned about: new-api's own dashboard sign-ins are JWTs now, so a token's shape can't tell new-api from a sub2api panel. Tested with new TestKeyUsageWithAccessToken (never asked, the fix said, the key alone still asks), TestUserSelfDefaultField and TestNewAPIUserRefusal (401 and 200, Chinese and English, the mismatch, and an unrelated refusal left as it came), which all fail on main; and a new internal/gui/tests/balance-fix.test.cjs in Chromium and WebKit (the warning on the saved setup, the click, the header hint going once typed, Check balance with and without the header and what it posted, Save, the Usage card's words, every new string in Chinese, and a new provider with a token and no URL). go build (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the other GUI tests pass
yetone
2026-09-29 17:42:30 +08:00 -
037189ebdb
agents: a provider switched off or removed, or its last account signed out, moves the agents on its models to others (#200: 希望关闭某个llm provider的时候,更改agent选择的model到可用的model … 关闭这个provider之后,第一次使用会报错,因为copilot/ 开头的模型不存在) — switching Copilot off took copilot/… out of the catalog and the agents' model lists, but an agent picked on one kept it in its config and its next request was refused as switched off. Now every field on one of magpie's models (the model, Claude Code's opus/sonnet/haiku/fable tiers, any other) whose model the change stopped serving is moved: to the same model from a provider still on, the same id first, else one spelt another way (claude-sonnet-4.5 and claude-sonnet-4-5-20250929), else back to the agent's own default (a Claude Code tier: following the main model). It is done around switching off, removing, a key removed or off, and a sign-in signed out or off, in the window, the tray panel, the TUI and the CLI; the page's status line, the TUI and the CLI say which agent went to what. A session started before the switch still asks for the old model: the gateway now takes a switched-off provider's model as unserved, so Claude Code's stand-in (its model as set now) answers it rather than a refusal. Tested in a sandbox HOME: Claude Code on the main model and three tiers plus Hermes, switched off and removed (same id, other spelling, default, a tier following), switched on again moving nothing; /api/provider/off returns the moves; the gateway serves a switched-off model through the stand-in (fails without the change) and still refuses it to an agent left on it
yetone
2026-09-29 17:37:46 +08:00 -
f9c99cf370
gateway: a call Codex makes for itself says so on the Routing page, and goes to a pool account with what Codex says about it (碳碳双键: 为何 codex 桌面选的 5.6sol,但是请求记录…显示的全是 gpt-6-luna) — magpie changes no model: Codex sends its own calls on Luna (the guardian review of an approval, x-openai-subagent: guardian; a thread's title; memories) and, on Luna Reserve once the plan's allowance is used up, every turn with x-openai-codex-luna-reserve. Relayed to Codex's own sign-in these headers went on as they came, but a turn the pool of Codex's accounts served was signed afresh and went without them. Now x-openai-*, x-codex-* (turn metadata, installation, window, parent thread), session_id, conversation_id, x-client-request-id and version go on with it too; never a sign-in (Authorization, chatgpt-account-id, anything with authorization in its name) nor Codex's device attestation, the pool account's own going in its place, nor what holds for Codex's own account alone (x-openai-codex-luna-reserve, as another account's allowance may be left, and x-codex-turn-state, the backend's routing of the turn for that account). The trace, the Recent calls and the usage log keep the call's kind (kind, from x-openai-subagent; memgen; luna_reserve), and the Requests list, a replay and the request's story show it as a grey tag by the model — Approval check / 审批判定, Title / 标题, Memory / 记忆, Subagent / 子代理, Review / 审查, Compaction / 压缩, Luna Reserve / Luna 储备, an unknown one as Codex names it — the story saying Codex made the call itself and picks its model. The Requests list's model column fits its content (up to 36%) so model and tag keep their room, the tag giving way first. Tested with a fake ChatGPT backend on the pool path (acct-1 used up, acct-2 answering: both get the headers and neither the client's sign-in; fails without the change), callKind/codexHeader cases, and a new internal/gui/tests/routing-kind.test.cjs in Chromium and WebKit, en and zh (tags, the model shown whole, the story, a click leaving the row where it is); the other GUI tests, go build -tags nogui, GOOS=windows go build and go test -tags nogui pass
yetone
2026-09-29 17:31:41 +08:00 -
bdb1dfdd1b
providers: a Command Code sign-in goes through when whoami errs on the new key (tigger_ultra on X: Sign-in didn't finish · Command Code didn't take the new key: Internal Server Error) — magpie asked /alpha/whoami to name the account of the key Studio had just posted, and failed the sign-in on any answer but a 200; whoami answered that key with a 500. The CLI's own browser sign-in never asks whoami (it keeps the key and the name Studio posts; whoami is its check on a key typed in, and only a 401 there means a bad key). Now only whoami turning the key down (401, 403) fails it; any other error is asked again twice, a second apart, and then passed over for the name Studio sent, the plan still read from billing/subscriptions. With no name from Studio either, it fails saying so. Tested: TestCommandCodeSignInWhoamiDown, a fake Command Code whose whoami answers 500: signed in as the posted name with its plan after three asks, a 401 key refused, no name at all refused; go test -tags nogui ./... (TestClaudeSignedOut failed once under load, passes on its own with and without the change and in a rerun), GOOS=linux build
yetone
2026-09-29 17:41:26 +08:00 -
dc31c483da
providers: Codex on Amazon Bedrock's GPT models goes to the runtime's Responses API (#176: Amazon Bedrock: Function tools with reasoning_effort are not supported for global.openai.gpt-6-luna in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'.) — the preset gave Bedrock chat completions alone for every model not Claude, so Codex's Responses, tools and reasoning in it, were translated to chat, which the runtime refuses for its GPT models. bedrock-runtime serves the Responses API at the same /openai/v1 with the same Bedrock API key as a Bearer, for OpenAI's closed GPT models (not gpt-oss, which stays on chat): the preset and each region now have it, a Bedrock provider saved before is given it where its chat is, and a GPT model is asked there first (Codex's request relayed as it came; a chat client's taken there once chat refuses it). A chat endpoint with no Responses to go to that refuses tools with reasoning so is asked again with reasoning_effort "none". Tested with a fake runtime returning that 400 on chat and serving /openai/v1/responses (Codex and a chat client with tools, a provider saved without Responses, gpt-oss still on chat) and a chat-only relay refusing the same (retried at none); both fail on main. Not tested against a real Bedrock account
yetone
2026-09-29 17:23:47 +08:00 -
7ca2debfe0
gateway: Connect folds away under its heading (zhanglolo: 把网关 tab 这里面接入加一个折叠的功能,这个接入使用很低频,但是太占面积了,相对来说下面的最近调用使用频率更高) — Connect is set up once and seldom looked at again, yet took most of the page above Recent calls. Its heading is now a fold with a chevron, as Models has been: folded, its fields go and the head keeps the base URL for the API picked, with a copy button, so the one thing reached for is still a click away; the fold is remembered per machine (magpie.gwConnectFolded) and a click on it leaves the heading where it is. The tray panel has no Connect. Tested with a new internal/gui/tests/gateway-fold.test.cjs in Chromium and WebKit (fold with the view scrolled, the URL in the head, a reload, unfold); the other GUI tests and go build -tags nogui pass
yetone
2026-09-29 17:02:54 +08:00 -
0a11252a27
groups: a routing group with a ChatGPT model in it offers Codex's ultra (TJHHHH: 似乎在使用路由时不支持ultra模式 — GPT-5.6 Terra · routing group in the Codex app had no ultra) — a group offers the reasoning levels its members have in common, and ultra (max, with Codex handing parts of the task to agents of its own) is a level of ChatGPT's own models alone, so the Terra group with codex/gpt-5.6-terra and the API's and Copilot's gpt-5.6-terra lost it and stopped at max. A group now offers ultra too when a member has it and every member that follows the agent's effort reaches max; one with a member below max, or with no ChatGPT model, is as before. At the gateway ultra was worse than missing: a Responses request made again for a member's own endpoint (Codex to a Chat or Anthropic member) read ultra as no effort at all and asked for none, and one passed on as it came sent a member whose levels magpie doesn't know "ultra" as it was. Ultra is now max to every member without an ultra of its own (or the nearest it has below), and a ChatGPT model keeps getting ultra, which it sends the backend as max as before. Tested with a new TestGroupUltra: the group's levels with and without a ChatGPT member and with a member at high, and Codex's ultra through the group to a member translated from Responses, a Chat member passed as it came and one with unknown levels (the levels and the translated and unknown-level cases fail on main). go build (and GOOS=windows) and go test -tags nogui ./... pass
yetone
2026-09-29 17:02:45 +08:00 -
3efba14ed5
qoder: thinking turned off reaches Qoder as off, a refused usage sign-in no longer lapses the account, only a gone sign-in is a 401, and a tool's images are sent (#162 review follow-ups)
yetone
2026-09-29 16:51:24 +08:00 -
2ed6dd0520
routing: a request clicked in the Requests list leaves the page where it is (点击这里的任何一个空间都会自动滚动到页面的最顶部,这是重大bug) — picking a request scrolled the view up to the stage on purpose (scrollIntoView with the reader's click in hand, the one scroll the click hold lets through), and the stage is at the top of the Routing page, so every row clicked threw the reader to the top. It no longer scrolls: the row stays under the pointer and the stage and its story change above it; the 'errors your agent saw' count does the same. Live, a day and Replay them all were already held. Tested: a new click-scroll.test.cjs case clicks three rows of a day and Replay them all, each must stay within 1px, in Chromium and WebKit; it fails on main (picking request 3 moved the page 393px) and passes with the change; menu-scroll.test.cjs passes
yetone
2026-09-29 16:58:02 +08:00 -
ae597fc3d9
tray: Gemini CLI with no sign-in chosen folds away with the other agents at their default (willz: 电脑上没有安装 gemini cli,第二个选项无法设置为默认,我不需要 gemini 的配置,它应该像其他agent 一样,默认后就被收起来了) — the tray keeps up the list the agents that have something set (a field with a value) and folds the rest under "Show {n} more"; Gemini CLI's sign-in field read as Google when settings.json chose none (security.auth.selectedType unset, or no settings at all, ~/.gemini left by another tool with the CLI not installed), so it always had a value, stayed up the list, and its default could not be picked back. It now reads as the default when none is chosen, as with no CLI at all; one chosen reads as before. Tested with a new internal/agent/unset_test.go: every agent in an empty HOME reads every field empty (Gemini's sign-in was the only one that didn't), and Gemini's sign-in unset, with only the look set, each chosen type, picked Google and back to the default. go build and go test -tags nogui ./... pass
yetone
2026-09-29 16:34:42 +08:00 -
680435965f
tray: expanding and collapsing on the Agents page keeps what was clicked where it is, and "Show {n} more" unrolls the rest under it (碳碳双键: 这个地方展开 ui 会偶现疯狂抖动,必现拉扯; #199: agent页面展开与收纳会抽搐 甚至跑在窗口外面去; willz: 点击展开的时候,列表不是向下展开 而是向上展开) — the rest unrolled above the button, so in a panel at its tallest with the list scrolled the button slid down and off the foot of the view. What a click holds in place was passed over while anything played on it, a colour easing in included: the button's own hover fade and its label's fade meant the page was held by the list instead, so the button went off-screen for ~300ms and then the page snapped 90px back to it; and a button slid out from under the pointer and back fades its hover again, so the page swung between the two (484 → 563 → 484 → 572 … frame by frame in Chromium). Held elements were also put back a frame late, from requestAnimationFrame, so what was clicked trembled by a frame's growth. Now only a play that moves an element (transform, top, margin …) passes it over, the hold is put back as soon as a growing part is laid out (a ResizeObserver, before paint), and the button comes before what it unrolls: the rest come in under it, the rows above stay with it, and in a view with no room below the view goes down with them (scrollOnPurpose with the click), led by the fold's foot frame by frame, never past the button's top; Show less rolls them up under the button, which stays put. Tested in Playwright Chromium and WebKit with a stub panel (13 agents, Codex open, list scrolled to its end) and the window: new internal/gui/tests/panel-fold.test.cjs checks each painted frame of Show more (the view only goes down, the row above keeps with the button, the rest below it, then in sight), Show less and a row opened and closed (what was clicked within 2px); it fails on main (button moved 99px, the swinging in Chromium). All node tests and go test -tags nogui ./internal/gui/... pass; click-scroll's "the view must be at its end" also flakes on main under load
yetone
2026-09-29 16:29:22 +08:00 -
d924802de5
usage: a streamed reply's time to its first token is kept, and its first text's (#196: usage 记录首字延迟(TTFT),用于计算模型的真实生成速度) — usage.jsonl had only a call's whole ms and its out tokens, so how fast a model writes couldn't be told from how long it waited or thought. The reply is now read as it goes to the agent, in the agent's protocol whatever the vendor spoke (Anthropic content_block_delta text/thinking/input_json and a tool_use's start, Chat content/reasoning_content/reasoning/tool_calls, Responses output_text/reasoning/function_call_arguments deltas and a function call's start, Gemini parts), and each record gets ttft_ms to the first content and first_text_ms to the first text, counted from the request as ms is, so ms − ttft_ms is the writing time; a reply that isn't streamed gets neither, and omitempty leaves old records as they were. Only the stream's events up to its first text are read (a JSON reply, or past 8 MB, not at all). Each try in the routing trace keeps its own from when it was sent, so a group that fails over shows which member was slow, and the route, the request log and the Codex backend's relay keep them too. Shown on the Routing page (TTFT in the list, first token / first text / tok/s in a try's story), in the Gateway's request log, and on the Usage page and in
magpie usageper model as the mean TTFT and tok/s = out ÷ (ms − ttft) over the answered streamed calls. Tested with fake vendors streaming a gap apart for Anthropic, Chat and Responses, as they came and translated (Chat from Anthropic, Responses from Chat), the ChatGPT backend without a Content-Type, a whole reply, a group whose first member fails (its try untimed, the second's timed from its own start, the request's including the failed try), each protocol's events and split writes, and the summary's sums; the GUI's browser tests pass
yetone
2026-09-29 16:32:08 +08:00 -
30300c40d0
providers: NVIDIA and ModelScope are presets (#197: 增加 NVIDIA NIM 和魔搭 ModelScope 作为供应商预设) — NVIDIA NIM at integrate.api.nvidia.com/v1 with a key from build.nvidia.com, and 魔搭's API-Inference at api-inference.modelscope.cn/v1 with a ModelScope access token (not DashScope, which the Qwen presets are), each with its models.dev catalog (nvidia, modelscope) and its logo. NVIDIA's /v1/responses answers 404 for its models but a few, so it has chat completions only; ModelScope's /v1/responses is served (401 without a token, 404 for a path it doesn't know), so it has both. Neither has an Anthropic endpoint here. An entry imported at either base URL is the preset, and the import link pages and README list them. Tested: the presets' endpoints, catalogs and import, a /v1/models answer shaped as each sends it (NVIDIA's embedding models left out, ModelScope's object "" rows kept), and catalog.FetchAt against the live integrate.api.nvidia.com (74 models) and api-inference.modelscope.cn (35) lists
yetone
2026-09-29 16:14:02 +08:00 -
95d71138bf
bedrock: Claude Code's betas are fitted to what Bedrock takes (#176: API Error: 400 Amazon Bedrock: Unexpected value(s)
advanced-tool-use-2025-11-20,prompt-caching-scope-2026-01-05,redact-thinking-2026-02-12for theanthropic-betaheader) — Bedrock's runtime checks anthropic-beta by name and turns the whole request away for one it doesn't know, and magpie passed Claude Code's betas on as it sent them. Now, at Bedrock, tool search is asked by Bedrock's name for it (tool-search-tool-2025-10-19, as Claude Code asks Bedrock itself) and the two it refused are left out; and at any provider, a 400 naming betas as unexpected is asked again once without them, and they're not sent to that provider again, so a beta Claude Code adds later costs one retry, not a failed turn. An anthropic_beta list in the body (how Bedrock's InvokeModel takes betas) is fitted alike. Tested against a fake Bedrock runtime that refuses unknown betas as the real one does: the issue's betas plus an unknown one go through on the first turn with one retry and straight on the next, and body betas (fails on main)
yetone
2026-09-29 16:11:52 +08:00 -
81736b2fb2
subscription: Qoder is a subscription you sign in to and route throug… (#162)
Ethan Xu
2026-09-29 16:40:20 +08:00 -
e4404dbd6b
agents: Kimi Code, Moonshot's kimi CLI, is an agent magpie sets up (agent求个kimi code支持) — it keeps its settings in ~/.kimi/config.toml ($KIMI_SHARE_DIR's), its default_model a key of its [models."…"] tables, each naming a [providers.…] one. Picking a model through magpie adds the provider "magpie" (the gateway, as Kimi's own chat completions type, so the thinking goes out as reasoning_effort and comes back, and is sent back, as reasoning_content) and a table per catalog model keyed "magpie/<provider>/<model>", with its context (128K when not known) and thinking/image_in capabilities, so the catalog is in Kimi's /model picker and kept up to date as providers change; the Kimi Code sign-in's default is stashed and put back on reset, and the tables go in and out in the order Kimi's own check wants (a default or a provider it can't find makes it refuse the file). Its requests are Kimi Code's in usage (KimiCLI/…). The gateway also drops Kimi's thinking: {type: …} on a request relayed to api.openai.com, as it does Qwen's enable_thinking. Tested: the config in the shape a Kimi Code sign-in leaves it (set, sync, a moved base_url, reset, its own model, no config at all), and the real kimi 1.24.0 --print against a sandbox gateway and a fake upstream: reply and thinking shown, reasoning_effort high on a thinking model, reasoning_content sent back on the next turn, usage under Kimi Code; not with a real vendor behind it
yetone
2026-09-29 16:36:33 +08:00 -
b95dab91d8
agents: "Show 7 more" unrolls the rows downwards (Image #22: 点击这里为什么是向上展开) — a click holds what was clicked where it is on the screen (v0.1.377), and the button sits under the rows it opens, so as they grew above it the view scrolled to keep it put and the page rode up past them. A control marked data-unrolls is held by the part it is in instead: the rows open down and the button goes down with them. click-scroll.test.cjs checks it in Chromium and WebKit (it fails without the change)
yetone
2026-09-29 16:29:33 +08:00 -
9e9c2ce96c
providers: StepFun and StepFun (China) show the Step Plan's 5-hour, weekly and credit windows on the Usage page (StepFun 国外版跟国内版这两个provider都应该支持显示额度) — the platform tells them only to a sign-in on platform.stepfun.ai or .com, never to a key ("api key not permitted for this method"), and its session cookie is HttpOnly with no way back to an app, so the user signs in in their own browser and brings the session over: the provider's editor has a bookmarklet that, on the signed-in platform page, asks the platform to renew the session as the page does and copies it, and a box to paste it into (the Oasis-Token cookie from developer tools is taken too). magpie keeps it in stepfun-auth.json (0600), one per site, renews it before it runs out and once more when it is refused, and asks QueryStepPlanRateLimit and GetStepPlanStatus for a card with the plan, its windows and when it ends; an anonymous session, another site's, or one renewed as someone else (past the refresh token's 30 days) is not kept or shown. Tested with a fake StepFun platform (renewal, early expiry, another account, pastes of each kind), the bookmarklet run against a stubbed page, and the field in a sandbox magpie web (signed out, a bad paste, signed in, sign out); not with a real StepFun sign-in
yetone
2026-09-29 16:04:43 +08:00 -
f770ad2aef
gateway: a conversation moved from Grok to Codex's GPT on a ChatGPT sign-in goes on (waroy: The encrypted content for item rs_ could not be verified … routing grok > codex GPT) — the ChatGPT backend streams its refusal of Grok's sealed reasoning as response.failed, after a codex.rate_limits event of its own; magpie held a stream only through the events it knew come before a reply, took codex.rate_limits for content and let the stream through, so the refusal went to Codex rather than being asked again without the sealed reasoning, and nothing was noted to leave it out of later turns. codex.* events now count as coming before the reply, so the refusal is caught, the member asked again without the reasoning, and what it refused left out up front on later turns. (The id reads rs_ as magpie sends the ChatGPT backend no item ids.) Tested with a fake Grok member and a fake ChatGPT backend in a group, refusing as a 400 and in the stream after codex.rate_limits (the stream case fails on main)
yetone
2026-09-29 15:27:24 +08:00 -
4216d2b338
redact: a gateway's key in "key": "oc_sk_…" is masked, and Settings → Privacy takes the user's own masking rules (#195) — an OpenCode Go key in providers.json went to the vendor as it was: the API key rules wanted sk- with a hyphen, and a field called key alone was none of the secret field's names. Now a key of a gateway or relay, two to four letters and _sk_ (oc_sk_, or_sk_) with digits in it, is an API_KEY wherever it is, and a field named key and nothing more (not sort_key, monkey or obj.key) is a SECRET when its value is 16 or more letters and digits both, so a map's "key": "value" and key = "someIdentifier" are left alone; \"key\": \"…\" in JSON inside a string is found too. The secret field's names and its markers come from one list, so they can't drift apart, and of two rules finding the same value the first keeps it, so its placeholder no longer changes between an API_KEY and a SECRET from one request to the next. Masking rules, next to 脱敏词: a name for the placeholders (upper case letters, digits and _, 24 at most) and a prefix (then 8 or more key characters) or an RE2 regular expression, group 1 the value if it has one, masked with the secrets while 脱敏密钥 is on and put back as the others are, the user's name winning where one of magpie's finds the same value. They are set on their own: a pattern that doesn't compile, matches nothing at all, or is too long (300), a prefix under 3 or over 64, both at once, or more than 32 rules, is refused with the reason under the add row and what was typed kept. Tested: every row of the issue's table and the false positives, the rules' tidying, errors, stable placeholders, restore, overlaps, and kinds with digits in a stream; the settings file and the gateway's count_tokens with a rule; the page in a stub magpie web (add, a bad regex, remove, zh dark)
yetone
2026-09-29 15:25:13 +08:00 -
04fb5cbf92
providers: StepFun and StepFun (China) show what is left on the account (StepFun 国外版跟国内版这两个provider都应该支持显示额度) — both answer a key, a Step Plan one too, with the account at /v1/accounts (api.stepfun.ai in dollars, api.stepfun.com in yuan), whose balance is what is left to spend, vouchers included; it is now a card on the Usage page beside the others. Checked against the live api.stepfun.ai with a Step Plan key, on the plan and the pay-as-you-go address. The Step Plan windows are told only to a platform sign-in, not to a key
yetone
2026-09-29 15:23:10 +08:00 -
4f5699aae5
providers: Kiro and Devin keep several accounts, as the other subscriptions do (Kiro要支持增加多个账号…Devin也要能够增加多个账号) — both only borrowed their CLI's one sign-in (Devin wrote devin's credentials.toml, Kiro kept one kiro-auth-token.json), so signing in again replaced the account. The CLI's or IDE's own account is now read as it is and each further account gets a home of magpie's: Kiro's token file is kept and refreshed there, and Devin's credentials.toml sits in a data folder the devin CLI is run with (XDG_DATA_HOME / APPDATA), so
devin auth statusandmodels listanswer for it. They are listed, switched, ticked on and forgotten like the others, and the gateway moves on to the next when one is out; the single Kiro sign-in kept before is moved into a home of its own. Tested with a fake Kiro and a fake devin CLI (second account, signing in again, switching, forgetting); not with real accounts
yetone
2026-09-29 15:20:20 +08:00 -
31f2fc8ed4
bedrock: GPT models are asked a reply's length as max_completion_tokens, and Claude Code's metadata is left out (#176: 400 Unsupported parameter: 'max_tokens' is not supported with this model; Request metadata contains a value that violates the regular expression) — Bedrock's OpenAI endpoint turns max_tokens away for its GPT models, so the endpoint test and every chat request to it (sent on as it came or built from another API) now ask max_completion_tokens; and Claude Code's metadata.user_id, a JSON string now, is not the plain id Bedrock's Anthropic endpoint checks it against, so it is dropped there. Tested against a fake Bedrock runtime (both fail on main)
yetone
2026-09-29 15:12:44 +08:00 -
12d79dd9f5
providers: Baidu Qianfan Token Plan lists no kimi-k2.6 (#193 follow-up) — Baidu's Token Plan 个人版 page takes Kimi-K2.6 (and DeepSeek-V4-Flash, already left out) offline 2026-09-29, the day the preset landed, so it was dropped from the models the preset lists; it can still be typed in by hand while it answers. gofmt on the preset and its gateway test
yetone
2026-09-29 14:38:54 +08:00 -
a284b61bb1
providers: an image model's Test models draws one small picture on the images API, as the gateway draws with it (mamba: 图像模型点测试模型是不是发送的请求不对啊,总是提示响应异常) — every model picked was sent a chat completion ("hi", max_tokens 16), so gpt-image, flux, seedream and the like, which a vendor serves only at /images/generations, were turned away and always showed as not answering. Now a model the gateway draws with on an images API (catalog.ImagesAPI, the list viaFor used, shared) is asked there for one picture of "a dot", n=1, at gpt-image's low quality and the vendor's default size, waiting up to two minutes (the request up to three); a 404 or 405 there is tried in chat as the gateway does. Chat models and gemini-*-image / gpt-5-image (which draw in chat) are asked as before, and the vendor-wide Test asks a chat model ahead of one that draws. Tested with a fake vendor that serves images only at /images/generations and chat only at /chat/completions
yetone
2026-09-29 14:36:01 +08:00 -
6ba83dbbbb
providers: add the Baidu Qianfan Token Plan (个人版) preset (#193)
Austie
2026-09-29 14:37:24 +08:00 -
62ded971c0
codex: retry unused 5-hour windows after warm-up (#192)
Ns2Kracy
2026-09-29 14:29:06 +08:00 -
3c02cbd5c9
routing: Stop replay and the speed answer a click in the app, and the clock's fast-forward reads as that (Discord: willz — 停止重放无效, 1× 速度一会儿 4x 一会儿 8x) — the replay's bar wrote its buttons' text every frame, and WebKit sends no click to a button whose text was replaced between the press and the release, so in magpie's window Stop and the speed did nothing and the replay ran to its end. Now each is written only when it changes. The N× beside them was the clock running fast through a quiet stretch cut short, not the speed: it now says so (跳过空闲时段 · 时钟 N×). Checked in WebKit against a sandbox magpie web: before, mousedown and mouseup reach Stop but no click and the replay goes on; after, it stops
yetone
2026-09-29 14:24:18 +08:00 -
bd71a587ef
routing: a failed try shows what its vendor said, and the errors count opens the newest failed request (Discord: 蓝猫, Devin 502 — Agent 收到的错误 did nothing when clicked) — the trace kept each try's error word for word, but the page only told magpie's reading of it (403 · failed), so the vendor's own reason was nowhere to be seen. Now it is under that try, and clicking the errors count picks the latest request whose agent got an error. Checked in a sandbox magpie web with a fake vendor answering 403
yetone
2026-09-29 14:08:03 +08:00 -
17ad8f9332
agent: a goose on PATH that is pressly's migration tool isn't taken for Goose (Discord: Jun, Goose listed with no ~/.config/goose) — any goose on PATH counted as the agent, and the Go database migration tool of that name is common among Go developers. Now a goose on PATH counts only when it isn't a Go program (Block's goose is Rust); goose's config dir still counts as before
yetone
2026-09-29 14:04:32 +08:00 -
38cbbaaf01
devin: fast runs are one model with an effort, and the Devin agent's own picker is families + effort too (蓝猫 on Discord: Fast 模型还是存在同样的问题,给 Agent 选择模型的时候还是同样显示) — v0.1.371 made each family one model with its efforts, but a variant with a tier after its effort (claude-opus-5-5-high-fast, gpt-6-sol-high-priority) still stood alone, one per level, and the Devin agent's model field still listed every variant flat. Now a family's fast (and priority) variants are one model, claude-opus-5-5-fast, whose effort the gateway turns into the variant at it (nearest level; the family's default with none asked), and ids ending -none join their family at none. The Devin agent offers the same families with an effort field beside the model; Devin's config has only agent.model, so the pair is written as the variant at the effort (the family's own id with none, so it keeps following the family) and read back apart. Ids an agent was set to before, and one typed in, go to Devin as they are; with Devin's list unreadable the id is read and written as it is. Checked against the real devin models list (85 models, the Opus fast runs low–max) and with tests of the agent's get/set on a temp config
yetone
2026-09-29 13:55:56 +08:00 -
ba9f0c610d
routing: a group's member may be a model at an effort of its own, provider/model:low (#189, @luw2007: model + effort 才是完整的组合项) — a member was only a model, reasoning as the agent asked or as the group's classifier picked, so "the same model, cheap and quick" and "the same model, thinking hard" could not both be in a group, nor could a rule send to one of them. A member now may carry an effort (none … max): a request routed to it is asked for that much reasoning whatever the agent asked or effort=auto picked, at the level the model has nearest, while the vendor is asked for the model's own id. The same model at two efforts is two members that rules (use=m:high), stickiness and the trace tell apart; an id whose own last part is a colon (a :free, :7b, :0) stays the model's. The group's reasoning levels are those of its members that follow the agent, and the editor has a reasoning button on each member. Tested with a fake provider through a sandbox magpie: the upstream bodies carry the fixed effort in Chat, Responses and Anthropic shapes, and the routing view shows m1:low / m1:high as two seats, light and dark
yetone
2026-09-29 13:45:29 +08:00 -
74242b9d11
workbuddy: an unfinished chunk's empty finish_reason reaches the agent as null, and a repeated empty tool-call name no longer rides along — CodeBuddy CN (copilot.tencent.com) says "finish_reason": "" on every chunk that hasn't finished, where the OpenAI contract has null, and a strict client (the AI SDK) rejects the chunk for the empty value; it also repeats "function": {"name": ""} on every tool-call argument fragment after the first, and a client that merges a delta by overwriting any field it carries (the grok CLI) assembles a tool call named "" and fails it with "Tool not found". chatTidy, which already drops the empty reasoning_content of that stream, now sets the empty finish_reason to null and drops the empty duplicate, so the real name is only sent once. Tested against a fake WorkBuddy stream: both shapes mended, and a last-wins merge of the mended fragments assembles the call as it was meant (#190)
xuyang2
2026-09-29 13:43:58 +08:00 -
a3d59eee01
cursor: a model's name no longer says its context, a grey 1M beside it does (Leo on Discord: Cursor 模型的展示名仍保留上游文本中的 1M) — Cursor names its large models "Claude Opus 5.5 1M", and magpie showed that as the name while it already kept the 1M as the model's context. The name now leaves it out ("(1M)" too); a name that would then be another model's ("GPT-5.5 Mini (1M)" beside "GPT-5.5 Mini") keeps it, as that is what tells them apart. Ids, efforts and the Cursor id each request goes to are unchanged, and a name the user gave still wins. So the context still shows, a model holding a million tokens or more has a small grey 1M by its name in the provider's models, the gateway's list and the model picker. Tested with a fake Cursor list in a sandbox magpie web, dark and light
yetone
2026-09-29 13:26:16 +08:00 -
73af4c282c
gui: a click never moves the page, held for every view in one place (为什么点击一个控件总会滚到页面顶部,能否从根上解决) — picking Live after a day emptied the requests' list, the page got shorter than where the reader was, and the browser pulled it up; any code could also call scrollOnPurpose() and scroll on a redraw, a tab switch or a filter. Now what the reader clicks stays where it is on the screen for the next 4s: the element clicked, or once it's drawn again its nearest neighbour or parent, is held in place against content above it changing, and when content below it shrinks, room is kept at the view's foot (a .view-room spacer, gone once the reader scrolls back up) so the page isn't clamped. Code scrolls only with scrollOnPurpose(e) given the reader's own event, and without one the scroll is refused. The wheel, touch, keys and dragging are the reader's and scroll as before. The Routing page's days stay reachable with no live requests, and the library tabs no longer reset to the top. New click-scroll.test.cjs (Live/day switching with the view scrolled to its end, a jump, growth, redraws and helpers with no event, a scroll asked for, the room kept and removed, the wheel) passes 12/12 on Chromium and WebKit; the old code fails it (picking Live moved the page 294px); menu-scroll 22/22
yetone
2026-09-29 13:21:57 +08:00 -
58b676663b
claude: its sign-in is read from the keychain under its account, as Claude Code reads it (@taresky: claude auth status loggedIn, the keychain item there, magpie still said not signed in) — magpie asked for "Claude Code-credentials" by service alone, and with more than one item for it (one an earlier sign-in left, say) the keychain may hand back one that isn't the sign-in in use; now it asks under $USER first, as Claude Code does, and by service alone only when that finds none that reads
yetone
2026-09-29 13:08:36 +08:00 -
89d8a59f15
codex: guide sealed subagent handoffs to supported models (#188)
Zhaofeng
2026-09-29 13:06:14 +08:00 -
21cc3d7647
providers: Kiro is a subscription added in magpie, signed in on Kiro's own page (Image #21: 为什么在订阅列表里看不到Kiro订阅) — Kiro was used through kiro-cli's or the Kiro IDE's sign-in, or a key, so it had no card among the subscriptions and no way to sign in from magpie. It now has a card, and signing in opens app.kiro.dev as the IDE does: Google and GitHub are exchanged with Kiro's auth service, Builder ID and Identity Center go on to AWS with a client registered for magpie, and the sign-in is kept in magpie's own kiro-auth-token.json, refreshed there, and used ahead of kiro-cli's and the IDE's, which are left as they were. A company's own identity provider is still kiro-cli's (kiro-cli login), and the sign-in says so. Tested with a fake Kiro and AWS (Google, Builder ID, a stale answer, an external IdP) and against the live page, token and register endpoints; not yet with a real Kiro account
yetone
2026-09-29 13:01:08 +08:00 -
1d6b415a68
providers: a saved Claude account left out says which check found no sign-in (@taresky: Claude 账号不显示, cc cli works) — on a Mac the note named only ~/.claude/.credentials.json, where Claude Code never keeps its sign-in there, so every cause read the same. Now it says whether no "Claude Code-credentials" could be read from the keychain (and nothing at that file), the credentials are there but claude auth status says no one is signed in, or neither ~/.claude.json nor the CLI names the account
yetone
2026-09-29 12:56:58 +08:00 -
b1d4bca90f
routing: picking Live or a day keeps the page where it is, and a list with no requests says what it is for (Image #59) — a day or Live picked in the requests' bar picked its first request as a click on a request does, which brings the stage at the top into view, so every switch sent the reader up to the top and back down; the stage now comes into view only for a request picked in the list. With no requests, the list is a quiet note (none since magpie started: each request an agent sends shows who answered it, why and each try; earlier ones are under the days) in place of an empty box, and the accounts and keys column, tallying nothing, is left out. Checked in a stub magpie web with the day's history: scroll stays put over five switches (it moved before), dark
yetone
2026-09-29 12:43:50 +08:00 -
233b463d3a
devin: Devin's models are offered as their families, with the effort picked as any model's is (蓝猫 on Discord: 能不能让 devin 也走 model + effort) — Devin writes the effort into its ids (54 families, 625 ids, most swe-2-high, …_MEDIUM), and magpie listed them all. Each family is now one model with the efforts its variants are at, lowest first, and the gateway asks Devin for the variant at the effort asked for, or the nearest the family has; a variant that is no effort (a 1M window, -high-fast) stays a model of its own. A variant already picked stays offered and goes to Devin as it is, and its effort is the one its id says, so the trace shows medium for a Medium variant rather than the max the request asked. Tested with a fake Devin list: families, picked variants, the variant chosen, the efforts and windows offered
yetone
2026-09-29 12:35:34 +08:00 -
15aef1c584
providers: Bedrock's Anthropic endpoint is sent its key as x-api-key alone, and GPT-6 is in its models (#176: Endpoint test failed) — magpie sent a Bedrock key on /anthropic/v1/messages as both x-api-key and a Bearer, as it does for compatible vendors, and Bedrock turns away a request with both; it now gets x-api-key alone, as AWS's own Messages sample sends it, and chat completions keep the Bearer. The preset's models gain GPT-6 Astra, Sol and Luna as their global. inference profiles, which every commercial region routes (they have no in-region id on bedrock-runtime); gpt-oss stays in-region, which ap-southeast-1 doesn't serve. Tested with a fake Bedrock through the gateway and the endpoint test: without the change both headers arrive; not yet against a real Bedrock key
yetone
2026-09-29 12:17:34 +08:00 -
e4375ce967
gateway: a tool's schema reaches Antigravity with only the keywords it takes (#187: Unknown name "exclusiveMinimum") — Antigravity refuses a request whose function declarations carry a JSON Schema keyword outside its subset, and magpie cut out only the ones it knew of, so zcode's and dsh's tools, whose numbers have exclusiveMinimum, were refused with 400 on every turn. Now each schema keeps only type, description, nullable, enum, properties, required, items, the bounds and pattern, and propertyOrdering, anything else left out; an exclusive bound given as a number becomes the inclusive one when there is none. Tested on a schema with exclusive bounds, format, title, uniqueItems, contains, dependentRequired and not
yetone
2026-09-29 12:12:45 +08:00 -
03dbde12d0
accounts: after a Codex switch, magpie says when Codex's background service is still on the account before, and restarts it when asked (vincentzhang on Discord) — Codex CLI can keep an app-server running (codex app-server --managed-daemon) that reads the sign-in once, when it starts, so sessions opened after a switch still ran on the account before. When a switch changes the account while such a daemon runs, Codex's account list shows that it is still signed in as the one before, with Later and Restart; Restart runs codex app-server daemon restart for the Codex home magpie signs in through, which ends the Codex sessions on it, so magpie never does it unasked. magpie accounts switch codex prints the same hint and the command. The notice goes when the daemon restarts or stops, or Codex is switched back. Only a codex program's app-server with --managed-daemon counts: the ChatGPT app's codex and other apps' app-servers don't. Tested with a fake codex and a fake daemon in a sandbox magpie web; the real codex 0.155 has the restart command
yetone
2026-09-29 11:59:51 +08:00 -
3bc3de79d9
usage: the panel says whether its rings are used or left, and a model named free is marked free (#184, #185) — the panel's usage groups carry a USED/LEFT (已用/剩余) pill on their header that turns every ring as a click on a ring does, and lights a moment when it turns, so the turn is seen; a model whose id or name has "free" as a word (OpenRouter's foo/bar:free, free-model, Qwen3 Free, not freedom-7b) gets the green FREE badge in the provider's model chips, the model picker, the Gateway's model rows and the import preview, titled "free: so its name says". Checked in a sandbox magpie web, light and dark, English and Chinese
yetone
2026-09-29 11:57:06 +08:00 -
976ae3f45b
gateway: Claude Code's body reaches a relay as it sent it, but for the model's name (#179: packy: 您的请求体疑似被篡改) — swapping the model re-encoded the whole body, so its fields went out sorted and its <, > and & as \u003c escapes, and a Claude model's request without thinking gained thinking off; a relay that only lets Claude Code in (packy) took that for a body tampered with. The model is now replaced where it stands and nothing else is touched, a Claude model's request is not given thinking off (Anthropic's models think only when asked), and a model that thinks by default (DeepSeek) has it added at the end with the rest left as sent; other rewrites no longer escape <, > and &. Tested with two requests captured from Claude Code 2.1.284 in a sandbox (85 KB with its tools and system prompt): through the gateway to a keyed Anthropic provider they arrive byte for byte as Claude Code sent them; not yet against packy
yetone
2026-09-29 11:47:43 +08:00 -
c348903159
workbuddy: a chat with no system prompt is sent with one (Jerry lee on Discord: 没有 system 消息时报 first message is not system prompt) — WorkBuddy refuses a chat whose first message isn't the system prompt, which scripts and plain chat clients often send; such a chat now starts with a neutral "You are a helpful assistant.", the rest of the body kept as it was, and one that already starts with a system prompt goes as it is. The comment on WorkBuddy AI's client headers no longer says they fixed "unapproved channel": that answer comes from Claude Code's and Codex's own system prompts (#182). Tested on the account's request body, through the provider as signed in
yetone
2026-09-29 11:40:00 +08:00 -
d287a05161
providers: Amazon Bedrock, with a Bedrock API key (#176: 增加 AWS Bedrock 作为 model provider) — Bedrock's runtime serves Claude on Anthropic's messages at /anthropic/v1/messages and the other models on chat completions at /openai/v1, both taking a Bedrock API key (AWS_BEARER_TOKEN_BEDROCK) with no SigV4, so a vendor preset reaches it: pick one of ten regions and paste the key. Claude ids (anthropic.claude…) go on messages only, the rest on chat only, for the preset and for any provider at a bedrock-runtime host; the runtime has no /models, so the preset's list (Claude as global. inference profiles, plus gpt-oss, Qwen3 Coder, DeepSeek, Kimi, GLM and MiniMax) is it, and a profile kept in one geography (apac., us., eu.…) is typed in by hand and takes its context size from the model it names. The region selector scrolls where its regions don't fit. Tested against a fake Bedrock through the gateway (apac.anthropic.claude-opus-5-5 on /anthropic/v1/messages with x-api-key, gpt-oss on /openai/v1/chat/completions with the Bearer key) and in a sandbox magpie web; not yet against real AWS
yetone
2026-09-29 11:29:43 +08:00 -
6b546aa81d
gemini: 3.8 Flash asked of an account it isn't rolled out to goes as that account's Flash (蒙面人 on Discord: 502 错误 for gemini/gemini-3.8-flash) — the list magpie falls back on has 3.8 Flash and 3.5 Flash Lite, and a model picked from it went to Code Assist under that id whatever the account's experiments said; now, as Gemini CLI would, 3.8 Flash goes as gemini-3-flash and 3.5 Flash Lite as gemini-3.1-flash-lite where they aren't rolled out. Tested against a fake Code Assist
yetone
2026-09-29 11:04:46 +08:00