Files
magpie/internal
..
agents: each agent's CLI shows its version and updates in one click (#202 JoeyMa-zh: Agents 页面增加"更新 CLI"功能 — 检查各 agent CLI 的版本并一键更新) — the Agents page now puts each CLI's installed version after its name (read with <cli> --version, 10s timeout, no stdin) and, when a newer one is out and magpie can tell how it was installed, a small "Update to x.y.z" pill. How it was installed comes from the resolved binary: the vendor's own installer first (Claude Code's ~/.local/share/claude/versions → claude update, Codex standalone → codex update, ~/.opencode/bin → opencode upgrade), then a global node_modules (bun, pnpm, else npm, run as npm install -g --prefix <prefix> pkg@latest with that prefix's npm, and only where the prefix is writable), then a Homebrew formula or cask allowlisted per agent (brew upgrade [--cask]); anything else shows the version alone, with no button. The latest comes from the npm registry or brew info --json=v2 (no auto-update), cached 6h (15min after a failure); installed versions are cached by the binary's path, size and mtime. GET /api/agents/cli waits at most 3s and says pending, and the page asks again; POST /api/agents/cli/{id} runs the update (one at a time per agent, 10min cap), reads the version again and says so if it did not change, or gives the command's last lines when it fails. The pill goes busy in place and the page never scrolls; not in the tray panel. zh strings added. Tested with a new internal/agent/cliupdate_test.go (version order and parsing; npm/bun/pnpm/brew formula and cask/native installs, a read-only prefix, a local node_modules and unknown binaries in a fake PATH; an update against a fake registry and updater) and a new internal/gui/tests/cli-update.test.cjs in Chromium and WebKit (versions, pills only where magpie can update, row heights, an update with the view scrolled, a failing one, zh). No real update was run. go build (nogui, GOOS=windows, GOOS=linux), go test -tags nogui ./... and the GUI tests pass
2026-09-29 17:56:43 +08:00
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
2026-09-29 17:56:43 +08:00
images: agents can generate and edit images with a model set in Magpie (让Magpie支持配置图像识别模型以及生图模型, part 2) — the gateway answers /v1/images/generations and /v1/images/edits for any model that draws: an images-API model (gpt-image-1, dall-e, imagen, a relay's) is sent the vendor's own images request (an edit as multipart, image[] per reference); a chat model that answers with images (OpenRouter's Nano Banana and GPT-5 Image, gpt-5-image) is asked with modalities ["image","text"], the size given as an aspect ratio, once per image asked for; Gemini's own image models over generateContent. Either way the answer is the images API's (b64_json with its mime_type, the model's text, usage), recorded in usage like any request. The model is Settings → Images → Image generation: Automatic (a model that draws among the providers set up, from the catalog's image-output models), Off, or any model; a request that names none is drawn by it, and with it Off is refused saying where to set one. Subscriptions draw nothing yet. magpie mcp image is a stdio MCP server with one tool, generate_image (prompt, size, n, path, reference_images): it asks the agent for its roots, saves each image in the project (assets/ by default, a name from the prompt, never over a file, the extension of what came back — a JPEG asked for as hero.png is hero.jpg), and says where; reference images are read from the project and sent as an edit. Its requests carry the agent's own name (claude-code/… magpie-image/1) so usage says which agent drew. Library → MCP servers → Discover has it as Magpie Image, opt-in: no agent is given it until picked, since every image costs what the model charges. Checked in a sandbox HOME against real vendors: OpenRouter's gemini-3.1-flash-lite-image drew a 3:2 magpie over MCP as Claude Code and edited it from the saved file as Codex (usage: claude, codex), AIHubMix's gpt-image-1 answered /v1/images/generations with a 1024² PNG; the Settings row and picker in light and dark, the Library card and its opt-in sheet
2026-09-29 01:52:53 +08:00
images: agents can generate and edit images with a model set in Magpie (让Magpie支持配置图像识别模型以及生图模型, part 2) — the gateway answers /v1/images/generations and /v1/images/edits for any model that draws: an images-API model (gpt-image-1, dall-e, imagen, a relay's) is sent the vendor's own images request (an edit as multipart, image[] per reference); a chat model that answers with images (OpenRouter's Nano Banana and GPT-5 Image, gpt-5-image) is asked with modalities ["image","text"], the size given as an aspect ratio, once per image asked for; Gemini's own image models over generateContent. Either way the answer is the images API's (b64_json with its mime_type, the model's text, usage), recorded in usage like any request. The model is Settings → Images → Image generation: Automatic (a model that draws among the providers set up, from the catalog's image-output models), Off, or any model; a request that names none is drawn by it, and with it Off is refused saying where to set one. Subscriptions draw nothing yet. magpie mcp image is a stdio MCP server with one tool, generate_image (prompt, size, n, path, reference_images): it asks the agent for its roots, saves each image in the project (assets/ by default, a name from the prompt, never over a file, the extension of what came back — a JPEG asked for as hero.png is hero.jpg), and says where; reference images are read from the project and sent as an edit. Its requests carry the agent's own name (claude-code/… magpie-image/1) so usage says which agent drew. Library → MCP servers → Discover has it as Magpie Image, opt-in: no agent is given it until picked, since every image costs what the model charges. Checked in a sandbox HOME against real vendors: OpenRouter's gemini-3.1-flash-lite-image drew a 3:2 magpie over MCP as Claude Code and edited it from the saved file as Codex (usage: claude, codex), AIHubMix's gpt-image-1 answered /v1/images/generations with a 1024² PNG; the Settings row and picker in light and dark, the Library card and its opt-in sheet
2026-09-29 01:52:53 +08:00
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
2026-09-29 17:56:43 +08:00
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)
2026-09-29 15:26:01 +08:00
sessions: OpenCode v2's sessions are read (Kumo31: opencode v2的session改写到新表里了) — OpenCode 2 (v2.0.x, checked against v2.0.18's source) keeps sessions in the same opencode.db but in tables of its own (packages/core/src/session/sql.ts): session_v2 (the session row as before — directory, parent_id, a title that may now be null, time_created/updated, model {id, providerID, variant}, session totals) and session_message (id, session_id, type, seq, time_created/updated, data), not "sessionmessage". A message's kind is its type column (user, assistant, synthetic, system, compaction, idle, …); a user message's data is its prompt {text, files, …} with no part rows; an assistant's has model {id, providerID, variant}, content (text/reasoning/tool), cost, tokens {input, output, reasoning, cache {read, write}} and time {created, completed} (packages/schema/src/session-message.ts, token-usage.ts), counted as before: input without the cache, output without the reasoning (core/src/session/usage.ts). A compaction carries its own tokens, counted on its model, else the one last replied with. On first start OpenCode 2 copies every old session over (core/src/database/v1-migration.bun.ts), leaving session/message/part unwritten and recording kv migration.v1-v2 {"phase":"completed"}; so with session_v2 present only it is read once that is recorded (or no old table is left, as for the pre-split v2 databases it renamed in place), and while the copy is under way an old session not yet in session_v2 is read from the old tables — never both, so nothing counts twice and a session deleted in v2 doesn't come back. Resume stays opencode --session <ses…> (cli/src/commands/commands.ts). Tested with a v2 fixture made in the test from SQL beside stale old tables; the old-table and JSON-file tests still pass.
2026-09-29 00:38:10 +08:00
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)
2026-09-29 15:26:01 +08:00
app: Magpie is in the Start menu on Windows (#158: Windows 便携版首次运行后,无法在开始菜单 / 系统搜索中找到 Magpie — @Aimer779) — the Windows build is one portable exe with no installer, so nothing put it anywhere Windows search looks. Each start of the desktop app (not the CLI, TUI or serve) now makes sure %APPDATA%\Microsoft\Windows\Start Menu\Programs\Magpie.lnk (the Programs known folder) opens the exe it runs as, symlinks resolved, in its folder, with its icon and "Magpie" as its description; one that already does is left be, one for where the exe was before it was moved is pointed here again. It is the shell's own link, made through WScript.Shell with go-ole (already in the build through Wails, now a direct require), in the background beside the magpie:// registration, and a failure is only logged. An update replaces the exe in place, so the shortcut stays good; a magpie left running from magpie.exe.old(-N) (or .new) points it at magpie.exe, never at what was moved aside. HKCU App Paths\magpie.exe is set to the exe (and its folder) so Win+R magpie opens it too. No uninstall entry, nothing copied. MAGPIE_NO_SHORTCUT=1 skips it (tests, sandboxes); other systems do nothing. Tested with the Windows test built here and run on Windows 11: a shortcut made, left as it was on a second start, pointed at the moved exe. Run on Windows 11 as the desktop app: the shortcut made with the exe's folder and icon, pointed anew after the exe moved, left as it was on a start with nothing changed; App Paths set
2026-09-28 22:44:36 +08:00
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
2026-09-29 17:56:43 +08:00
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
2026-09-29 17:56:43 +08:00