main
135
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
34f2078db8 |
site: a partner's website and key links go through usemagpie.ai/go, which counts the click; fetches of the partner list are counted too
The app's own partner counts only come from users who leave the stats on, so a sponsor's reach and clicks are now also counted where the server sees them. /api/partners hands out each partner's website and keysUrl (and a region's) as usemagpie.ai/go/<id>/site|keys[/<region>]; /go sends a "magpie partner go" event to PostHog (partner, link, region, country) and 302s to partners.js's own URL. One in ten list fetches sends "magpie partners fetch" with weight 10 while someone is listed. Events carry a random id each, no install id or address. Ended entries leave the list but /go still resolves them, since added providers keep the links. Privacy text updated in README (en, zh), docs/reference.md and the site docs (en, zh, ja). Run: node --test site/*.test.mjs (27 pass); a scratch copy with a fake partner served rewritten links, 302'd /go to the real URLs and posted both events; PostHog's /i/v0/e/ answered 200 to the payload shape. |
||
|
|
e85be9d6f1 | readme: no stats strip under the hero screenshot (45+ agents, 4 wire APIs, 5 routing modes, 0 config files), in en and zh (the owner) | ||
|
|
f3d5d12ba3 | readme: the intent-routing and nested-routing screenshots are back under the Routing view, in en and zh (the owner wants them) | ||
|
|
9bb2a467e4 |
agent: Snow CLI is an agent magpie sets up: a magpie profile in ~/.snow, made the active one, that Snow App takes in with "Sync Snow CLI API config" (#1457)
Snow CLI (npm snow-ai, MIT, MayDay-wpf/snow-cli) keeps one profile per file under ~/.snow/profiles (or $SNOW_CONFIG_DIR), names the active one in active-profile.json, and copies it over config.json at every start, headless `snow --ask` included. Each profile is the whole snowcfg: base URL, key, request method and models. Snow App (MayDay-wpf/snow-app) reads ~/.snow/profiles/*.json when "Sync Snow CLI API config" is pressed. magpie writes profiles/magpie.json from the profile the user was on (their settings kept, Snow's OAuth sign-in left out): baseUrlMode base at the gateway's /v1, requestMethod chat, the agent's key, the model as advancedModel and basicModel, and the catalog's window and output (200000 and 64000 when not known, the output cut to the window). It makes that profile active and copies it to config.json. The profile the user was on, whether there was an active-profile.json at all, and a magpie.json of their own are stashed; Disconnect, `magpie snow default`, or picking a model of one of the user's profiles puts those back. A Snow from before profiles (config.json alone) gets the default.json Snow itself would make, so its next start doesn't take magpie's config for the user's. Check reports a base URL, key or request method moved off magpie's; Sync rewrites the profile on a port move. A WSL distro's ~/.snow is found and written the same way. Registered: agents.go, the WSL kinds, SNOW_CONFIG_DIR in agentenv, the npm update spec (snow-ai), the icon (Snow App's own, trimmed to 96px), README, docs/reference.md, docs/subsystems/agent-wiring.md, the site's agent list and start.html (en, zh, ja). Run: - go test -tags nogui ./... in a scratch HOME: 44 packages ok. New: TestSnow, TestSnowOwnProfile, TestSnowNoProfiles, TestSnowUsersMagpieProfile, TestSnowCheckSync, TestAgentSnowDefault; snow added to TestOwnProviderNamedAsMagpies, the WSL more-agents tests and TestMaxTokensWithinContextWindow. All fail before this commit (no agent "snow"). - go vet -tags nogui for darwin, linux and windows; gofmt -l clean. - Real Snow CLI 0.9.2 in a sandbox HOME against a sandbox magpie (`magpie serve` on 127.0.0.1:3571, a fake OpenAI upstream on 3561): `magpie snow fake/pro` made the magpie profile active, `snow --ask` answered through the gateway (POST /v1/chat/completions, model pro, logged as agent snow), and `magpie snow default` left profiles, active-profile.json and config.json byte-identical to before. - GUI: agent-layout, agent-connect, agent-models-picker, agent-field-names and models-add-and-icons in Chromium and WebKit; a Snow CLI row checked at 520-960px in en, zh, zh-TW, ja and de in both engines. No new GUI strings. |
||
|
|
d86a4e0568 |
site: the docs have Docker and Remote magpie pages in English, Chinese and Japanese, listed in every docs page's contents and footer and linked from the READMEs (HuanCheng Bai on Discord)
Until now, both were only in docs/reference.md. The new pages are at
/docs/docker, /docs/remote and their zh/ and ja/ versions.
- docker: the image, its tags, ports, the /config volume and the nonroot
user. How to run it with the browser UI (`web --addr 0.0.0.0:3430
--no-open`, MAGPIE_WEB_KEY, the ?k= cookie). Signing in through the
Callback URL paste or `docker exec ... magpie accounts add`. What
/config keeps, the env vars, sharing with gateway keys, and a Compose
file for a NAS. Healthcheck and updates.
- remote: sharing the main magpie, then adding it as a Remote magpie
from the app, the TUI (a/w/m) or `magpie provider add remote-magpie
<key> url=... id=office`. What comes over (models, groups, APIs,
images, System One, embeddings, prices, quotas) and the errors magpie
shows, quoted from remote_quotas.go and lan.go.
The facts were checked against the code, not reference.md:
- the Dockerfile ENV, VOLUME, HEALTHCHECK and EXPOSE;
- docker.yml's tags;
- web_cli.go;
- the lanGuard in internal/gateway/lan.go;
- the remote-magpie preset.
The Docker page's warning says a Docker gateway answers anyone until
sharing is on, because MAGPIE_ADDR is 0.0.0.0 there.
The new links in the top nav bar carry a toc-only class:
- docs.js still lists them in the contents;
- the bar keeps today's width, since nine links overflowed it at
1024-1280px.
`.steps li` gets min-width: 0, so a long inline code no longer widens
the grid past a phone's width, where `overflow: hidden` clipped the
text.
Run:
- Real containers from ghcr.io/yetone/magpie:latest (v0.1.1150):
- the documented `docker run ... web` came up healthy, and its log
output is what the page shows;
- with "lan": true, `Bearer magpie` got 401 and a gateway key got 200;
- a second container ran `provider add remote-magpie <key>
url=http://<a>:3425 id=office` and listed office/test-relay/... models.
- `node --test site/*.test.mjs`: 23 pass.
- Playwright, in Chromium and WebKit, at 1280, 1024 and 390, light and
dark:
- no horizontal scroll and no nav overflow on all 20 docs pages;
- every internal link and #anchor resolves. /zh/ and /ja/ are routed
by the worker and return 200 live.
|
||
|
|
28c31a365e | readme: ordered for a reader deciding whether magpie fits (noffle's Art of README, via better-readme-skill): who it's for and who doesn't need it, then Quick start and Supported agents before the features; the six feature cards that repeated the sections below are gone, usage and sharing are tables, the intent and nested-routing shots give way to the intent docs link; zh mirrors en and gains the missing Qwen Code; every relative link and image checked | ||
|
|
6d1322e58e |
agents, profiles: a profile keeps each agent's model list, and switching profiles switches it (#1368, liuheo)
Saving a profile (+ Save current, magpie save, the TUI) now keeps, beside
every detected agent's fields and the library, the agent's model list as
the Agents page's Pick sets it: the models taken out one by one
(hiddenModels), or "only the models I pick" and those picks
(pickedModels), the order put on them (orderedModels), and the
visibility `magpie visible` narrows it to (visible). It is kept under the
id the lists are kept by (agent.ListsFor), in profiles.json under a
"models" key beside "library". Applying a profile puts each list back
exactly (provider.SetModelLists: one settings write, renamed providers'
ids followed), then the fields, so the model is set last over the list;
the lists are written once through catalog.Touched, the same path a pick
made by hand takes, so Codex's magpie-models.json and the other agents'
model files are rewritten. A list that changed counts in "N settings
changed".
Left out on purpose: fastPicks (how a model is sent, not whether it is
listed, and it stays with the model) and agentEfforts (Cursor Private
Inference's effort is already an agent field a profile keeps).
A profile saved before this has no "models": it is read with none, leaves
every agent's list as it is (unknown, not empty), and is written back
byte for byte. An agent a new profile doesn't name keeps its list too.
A profile's details show a Model list line per agent: "8 hidden", "only
the 3 picked" or "every model shown", in all five languages.
Verified:
- internal/profile TestProfileSwitchesModelList: three profiles (two
hidden; all shown with an order; only-picked), each applied rewrites
settings and Codex's magpie-models.json to that list. Fails with Apply's
list step removed ("applying home changed nothing", the hidden list
not restored) and with catalog.Touched taken out of SetModelLists
(Codex's file keeps the old list).
- TestOldProfileKeepsModelList: an old profile keeps codex's hidden and
claude's only-picked, writes back unchanged; one naming only codex's
list leaves claude's. Fails when a missing "models" is read as empty.
- The built nogui binary in a sandbox HOME (env -i): magpie save home /
save work / use home / use work switched Codex's magpie-models.json
between m1 and m1-m3.
- profile-details.test.cjs (en, zh window and panel, zh-TW, ja, de) in
Chromium and WebKit; fails without the app.js line. With
profile-confirm, panel-profiles, gui-ja, gui-de, gui-zh-tw, i18n-keys
and click-scroll: 38/38 in each engine.
- gofmt -l clean; go vet (darwin), GOOS=linux go vet -tags nogui,
GOOS=windows go vet on profile, provider, gui; go build, linux nogui
and windows builds; go test -tags nogui ./... under a temp HOME, all ok.
Not tried in the real window with a live gateway.
|
||
|
|
b4aa62af96 |
docs: the READMEs' Privacy and the site's "What does magpie send about me?" (en, zh, ja) say a partner's daily counts are sent too
|
||
|
|
f9a1ca0355 | docs: a Privacy section in the READMEs and a "What does magpie send about me?" FAQ on the site's Get started (en, zh, ja): the daily event's id, version and system, and the agents, providers and models by magpie's own ids (a provider of the user's own only "custom"), what is never sent, and the Settings → Privacy switches; node --test site/*.test.mjs passes | ||
|
|
b91569d311 | readme: feedback goes to Discord, and the repository takes no pull requests (only collaborators can open one) | ||
|
|
ae39bc8ec8 |
agents: Qwen Code (qwen) as a managed agent (#1311)
* agents: Qwen Code (qwen) as a managed agent (#1279) Qwen Code (QwenLM/qwen-code, a Gemini CLI fork) keeps custom model providers in ~/.qwen/settings.json modelProviders.<protocol> entries ({baseUrl, envKey, id, name}, envKey naming a variable in the same file's env object) and its session model in settings.model {name, baseUrl}. magpie writes one modelProviders.openai entry per catalog model — wire id the gateway takes, name magpie/<provider>/<model>, every one on env.MAGPIE_API_KEY holding the gateway's key, which is how an entry of magpie's is told from the user's own — and points settings.model at a pick. Stash, Check/drift, Sync, port-move Rewire and disconnect work as for the other JSON-config agents; on disconnect the user's entries, model and key are restored, the file byte-identical to before. Verified: go build, GOOS=linux nogui + GOOS=windows builds, go vet ./...; go test -tags nogui ./internal/agent (TestQwen* pass; TestDriftUnwiredEveryAgent/qwen and TestPortMovesEveryAgent pass after binding drift identity to the entry, not the address), go test -race -count=3 -run TestQwen, go test . (TestAgentQwenDefault: set then 'default' restores the settings byte-for-byte). TestQwen fails with 'unknown agent' when the registration is reverted. Real use: a copy of a live 341-line settings.json in a sandbox HOME — wire, then 'qwen default' left it byte-identical. Not run: GUI suite (no gui/assets change); TestReasonixCLI* and TestPluginHTTP2TakesTheProxy fail on main too (fixture/network), unchanged by this. * agents: qwen honors QWEN_HOME, sets auth type, owns a qwen-specific key name, carries context windows (#1279 review) - $QWEN_HOME stands for ~/.qwen, resolved as the other agents' (via place.getenv, appdir.Getenv; a distro's isn't read), and is in agentenv.Vars so the sandbox clears it - wiring sets security.auth.selectedType openai so qwen skips the /auth prompt; the user's own (including a pre-existing "openai", told from magpie's by a stash marker) is restored on disengage - envKey is MAGPIE_QWEN_API_KEY: a shell-exported or ~/.qwen/.env value wins over the settings' env, so the magpie-generic name is not used - entries carry generationConfig.contextWindowSize from the catalog - a settings.json without modelProviders reads as no models of anyone, instead of gjson's 'cannot use null as iterable' Verified: TestQwen* incl. TestQwenAuthOpenAIOfTheUsersOwn and TestQwenHomeOverride, TestAgentQwenDefault (CLI), -race -count=3; unwired-drift and port-move generic tests pass. Real end-to-end: dev gateway on 127.0.0.1:3999 in a sandbox HOME, real qwen binary in a sandbox QWEN_HOME ran a prompt through the gateway (PONG), and wire → default left the settings byte-identical. Reasonix fixture tests fail on main too (environment). * agents: qwen's requests are attributed by their QwenCode/<version> User-Agent |
||
|
|
d2241a5798 | agent: Copilot in JetBrains IDEs takes magpie's models as a Bring Your Own Key custom endpoint provider, written into the Copilot language server's byok.json beside the user's own (KamToHung on Discord) | ||
|
|
e52d1cb820 | docs: README rewritten for what magpie does now — stat line, flow diagram, warm-up, context, image MCP, keep awake, agent updates, all 47 agents; "coding agent" is now just "agent"; per-agent notes moved to the reference | ||
|
|
efd501ec74 |
agent: add Reasonix Studio with separate Executor and Plan models (#427)
* feat(agent): add Reasonix Studio 2.x integration
* agent: select Reasonix planner independently from executor
* agent: restore Reasonix when its credential file was initially absent
* agent: preserve Reasonix planner choices and model controls
Keep configured and saved native planner references selectable, and retain full model picker behavior and role labels. Exercise native planner restoration and both browser layouts. Separate temporary CLI first-launch work from version and intentional timeout assertions.
* test(gui): follow the current Reasonix connection flow
* test(gui): open connected Reasonix details before checking roles
* agentenv: isolate Reasonix configuration in test sandboxes
* gui: restore Reasonix roles without disconnecting the other
* gui: Reasonix's role labels read in every language, and its confirm names them
The review found four things on the Reasonix roles' strings.
"executor" and "planner" are field labels like any other, so t() translates
them; they had no i18n entry in any language, so the zh, ja and de windows
showed the English key. They now read 执行模型/规划模型, 実行モデル/計画モデル
and Executor-Modell/Planner-Modell, beside model/effort/thinking in each block.
The confirm said "neither Executor nor Plan" — names that appear nowhere in
the UI — and its zh text said "Executor 和 Plan", while the fields are labelled
executor and planner. It now names the labels, and each language names its own
translation of them (zh 执行模型和规划模型, ja 実行モデルも計画モデル, de vom
Executor- bzw. Planner-Modell).
Both new strings had a zh entry and no ja or de entry, which is what turned
gui-ja and gui-de red: every zh key needs its ja and de with the same
placeholders. "restore the previous {field} selection" also started at column
0 in i18n.js; it is indented with the lines around it now.
reasonix-roles.test.cjs asserts the rendered label, so its zh expectations name
执行模型/规划模型; the en ones stay as they were, the en label being the key.
Its en assertions on the confirm's first sentence are unchanged, and the
reworded second sentence is not asserted.
Verified on this head: gui-ja passes 3/3 and gui-de 3/3 (every zh string has
its ja and de, same placeholders and tags); reasonix-roles.test.cjs passes 8/8
in Chromium and WebKit, en and zh, window (1000px) and panel (440px); go vet
./... and the four builds pass. The rest of internal/gui/tests was run and
compared against a clean main worktree at
|
||
|
|
a711f428ad |
agent: CodeBuddy Code (Tencent's CLI, codebuddy/cbc, the China edition too) is an agent that uses magpie's models (#980, yujianbinm) — the CLI (npm @tencent-ai/codebuddy-code 2.161.4, one package for codebuddy.ai and codebuddy.cn, CODEBUDDY_INTERNET_ENVIRONMENT=internal) takes models of one's own from ~/.codebuddy/models.json ($CODEBUDDY_CONFIG_DIR), the format WorkBuddy reads, hot-reloaded, and starts a session on settings.json's "model". magpie now writes its models there as WorkBuddy's are written (vendor magpie, key magpie-codebuddy, url the gateway's /v1/chat/completions), keeping the user's own entries, their order, a model turned off and availableModels (magpie's ids added when the list is there), and sets "model" to the pick, the user's other settings kept; one of CodeBuddy's own models, or default, takes magpie's models out again. A new models.json is {"models":[…]} as CodeBuddy's docs give it. workbuddyWrite becomes buddyWrite(agent), the same writer for both. CODEBUDDY_CONFIG_DIR joins agentenv.Vars; cliupdate knows the npm package. TestCodeBuddyCode (user's model, availableModels and settings kept; magpie pick; sync; back to own; default) and TestCodeBuddyCodeNewFile fail without the agent ("no codebuddy agent") and pass; -race -count=5 on the CodeBuddy/WorkBuddy tests. End to end, the real CLI 2.161.4 under a temp HOME with CODEBUDDY_INTERNET_ENVIRONMENT=internal and no CodeBuddy sign-in: magpie codebuddy magpie/mock/pro against a sandbox gateway on 3462, then codebuddy -p with no --model, answered through the gateway from a mock upstream, and magpie's usage counted it under CodeBuddy Code (the CLI sends "CLI/2.161.4 CodeBuddy/2.161.4", so it is told by its key); magpie codebuddy default took it out. A bare-list models.json is read by the CLI too. Not tried with a real CodeBuddy account or a real model vendor. go test ./internal/agent/ ./internal/agentenv/ ./internal/usage/ ., go vet, darwin, linux (nogui) and windows builds pass.
|
||
|
|
2f15623e35 | feat: add VSCodium to vscode kinds (#926) | ||
|
|
1330b9660e |
agent: VS Code Insiders is an agent of its own, its Chat given magpie's models in Code - Insiders/User as VS Code's is in Code/User (wani on Discord)
vscode.go's agent is now one per build (vscodeKind): VS Code as it was, and VS Code Insiders, id vscode-insiders, its User folder Code - Insiders beside Code on the Mac, Windows and Linux, its binary code-insiders. Insiders' chat names itself GitHubCopilotChat as Stable's does, so it has no UA of its own: its models send the token magpie-vscode-insiders, which the gateway reads first, and its requests are its own in Usage while Stable's stay Stable's. Connecting one writes nothing of the other's. TestVSCodeInsiders fails without the change (unknown agent "vscode-insiders"). go vet, go build (GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. |
||
|
|
5115f0eea6 |
feat: configure Zed-compatible Agent paths (#922)
* feat: configure Zed-compatible agent paths * fix: honor custom Zed bundle paths * fix: classify Zed path overrides correctly --------- Co-authored-by: yetone <yetoneful@gmail.com> |
||
|
|
2f135ed4d5 | plugin, gui, docs: Plugins says which plugins are providers and which are middleware, lists the community's ready-made middleware, and edits a middleware's options in its row (the owner) — the Plugins page still read as if every plugin were a subscription, and nothing showed what a middleware does or let you set its options without editing plugins.json. Every Discover card and Installed row now carries a Provider or Middleware chip (both for a plugin that is both), also for one that is off (isMiddleware/middlewareOnly from the server, read from its files); Discover splits into Subscriptions and Gateway middleware ("runs in magpie's gateway on what every agent sends and gets back, whichever provider serves it"), the latter from market entries with "kind": "middleware" and no providers or icon (the built-in market.json lists the five new @magpie-community/middleware-* packages: param-override, model-map, system-prompt, word-guard, think-tags, mostly New API's channel settings with the same JSON). A middleware row has Options: a JSON editor in the row, starting from the package's magpie.options example when none are set, which validates it is an object, saves (POST /api/plugins/options) for the next request, clears, and cancels with Esc. magpie plugin options takes a short name (model-map for @magpie-community/middleware-model-map; plugin.ShortName, also in SetOptions). en/zh/ja/de. Docs: usemagpie.ai/docs/plugins (en/zh/ja) gains Ready-made middleware and Options and the registry's kind; reference.md, both READMEs and docs/subsystems/gateway-middleware.md likewise. Tested with a real DeepSeek account and real Claude Code through magpie web --gateway in a sandbox HOME, with all five installed and options set by short name: a mapped model named back, max_tokens capped, a prompt appended for claude only, word-guard's 400 and param-override's 403 in the agent's shape, <think> stripped from Chat and Responses streams and a whole Anthropic reply, reasoning_content moved into the text; /api/plugins showed the kinds and the example. TestSetOptionsShortName is new; TestBuiltinMarket checks a middleware entry has no providers or icon and no other kind. plugin-middleware.test.cjs (Chromium and WebKit, en and zh: the chips, Discover's two sections, the options editor with its errors, no select, no scroll, no left border) fails without the change; the other plugin tests and click-scroll pass in WebKit (plugin-market's section name updated; plugin-move fails only under parallel load and passes alone). go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. | ||
|
|
9fb51c7742 |
docs: a new README, in English and Chinese, that shows what magpie does
The old README was the whole manual in one file. Someone who opened the repo saw a wall of config paths before seeing the app. The new one leads with the app: a hero screenshot, six highlights, then a section for each part (picking models, providers, routing groups and intent routing, subscriptions and plugins, usage, sharing, the library), with the site's light and dark screenshots in <picture> tags. A supported-agents list, a three-step quick start and a CLI sample follow. README.zh-CN.md is the same page in Chinese, with the Chinese screenshots where the site has them. The full manual moves, word for word, to docs/reference.md, with its two relative links fixed. Both READMEs link to it, so no detail is lost. |
||
|
|
7bfe3137bd |
docker: the image has bash and busybox, so a terminal in the container opens (スライム on Discord: 这2天的docker版更新后无法进入terminal模式,导致配置不了devin … exec: "/bin/bash": stat /bin/bash: no such file or directory)
The image is distroless, which has no shell at all, so a NAS panel's terminal (Synology, 1Panel, Portainer open /bin/bash or /bin/sh) and `docker exec -it magpie bash` failed, and a sign-in that needs a terminal (magpie accounts add devin) couldn't be made there. The image now copies in Debian 12's bash with the libtinfo it links (distroless cc is Debian 12, so its glibc runs it) and busybox's commands, and puts magpie on PATH (/usr/local/bin/magpie). The image still runs as nonroot and starts /magpie serve as before; it grows from about 37 MB of extras to 66.6 MB. Built locally (docker 29.4, arm64): bash 5.2.15 and sh run as uid 65532, `magpie version` and `magpie healthcheck` work from bash, the gateway answers 200 on /v1/models, /config keeps its folders. The Docker workflow's smoke test now runs bash and sh in the container. README says so. |
||
|
|
0e7a1f6b6a | cli: magpie <agent> default takes magpie out and puts back what the agent had before, as the Agents page's Disconnect does (__jingling on X: magpie claude default took the model they had set away with magpie's) — the agent-wide default set its first field to the agent's own default, which for Claude Code is Claude Code as installed: settings.json's model was deleted with magpie's endpoint and tiers, so a user on opus before magpie was left on no model at all. On an agent magpie is wired into it now runs Disconnect (Unwire, then each field magpie set back to its default), which puts back the model, endpoint and token stashed when magpie was wired in; Codex goes back to its own model the same way. magpie <agent> <field> default is as it was, the field to the agent's own default. The README and usemagpie.ai said under 15 MB for the desktop app and 7 MB for the terminal build; v0.1.869's are 27–30 MB (CLI) and 31–35 MB (desktop on Linux and Windows), a 13–16 MB download on a Mac, and say so now (en, zh, ja). In a sandbox HOME: settings.json with // mine, model opus and theme dark, magpie claude deepseek/deepseek-chat, then magpie claude default: model opus back, the env cleared, the comment and theme kept (on origin/main: no model). TestAgentDefaultPutsBackTheModelBefore fails on the old code. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. | ||
|
|
28a830fc5c |
aside: magpie picks Aside's model, its thinking level, its four task models and its picture model (#789)
* aside: magpie picks Aside's model, its thinking level and its four task models (X: junjie — Aside Settings → Models has a Default model row magpie could not touch, and Fast/Standard/Deep/Visual rows it could not reach at all) Aside reads the same provider list Pi does, so magpie hands it the whole catalogue in a provider block of its own, carrying the magpie-aside token so its requests are told apart in usage. What differs is the default: Aside keeps it as one object in ~/.aside/u/0/settings.json naming the provider, the model, the thinking level and fast mode together, so the model field reads and writes two keys where Pi's are two strings, and both go through Aside's own settings API — the running daemon holds its default and writing the file behind it leaves the daemon on what it already had, read back the same way with the old model still named after magpie had written the new one. The API is reached through the repl, the settings command having no set of its own. The four task roles are the same shape as the default and are wired the same way, each a Quiet field following the model, so a role the user has not set is not a line of its own and follows the default. A role is written whole: thinkingLevel is required by Aside's schema and a role without one is refused, so a role keeps the level and fast mode it had, a new one takes the default's, and where neither is set it takes the level Aside gives its own roles. Taking magpie out puts every role back, the user's own model where there was one and nothing at all where magpie was the only thing that set it, and a role the user moved onto a model of their own is left alone. Each change checks the value that came back: Aside answers a set with the settings as they now are and drops what it will not take without a word — a role naming a model it has not resolved yet is the one that happens — so without the check a change that went nowhere read as one that landed. Caught, it is written to the file, which is what Aside reads at its next start, and the Agents page says so. The account magpie wires is 0: the folder is ~/.aside/u/0 while the CLI names it u0, and TestMain stands in for the binary so a test can never change the real Aside's default. A models.json carrying a magpie provider of someone else's is refused rather than written over, and one that will not parse is the user's to fix. With the owner's real Aside under a sandbox HOME: a pick reached the gateway and was attributed to a client named Aside, the default put the user's own minimax model back and took the magpie provider out, and a role set through magpie was read back by the running daemon the moment it had a magpie model to resolve. Not tried: a change written to the file taking hold at Aside's next start, which needs a restart. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... pass (TestBunThroughBridge fails, reproduced on a pristine worktree of the parent; internal/plugin passes alone and times out only under the full run's load). * aside: the row appears for an Aside that is installed and not yet used (the CI preview of #789 could not find .row.agent[data-id="aside"]) Detected() took ~/.aside/u/0, the account folder, and that only exists once someone has signed in. The binary was the other look and the CLI installer links it into ~/.local/bin, which a magpie launched from Finder has no PATH entry for, so an Aside that was installed and never used showed no row at all — the preview installed one, drove the UI and could not find it in either the installed list or the row selector. The installer writes ~/.aside/cli and the daemon writes the rest, so ~/.aside is the folder that is there from the install, and it is the one Detected() should ask about. Path stays the account's settings.json, which is the file magpie edits and the only one worth reading a model out of. TestAsideIsDetectedBeforeAnyAccount fails on the old Dir: it takes PATH away so the binary cannot be found either, makes the installer's folder and no account, and expects the row. * aside: a wedged repl gives up, and the model comes off through Aside (yetone on #789) Three things about the way out of an agent and into one, all found on a real Aside. The repl had no timeout. proc.Command(...).Output() waits as long as the command does, so a daemon that has wedged or one waiting on input leaves a pick in the Agents page or the CLI hanging on it, and switching off asks up to five times over — the default and four roles. proc.CommandContext with ten seconds: Aside answers in milliseconds, and a run that does not is written to the file instead, which is what Aside reads at its next start, with the notice that says so. TestAsideGivesUpOnAReplThatHangs runs the real command against an aside that never returns: it passes in the timeout, and without CommandContext it hangs until the test's own timeout kills it. Switching off with no model of the user's behind magpie's wrote settings.json directly. By then the provider block was gone, so the running daemon went on using magpie/… with no way to reach it, and asideStale was never set, so nothing said the change needed a restart. It goes through asideApply like every other change, with the value that came back checked. The provider block now goes last, after the default and the four roles are put back. A step that fails on the way out otherwise leaves the settings naming a provider that is no longer there. TestAsideDefaultTakesTheProviderOutLast watches the block from inside the settings step and fails on the old order. The icon is Aside's own brand-embossed-light.svg with the embossing's transparency taken off, so the mask is solid and takes the row's text colour like every other agent. As it was, the white fills at 8–20% opacity the dark and light brand files are built with left the mark almost invisible on a light row, and it vanished entirely in dark mode. Same paths, same shape, now solid; checked in the app next to Pi's. Tested against Aside 1.0.928.1 (daemon 1.26.928, CLI 1.26.916.1741) on darwin/arm64. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./internal/agent pass (408). * aside: the model Aside draws with is magpie's to set too (X: Aside Settings → Images had no row for it) Aside keeps a fifth model setting beside the default and the four task roles: imageGenerationModel, the provider and the model it draws pictures with and nothing else — no thinking level, no fast mode, since neither means anything for a picture — and no key at all on an account that has never picked one. It is a magpie field like the roles: Quiet, so it is not a line of its own on an account that has none, and taking the model after while it is unset. A model of magpie's needs the provider block to reach it through, and what it named before is remembered the same way a role's is, so `magpie aside model default` puts the user's own picture model back and takes a picture model of magpie's off rather than leaving it naming a provider that is no longer there. Not wired: customModels, which is the user's own model definitions with their own credentials, and asideModelCatalog, a list of model ids to pin or hide. Neither is a model magpie picked, so neither is a setting of magpie's to own. Aside 1.0.928.1, daemon 1.26.928. go vet, the agent/gui/profile tests, the three CLI cross-builds and the CLI verification under a temp HOME all pass. |
||
|
|
8031edc78b |
agent: AtomCode support — one provider account, one model table per catalog model, as its own sign-in writes (#736)
* agent: AtomCode is wired like its own CodingPlan sign-in — one provider account, one model table per catalog model
AtomCode (atomgit.com/atomgit_atomcode/atomcode, AtomGit's open Rust terminal agent) keeps its settings in ~/.atomcode/config.toml ($ATOMCODE_HOME). Its account schema, what its own CodingPlan sign-in writes, is one [provider_accounts."magpie"] table at the gateway's /v1 (protocol openai, gateway.TokenFor("atomcode") as the key, so a request is its agent's even when the UA is overridden) and one [models."magpie/<provider>/<model>"] table per catalog model: the model string goes to the gateway as it is to resolve, with the model's context window (AtomCode compacts as it nears it; a 128000 one is told when the model's isn't known, and an output above the told window is cut to it), its output limit, vision and reasoning_effort_levels; reasoning_effort rides on the table once the user picks one. Picking a model points default_provider and default_model at it — default_model wins when both are set, as atomcode reads it — and stashes the defaults the user had; stepping out puts them back. The picker offers the user's own model tables and the catalog via magpie; the Library gives AtomCode its ATOMCODE.md, its mcp.json (omp's shape) and skills/; its UA is atomcode/<version>.
Found against a real atomcode 5.2.1: the documented legacy [providers.*] tables work, but its own sign-in writes the account schema, so magpie writes that; reasoning_effort, max_tokens and supports_vision were verified to reach the request from the model table, and default_model to win over default_provider. Verified end to end on a real install: atomcode --provider "magpie/…" answered through the gateway, and the Agents page shows the tile, its picker and the Usage attribution. TestAtomcodeWiring, TestAtomcodeRestoresOwnDefaults and TestAtomcodeSync fail on the old code; TestDriftUnwiredEveryAgent and TestUnsetAgentsReadEmpty cover it from All(). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./internal/agent ./internal/library ./internal/agentenv pass; the plugin-host and timing failures of the full ./... run fail on main without this change too.
* tweak
* agent: AtomCode's config is matched by its own spellings, and Sync keeps the effort — AtomCode rewrites its config through serde on every /model and /cd, and that writes the account header bare ([provider_accounts.magpie]) where magpie wrote it quoted, so after the first rewrite Check read the wiring as gone, Sync and every pick appended a second account table (edited TOML is invalid: table magpie already exists) and clearing left the unquoted table and its api_key behind. The account is now matched by exact name in its three spellings (quoted, bare, literal — magpie-2 stays out) and the model tables by their decoded keys (kimiModelKey, replacing atomcodeModelName), through a new SetTOMLTablesMatching that takes exact names beside prefixes; SetTOMLTables delegates with none, unchanged. Sync carries each magpie model table's reasoning_effort over when rewriting — it ran at every gateway start and catalog change and dropped both an effort picked in magpie and one picked with AtomCode's /effort, and picking another model cleared every table's. The effort Set validates against the levels AtomCode knows (low, medium, high, xhigh, max — its clamp_effort_to_levels drops none, minimal and ultra), a model without levels offers none now, and the low/medium/high fallback is gone with it. supports_vision is written explicitly false, where AtomCode otherwise guesses from the model name. The user's legacy [providers.*] entries join the picker, a stashed default_model naming one comes back on Disconnect, and picking one sets default_provider and takes default_model out, as the legacy shape reads. site docs (en, zh, ja) list AtomCode. TestAtomcodeSync rewrites the config into the bare and single-quoted spellings before Sync and again before clearing and fails on the old code (drift reported, effort dropped, unquoted table left behind); TestAtomcodeLegacyProvidersArePickableAndRestored and TestAtomcodeEffortLevelsMatchAgentSupport are new. Reported by yetone on #736. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./internal/agent ./internal/edit pass.
* agent: TestAtomcodeWiring picks an effort through the field too — the write side of the validation the last commit added (a level AtomCode knows lands as reasoning_effort on the model table, one it does not know is refused) had no coverage left: the test only read back what AtomCode's own /effort had written. provider.SetModelEfforts gives fake/m1 levels to offer.
* agent: Disconnect puts AtomCode's default_provider back only where AtomCode still has it — the restore checked the stash's value was not magpie's but not that the table it names was still there, so a table the user had deleted in between came back as a dangling default (the account schema's default_provider names a model table, the legacy shape a [providers.*] one; own answers for both). The default_model branch checked already. TestAtomcodeRestoresOwnDefaults deletes the stashed provider's model table before clearing and fails on the old code.
* agent: AtomCode joins the CLI updates — npm @atomgit.com/atomcode, the brew cask atomcode, and the curl script's ~/.local/bin/atomcode updating itself with atomcode upgrade, so the Agents page shows its version and a newer one as the other agents' do (#736). TestHowInstalled covers the npm prefix, the cask and the self-updating installer.
* agent: AtomCode's self-updater waits for its install paths to be confirmed — the branch recognized a binary under ~/.local/bin as `atomcode upgrade`, on a docs page, while the README shows another path and the command has not been driven outside the help text; the reviewer asked for it removed until the standalone command and the script's real default are settled. npm and the brew cask stay, with their tests; a comment marks what is pending.
---------
Co-authored-by: googya <118465+googya@users.noreply.github.com>
|
||
|
|
5a0559d37f | agent: JetBrains Air reaches magpie's models through a Magpie ACP agent (OnurBen on Discord: Air's model picker should list magpie and its routing groups, as AI Assistant's OpenAI-compatible provider does) — Air's own agents (Claude Agent, Codex, Gemini CLI, Junie) run on the accounts under Settings | Account | AI Providers, a sign-in or an API key with no base URL to point at magpie (AIR-4541 asks for one); what Air does take is any Agent Client Protocol agent listed in acp.json in its config folder (~/Library/Application Support/JetBrains/Air, $XDG_CONFIG_HOME/JetBrains/Air, %APPDATA%\JetBrains\Air) under agent_servers, and the models an agent reports at session/new are its model menu. The new air agent's model field adds one there, Magpie: OpenCode's ACP server (opencode acp, by its full path when found on PATH or in ~/.opencode/bin, as Air may not have the shell's PATH) with OPENCODE_CONFIG naming magpie-opencode.json beside acp.json, which holds magpie's provider, the model picked as the one a task starts on (and small_model) and enabled_providers [magpie], so the menu lists magpie's models and routing groups only; the user's own acp.json agents are kept, and disconnecting takes out that entry and that file. Sync keeps the provider's models current, Check reports the entry taken out by hand (set again puts it back) or the wiring changed, the notice says to pick Magpie in a new Air task, or to install OpenCode first when there is none; a model that isn't magpie's is refused, as Air's own agents are picked in Air. Icon: Air's wordmark from jetbrains.com; zh strings for the notices; README row. Checked with OpenCode 2.0.20's opencode acp under a sandbox HOME, as Air would start it: session/new's model option lists magpie/group/gpt-6-luna and magpie/openai/gpt-6.1-sol only, current the configured one (OPENCODE_CONFIG, OPENCODE_CONFIG_DIR, OPENCODE_CONFIG_CONTENT and the global config all do). Known gap, OpenCode's: with a provider's key set in the environment it starts with (OPENAI_API_KEY, ANTHROPIC_API_KEY, DEEPSEEK_API_KEY, even a dummy one), 2.0.20's ACP server lists that provider and none of its config's providers, model or enabled_providers, from any config file, project one included; an empty key or a credential in its auth.json doesn't. Not tried in Air itself. TestAirAddsMagpieAgent, TestAirWithoutOpenCode, TestAirCheckAndSync; go vet, builds (darwin nogui, GOOS=windows, GOOS=linux nogui) and go test -tags nogui -count=1 ./internal/agent/ ./internal/gui/ under a temp HOME pass. | ||
|
|
a370877ca4 |
agents: VS Code's built-in chat (Copilot Chat) — switched on, magpie's models join Chat's model picker as a Custom Endpoint group in chatLanguageModels.json, with no key to enter and no Copilot sign-in needed (wani on Discord: 可以添加vscode自带的agent的配置吗)
VS Code Stable takes other vendors' models through its Custom Endpoint provider since 1.122 (customoai, the older OpenAI Compatible one, is Insiders only), signed in to GitHub or not. The provider groups live in chatLanguageModels.json beside settings.json in the default profile's User folder (~/Library/Application Support/Code/User, ~/.config/Code/User, %APPDATA%\Code\User), a JSONC array VS Code watches. magpie adds {"vendor":"customendpoint","name":"magpie","apiType":"chat-completions","models":[...]} with each catalog model at the gateway's /v1/chat/completions, toolCalling true (agent mode lists only tool-calling models), vision, contextWindow and maxOutputTokens (capped at a quarter of the window, as VS Code keeps the output budget out of the prompt's), and the model's efforts as supportsReasoningEffort. A group's apiKey is a secret property that VS Code reads from its secret storage only (languageModels.ts _resolveConfiguration), so it can't be written to the file; each model instead sends Authorization: Bearer magpie in requestHeaders, which CustomEndpointOAIEndpoint lets through and which stops it sending its own key header. The model a new chat starts on is chat.defaultModel in settings.json (matched by model id, so the catalog id); its old value is stashed and put back. The group is the agent's Joined: picking one of VS Code's own (auto) keeps magpie's models listed, and switching off (Unwire) takes the group out and puts chat.defaultModel back. Sync rewrites the group as the catalog changes, Check says when its url or header no longer reach the gateway, the Notice says where the models are and to reload the window if they don't show, and requests are credited to it by GitHubCopilotChat/<version>, a User-Agent a model's headers can't change.
internal/edit: SetJSONItem/GetJSONItem/DelJSONItem edit one element of a JSONC file's top-level array, found by its members, keeping comments, trailing commas and the other elements; SetJSON/DelJSON take a backslash-escaped dot as part of a key, as gjson does, for VS Code's flat "chat.defaultModel".
TestVSCode (connect keeps the user's settings and groups with their comments, the group's url/header/toolCalling/window and no apiKey or discovery url, Sync, Check, an own model keeping the group, disconnect back to the user's last own model), TestVSCodeRoundTrip (on and off leaves both files byte for byte; no files at all) and TestVSCodeUA; TestJSONItem, TestDelJSONItemAmongOthers and TestJSONEscapedDot. go vet ./internal/..., go build -tags nogui ./..., GOOS=windows and GOOS=linux builds pass; go test of internal/agent, edit, usage and gateway (each sandboxes HOME in TestMain) and internal/gui's page and agent connect tests pass; the full go test under a temp HOME was not run (the sandbox refused the HOME override). No GUI code changed (the generic connected wording fits), so no webkit test. VS Code itself is not installed here: not tried against a real VS Code.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5fb7799b11 |
agents: Mister Morph (morph) — picking a magpie model points ~/.morph/config.yaml's llm at the gateway ($MISTER_MORPH_CONFIG names another file) (#728)
Morph has no list of providers to add magpie to, so magpie takes llm over: inference_provider openai_response_compatible (the Responses API, morph's own default), endpoint the gateway's /v1 (morph adds no second /v1), api_key magpie's token, model the catalog id, context_window_tokens the window magpie knows (morph knows one only from its own list), and headers.User-Agent mistermorph so the gateway credits its requests to morph. llm.provider is left alone: morph derives it from inference_provider. What the user had under those keys is stashed and put back when they pick a model of their own or reset; comments, other headers and other keys stay. An effort field sets llm.reasoning_effort. Found by ~/.morph, not by the morph command (a common name). Also on WSL. In NO_PICKER, as morph's /models switches profiles, not models. TestMorph and TestMorphContextWindow. go vet, builds (GOOS=linux/darwin -tags nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass, but for TestPluginProxy, TestPluginSOCKSProxy and TestCodexArchivedSameSizeRewriteInvalidatesDiscovery, which fail on main too. With the installed morph against a sandbox gateway and a mock vendor, morph run's requests went to /v1/responses, were relayed as chat completions and were credited to morph; magpie morph default restored the user's file. Not run against a real model. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9db0d155bc |
cli: magpie quota wait <provider|account> blocks until that subscription (any of its accounts) or that account has allowance again, then exits 0 (#720, thedavidweng: a long Codex task stopped when every account ran out, and nobody came back to it hours later; step 1 of the maintainer's plan, for until codex exec …; do magpie quota wait codex || break; done) — it reads the vendors itself as magpie quota does, gateway running or not (provider.Allotted: the subscriptions' and plans' readings, no key balances; AskUsage has each look wait for a new reading, and only a wait for Claude runs Claude Code's /usage, through AskClaudeUsage, which now calls it). An account has allowance when routing's usedUp says so (no window that stops it, every model, at 100%; exported as provider.UsedUp), and BackAt is when the last of those windows starts again. While all are used up it sleeps to the soonest reset plus 30s, at least a minute and at most ten, saying on stderr what it waits for and until when; an allowance it can't read (an error, or only the last reading kept in its place) is asked again after 1, 2, 4, 8 then every 10 minutes. A provider's accounts switched off or signed out in magpie are left out, as the agent is never moved to them; an account is named by its user, or <provider>/<user> when two subscriptions share it. --timeout <duration> exits 1 when it passes first, --quiet says nothing, an unknown name or flag exits 2 (main now honours an exitError's code) and Ctrl+C 130. The loop takes its reading, clock and sleep as functions: quota_wait_test.go runs it on a fake clock (already available → no sleep; used up → 10m, 10m, then to just past the reset; one account named ignores another's room; a reset under a minute away is a minute; --timeout ends at the deadline after its last look; unreadable usage backs off and the wait goes on once read; an untold reset is looked at every 10 minutes; an off account is passed over; Ctrl+C), plus resolving names and the usage errors. Help in magpie quota's and magpie's usage, and the README. go vet, builds (darwin, GOOS=linux, GOOS=windows), the new tests and ./internal/provider pass; not run against real accounts.
|
||
|
|
c8278a8691 | docs: plugins are OpenCode's provider plugins or pi's packages — the Plugins page's intro, magpie plugin's help, the README and the plugin docs (en, zh, ja) said OpenCode only, though pi packages that register a provider run since v0.1.772; the docs gain a pi packages section (what makes a package pi's, what is loaded and left alone, sign-in through onPrompt/onAuth and ctx.ui, pi's files under <config>/pi or PI_CODING_AGENT_DIR, Anthropic Messages requests), the pi-package search and a FAQ entry | ||
|
|
22b1ed5b7a |
docs: document headless NAS deployment (#710)
* docs: document headless NAS deployment * docs: keep NAS gateway private during setup |
||
|
|
b9a38a83ba | tui: c on the Usage page presses WorkBuddy's daily check-in (签到) for every WorkBuddy (China) account signed in here at once, and the WorkBuddy lines say how today's went (akic404 on Discord: the TUI had no way to check in) — the app had its Usage card's Check in now and the CLI magpie accounts checkin, the TUI nothing. Now c on the Usage page runs provider.CheckInWorkBuddy, as the CLI does, for all the accounts on this computer at once (the built-in's, or the plugin's when moved or under workbuddy-plugin), and says each in the status line: "旅行者 checked in today +100 · a 4-day streak", "· already", "isn't eligible", "no check-in event now", or in red "couldn't check in: <why>"; with none signed in it says so. The footer lists c, and each WorkBuddy line on the page carries its check-in (签到 ✓ +100 · 4-day streak, 签到 not yet today · c, 签到 failed · c tries again), read from workbuddy-checkin.json via WithCheckins (provider.CheckinDay names the Beijing day) and redrawn after c without asking the vendors again. Local only: a remote magpie's accounts are checked in on that magpie, from its own app, TUI or CLI; no gateway endpoint is added. WorkBuddy AI has no check-in event, so only WorkBuddy (China) is checked in, as before. README says so. TestTUIChecksWorkBuddyIn (none signed in, claimed, already + refused, ineligible + no event, with a stand-in check-in) and TestCheckinOnTheUsageLine pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried against a real WorkBuddy account, nor in a real terminal. | ||
|
|
2719dd6ac3 |
tui, cli: adding a provider says how many models its list had, or why it had none, and m (magpie provider refresh <id>) fetches the list again (akic404 on Discord: 还是tui cli问题,没有获取上游模型功能,新建之后模型数是0)
The TUI's add fetched the new provider's list and dropped any error, so a remote magpie that couldn't be listed (wrong address, the other magpie not shared, a gateway key it refuses) showed just "added Remote magpie" and 0 models, and the Providers page had no way to ask again: t tests the APIs, the models picker said "t tests it, which asks the vendor for them", which it doesn't. Now the add says "added X · N models from its list · M for agents (↵ picks)", or "added X · no model list · <the first URL's answer> · m asks again" in red; m on the Providers page is the app editor's Refresh (Refetch: picks the new list no longer has are dropped and named), with the same note; the footer lists it and the empty picker points at it. The CLI already said a failed list on add and fetched with provider models <id>; provider refresh <id> (and fetch) is that by its GUI name, and models <id> now goes through Refetch too, naming dropped picks. The ids were never names: a remote magpie's are <its id>/<the other magpie's provider id>/<model> (remote-magpie/custom/qwen3.6-max); the Chinese is the label, "qwen3.6-max · 中转站", the other magpie's provider name it sends as magpie_label, which magpie models and the TUI's picker show beside the id. README says so. TestTUIFetchesAProvidersModels (add over a fake magpie: 2 models; its list grows, m: 3; it refuses, m: the error and the 3 kept) and TestProviderRefreshCmd fail on the old code; TestTUIAddsARemoteMagpieWithItsAddress now expects the error at a dead address. End to end with two sandbox magpies (temp HOMEs, 3461/3462, a fake upstream on 3463): A with "中转站" over the upstream (id custom), B's TUI in tmux adding A at 127.0.0.1:3461: "added Remote magpie · 2 models from its list · 2 for agents", row 2 models, m again the same; a second at 127.0.0.1:3464: "added Remote magpie 2 · no model list · … connection refused · m asks again", 0 models; provider refresh remote-magpie lists remote-magpie/custom/qwen3.6-max "qwen3.6-max · 中转站". go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. |
||
|
|
bc141ad834 | update: install.sh and magpie update take --proxy and --mirror, and magpie update mirror keeps one for the app's updates too (akic404 on Discord: 安装和update如果没有梯子也是麻烦事,能否在install.sh和magpie update后加子选项?--proxy等可自定义或默认ghproxy) — install.sh had no options and fetched with curl alone, and magpie update took none; without a way out to GitHub both failed. Now both take --proxy <url> (http, https, socks5, socks5h; install.sh also MAGPIE_PROXY=) for the feed and the download, and --mirror <prefix> (or MAGPIE_MIRROR=), a GitHub download mirror the release file is fetched from as <prefix>https://github.com/…; only github.com / githubusercontent.com URLs are put through it. No mirror is used unless one is given: magpie names none and has none by default. The feed with each file's SHA-256 is always fetched from usemagpie.ai, never through the mirror, so a file the mirror changed is refused and deleted ("… from the mirror X does not match its checksum from https://usemagpie.ai; not installed"); macOS still checks the app's signing team. magpie update mirror <prefix>|off saves Settings' updateMirror (http(s) only), which the app's own background update follows too; magpie update --mirror off skips it once; the Settings page's save keeps it. The app's update already followed Settings → Proxy (#294); magpie update --proxy overrides it for one run. The environment's ALL_PROXY / all_proxy is now honoured when HTTPS_PROXY / HTTP_PROXY are unset (Go's ProxyFromEnvironment ignores it), with NO_PROXY as curl reads it. magpie update takes --flag value or --flag=value, and an unknown word is an error instead of an update. The help, README and the docs' FAQ (en/zh/ja) say how. install.test.mjs's proxy, mirror and tampered-mirror tests, TestAllProxyFromTheEnvironment, TestUpdateThroughAMirror, TestUpdateThroughAProxyGiven and TestUpdateFlags fail on the old code and pass; TestSettingsSaveKeepsWhatItDoesNotSend covers updateMirror. A dry run of install.sh as Linux amd64 into a temp HOME installed the real 0.1.773 with its SHA-256 checked, refused a local mirror serving a wrong file, and with --proxy to a closed port failed reaching the site through it. go vet, builds (darwin, GOOS=linux, GOOS=windows), sh -n, node --test site/install.test.mjs and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeIdentityServedWhileAsked, a timing test that passes alone. Not tried through a real proxy server or a real public mirror; no install.ps1 exists for Windows, and the app has no field for the mirror. | ||
|
|
38a0c1f053 | tui: a Remote magpie is added with its address, and w changes it (akic404 on Discord: tui里的remote magpie…gui里会填写url,但是tui没有填写位置,我只能退化为用指令添加) — the Providers page's a picked a vendor and asked only its key, so a preset reached at the user's own address (Remote magpie, Azure OpenAI) was saved with no URL and refused ("a provider needs a base URL"); only magpie provider add remote-magpie … url=… or the app's editor could add one. Now picking such a preset asks its address first, as the app's editor does (the preset's example, http://192.168.1.20:3425, as the placeholder, its hint under it, nothing typed answered with "The other magpie's address is needed"), then the key; the address goes on Chat and Responses as the editor puts it, and a remote magpie's every API is put on it when saved (host:port, …/v1 or the Anthropic root all work). w on the Providers page changes the address of such a provider, with the one it has in the line; other providers say theirs is the vendor's. The footer names w. README says the TUI adds one too. TestTUIAddsARemoteMagpieWithItsAddress fails on the old code (the key asked first) and passes. Remote magpie has no default target: the address is the other computer's magpie as its Settings → Share on local network shows it; the example is only a placeholder. | ||
|
|
d5591b8f8d | usage: export effective model costs to Langfuse (#684) | ||
|
|
48d524d65a | feat: select a day in usage charts (#683) | ||
|
|
860ed6618d | plugins: a 「国内镜像」 switch on the Plugins page downloads the list, plugins and Bun from mirrors in China first (Discord, akic404: GitHub/npm are slow from the mainland; asked for ghproxy) — Plugins › Mirrors in China, off by default, kept in settings (chinaMirror). On, source asks a copy of the very file before its official address: npm's registry (packages, versions, search) at registry.npmmirror.com, Bun's release zips at registry.npmmirror.com/-/binary/bun, and the community registry.json at cdn.jsdelivr.net/gh/magpie-community/plugins@<branch>; bun add gets BUN_CONFIG_REGISTRY=npmmirror unless the environment names a registry. No proxy: gh-proxy (MAGPIE_MIRRORS' GitHub fallback) handles nothing the switch sends, since a proxy could hand back other code. A mirror asked first that can't answer (404/403/5xx, e.g. a version it hasn't synced) falls through to the official address; npm's weekly downloads stay api.npmjs.org's (npmmirror counts its own). Checked by hand: npmmirror's bun-v1.4.2 darwin-aarch64 zip has the SHA-256 pinned in bun.go, jsDelivr's registry.json is byte-identical to raw.githubusercontent's, and bun 1.4.2 add through npmmirror locks the same sha512 integrity npmjs gives. TestChinaFirst, TestChinaComesFirst, TestChinaMirrorAnswers, TestChinaMirrorsMissIsAskedOfficially, TestDoFaithful, TestBunInstallsFromChinaMirror and TestPluginsMirrorSwitch cover it. A live run with the switch on fetched the list from jsDelivr, npm info from npmmirror and Bun from npmmirror (checksum passed); a sandbox magpie web showed the switch in the Plugins head, saved it, kept it after a reload, and a click didn't move the page. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried from inside mainland China. | ||
|
|
69368007b0 |
termux: support Android builds and installation (#644)
* termux: build natively and run plugins on Bun 1.4.2 Select nogui for Android builds, open browser and OAuth links with termux-open-url, and write an executable Termux:Boot script that starts the gateway with shell-quoted paths. Bun 1.3 has no official Android build. Upgrade the default plugin runtime to 1.4.2, add the official Android arm64 and amd64 download targets, and update SHA-256 checksums for every supported platform. Document Termux installation, startup and source updates. Validation on Termux arm64: - Native build and go vet pass. - A fresh config downloads and verifies Android Bun 1.4.2 and loads a test plugin. - Boot startup can be enabled and disabled. - The Web page, authenticated state API, unauthenticated API rejection, gateway healthcheck and Web quit action work. - Browser argument preservation, boot path quoting and permissions, and plugin tests pass. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a10014-56c1-73d3-b3ee-2ea569713989 * site: install Android builds in Termux Detect Termux through TERMUX_VERSION or its PREFIX and select the Android CLI asset instead of a Linux binary. Install into PREFIX/bin by default, keep MAGPIE_BIN_DIR overrides, and suggest the Web or terminal interface without Linux desktop setup instructions. When the release has no Android asset, explain how to build from source. Add android/arm64 to release-cli and stop the build loop on failure. Document the installer and its release-asset requirement. Shell syntax and all 11 site tests pass, covering platform selection, custom paths, missing assets, checksum rejection and existing Linux installation paths. The Android arm64 release build succeeds on Termux; with a simulated release feed, the installer verifies and installs that binary into PREFIX/bin and the installed command runs successfully. Amp-Thread-ID: https://ampcode.com/threads/T-01a10014-56c1-73d3-b3ee-2ea569713989 Co-authored-by: Amp <amp@ampcode.com> * ci: check Android releases and the installer Build both linux/amd64 and android/arm64 through make release-cli in the CLI test matrix. Run the site and installer tests in the Linux job with Node 22, and check the installer's shell syntax. The release workflow in yetone/magpie-releases already calls the source Makefile's release-cli target, uploads dist/magpie-cli-*, and generates checksums for every artifact. The Android target added to TARGETS is therefore included without changing that workflow. Replace the full Termux README section with a short platform note beside the Linux and Windows installation requirements, removing duplicate commands, the fork URL and temporary release availability details. Both CLI release builds, all 11 site tests, shell syntax checks and actionlint validation pass locally. Amp-Thread-ID: https://ampcode.com/threads/T-01a10014-56c1-73d3-b3ee-2ea569713989 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
7d7175cfb1 | fix: preserve Langfuse coverage for first and auxiliary calls (#599) | ||
|
|
cb82822f3d | source: add opt-in China mirror fallback (#566) | ||
|
|
bafdd6954b | agents: Empryo (#612) — picking a magpie model in Empryo adds a "magpie" provider at the gateway to ~/.empryo/config.json's providers (baseURL + modelsAPI, no envVar) and sets defaultModel to magpie/<catalog id>; the user's defaultModel is stashed and put back on leaving, magpie's provider goes again, and a file magpie made is removed when empty. The user's own providers and keys are left alone. The gateway now answers GET /v1 (and /v1/) with its info, as Empryo lists a keyless provider only when its baseURL answers. Also on WSL. Built from Empryo's docs and its public v2 code, not run against the Empryo binary. TestEmpryo, TestBaseURLAnswers (GET /v1 was 404 before). | ||
|
|
6e6338f8d6 |
agent: configure Zed models and gateway credentials automatically (#584)
* agent: configure Zed models and gateway credentials automatically Add Zed to the CLI and Agents page with an OpenAI-compatible magpie provider, a synced model catalog and default model selection. Preserve existing JSONC settings and restore the previous provider and default model on reset. Store the gateway credential in Secret Service, macOS Keychain or Windows Credential Manager so Zed needs no manual API key entry. Cover config preservation, catalog sync, model limits, Flatpak paths and credential failures. Verified with the full nogui test suite, go vet, macOS and Windows cross compilation, and a local Zed conversation through the gateway. Amp-Thread-ID: https://ampcode.com/threads/T-01a0fd03-00d7-75fd-956f-c505e07ba60d Co-authored-by: Amp <amp@ampcode.com> * agent: store Zed credentials directly through Secret Service D-Bus Replace the Linux and FreeBSD secret-tool subprocess with the existing godbus client. Open a session, create the default collection when needed, unlock it and replace the matching Zed credential. Handle collection, unlock and item prompts, dismissal and timeout. Verify the protocol on isolated D-Bus instances with no helper executables on PATH, including immediate completion signals and failed writes. The full nogui suite, targeted vet and race checks, macOS and Windows cross compilation pass; local credential readback confirms replacement without duplication. Amp-Thread-ID: https://ampcode.com/threads/T-01a0fd03-00d7-75fd-956f-c505e07ba60d Co-authored-by: Amp <amp@ampcode.com> * docs: remove the separate Zed configuration paragraph Amp-Thread-ID: https://ampcode.com/threads/T-01a0fd03-00d7-75fd-956f-c505e07ba60d Co-authored-by: Amp <amp@ampcode.com> * agent: fix Zed reset, keychain access and translated notices Avoid restoring magpie’s own provider after a model change in Zed, and cover reselection followed by reset. Remove the unsupported Flatpak config path. Authorize the installed Zed app and security command to access the macOS gateway credential. Translate Zed’s notice and apply translation when selecting or reapplying an agent model. Verified with the full nogui test suite, go vet, Zed race tests, cross compilation and browser checks. macOS keychain behavior still needs runtime verification on a Mac. Amp-Thread-ID: https://ampcode.com/threads/T-01a0fd70-98a1-737f-93bd-77e181f904c1 Co-authored-by: XYenon <register@xyenon.bid> * agent: preserve Zed model picks when resetting magpie Restore the stashed default model only while Zed still selects magpie. Preserve a model chosen in Zed itself and discard the stale model stash, while restoring the previous provider. Find Zed Preview in Applications folders and Spotlight without requiring a CLI, and use any in the isolated D-Bus tests. Verified the reset regression fails before the fix and passes afterward. The full nogui suite, go vet, Zed race tests and macOS/Windows test cross compilation pass. Amp-Thread-ID: https://ampcode.com/threads/T-01a0fd70-98a1-737f-93bd-77e181f904c1 Co-authored-by: XYenon <register@xyenon.bid> * agent: retain Zed provider backup when reselecting magpie When Zed’s provider already points at the gateway, update only the model stash and preserve the original provider backup. Reset can then restore the user’s provider after a native model pick followed by another magpie pick. Add a regression covering the full sequence and checking the restored provider, model catalog, custom headers and latest native model. Confirm it fails before the fix and passes afterward; all Zed tests, the full nogui suite and go vet pass. Amp-Thread-ID: https://ampcode.com/threads/T-01a0fd70-98a1-737f-93bd-77e181f904c1 Co-authored-by: XYenon <register@xyenon.bid> --------- Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
38b4a9d984 | feat: export gateway and agent conversation waterfalls to Langfuse (#581) | ||
|
|
99b1865494 |
docs: add missing relative pronoun in README.md (#593)
Co-authored-by: lunar-me <lunar-me@users.noreply.github.com> |
||
|
|
0879894af3 | gateway: each gateway key can have its own limit, a token total and/or an estimated cost per day, week or month, and a spent key is refused before any provider is asked (margbug01 on #585, v0.1.671: 每个网关密钥都能设置自己的用量上限) — a gateway key named who called and the Usage page grouped its calls, but nothing held a key to an amount, so one client shared on the network could spend a subscription's whole window. A key now carries limit {period day|week|month, tokens, cost, cacheReads} in caller-keys.json, set with the new "limit-key" change. Windows are calendar ones in local time (from midnight, from Monday, from the 1st) and a call counts in the window it started in. Tokens are a call's uncached input, output and cache writes, plus cache reads when the limit says so; cost is an estimate at the Usage page's effective prices (usage.NewCoster), a call with no known price adding none and counted as unpriced. What a key used is summed from usage.jsonl (usage.Visit), so it survives a restart; the new internal/budget keeps the sums in memory per window and appendUsage writes a key's record through budget.Append, which appends and counts under one lock so a window read meanwhile never counts a call twice. Before a keyed POST reaches the mux, keyLimited reserves for it: its body's size in tokens (4 bytes a token) plus the key's mean output per call this window, at the window's cost per token or the named model's input price; it is let in while used + reserved is under the cap, so requests sent at once overshoot by about one call, and the reservation is released when the request ends, a streamed reply being settled then with the usage its vendor reported. A spent key gets a 429 in its API's error shape (Anthropic, OpenAI chat/responses, Gemini) naming the key, the cap and the reset time, with Retry-After, X-Should-Retry: false and X-Magpie-Limit-Reset, recorded as a rejected call (gateway_key_limit); other keys go on. Requests from this computer without a gateway key are not limited; a key used from this computer is. count_tokens and bridge callbacks are never held. GET /v1/magpie/limit tells a key its own status. Gateway keys rows show a Limit badge (what is used of it, per what, Spent in red) and a line under a limited key (used of limit, left, resets / refused until, in flight; unpriced calls in its title); Limit opens an editor under the row (window, tokens with k/m/万/亿, cost in US$, Count cache reads too, what is counted) staged until Save, marked unsaved, Cancel/Escape dropping it, a bad amount disabling Save, No limit removing it; en and zh; no left-border accent; no click scrolls. The CLI gains magpie gateway-key limit <id> [off | day|week|month --tokens N --cost USD --cache-reads] and a LIMIT column in list. TestKeyLimitRefusesBeforeUpstream (two calls within 700 tokens, the third a 429 the fake upstream never sees, message/type/headers, the rejected record, Anthropic's shape on /v1/messages, another key and a keyless loopback request let in, still refused after budget.Forget as after a restart, a raised and a removed limit let in, /v1/magpie/limit), TestKeyLimitHoldsParallelRequests (20 at once against 2000 tokens: some refused, at most one call over, no reservation left; without the reservation all 20 got in and 7350 were used), TestKeyLimitWindowResets, TestLimitWindow, TestLimitValid and TestGatewayKeyLimitCLI are new; gateway-key-limit.test.cjs (WebKit, en and zh) is new and passes, as do gateway-caller-keys, gateway-connect-keys, api-key-theme and click-scroll (whose request-pick case flaked once under a parallel run and passes alone, as on main). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState's known TempDir-cleanup flake aside). | ||
|
|
ff36a2994f | agents: T3 Code gets magpie as a provider of its own, every magpie model in it (KevinXC on Discord: T3 Code's Settings → Providers lists Codex, Claude, Cursor… each with "Add custom model"; magpie's models should be there without adding them one by one) — T3 Code (pingdotgg/t3code) keeps its settings in ~/.t3/userdata/settings.json ($T3CODE_HOME/userdata), sparse JSON its server watches and reloads on an outside edit, and besides its built-in providers takes provider instances of one's own: {"providerInstances":{"<id>":{"driver","displayName","enabled","environment":[{name,value,sensitive}],"config":{"customModels":[{slug,name}]}}}}. A claudeAgent instance runs Claude Code (the Agent SDK, ~/.claude settings read as Claude Code reads them) with its environment added, and sends a custom model's slug as Claude Code's model. The new t3code agent (t3code.go) adds the instance "magpie": driver claudeAgent, displayName magpie, ANTHROPIC_BASE_URL the gateway and ANTHROPIC_AUTH_TOKEN the token Claude Code is routed with (so its requests are Claude Code's to the gateway, haiku tier and all), not sensitive so T3 keeps them in the file rather than its secret store, and every magpie model shown to T3 Code as a custom model, a 1M one marked [1m] as for Claude Code (claude1MFor). The binary path and Claude home the user gave T3's own Claude (its instance, else the legacy providers.claudeAgent) are carried over. It is magpie's alone: the user's instances, their custom models and T3's other keys stay, off takes only it out, and the catalog sync rewrites it only where it is. T3's Codex lists Codex's model/list, which is magpie's catalog once Codex is routed through magpie, so it needs nothing; T3's own Claude asks for Claude's models by their full ids, which the gateway already sends to Claude Code's tiers (claudeTierStandIn). Found by ~/.t3 or, on a Mac, T3 Code (Alpha).app / T3 Code.app; T3CODE_HOME is in agentenv.Vars. Its icon is T3's mark (assets/prod/logo.svg, MIT), its letters alone so it draws as a mask; the option's note has its Chinese. Checked against T3 Code's own schema (effect 4.0.0-rc.115, packages/contracts at 54084ae1): the file magpie writes decodes as ServerSettings, magpie's config as ClaudeSettings, and encodes back to the same instance. Not run against a live T3 Code. TestT3CodeInstance (the user's instance, legacy Claude binary path and T3's key kept through on, sync and off; magpie's instance decoded strictly; found only with ~/.t3; among All()), TestT3CodeHomeAndInstance ($T3CODE_HOME, a file made, the Claude home carried from the user's claudeAgent instance) and TestStateListsT3Code are new. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass. | ||
|
|
5d7c7d2808 | quota: each subscription account, plan and key says when it last answered, and the latest is marked last (#570: quota --json / GET /v1/magpie/quotas 增加每个订阅、plan、key 最近一次应答请求的时间) — under routing Smart nobody is first for good, so a menu-bar widget had nothing to tell the one in use by: a Quota's asOf is when its reading was taken, accounts --json covers signed-in Codex and Claude accounts only, and /v1/magpie/route wants a session. Each Quota in magpie quota --json and GET /v1/magpie/quotas now has lastServedAt (RFC3339, left out when it hasn't answered) and last: true on the latest (each of a key's plan and balance, which are one key). The gateway notes each answer where servedCandidate counts it (provider.NoteServed): an account by its agent and user, a key by its provider and key id, matched to a card by its user, or by the key's name or mask, or, for a card with no user, by any of its provider's. It is kept in served.json beside providers.json, entries older than 30 days dropped, since magpie quota runs in a process of its own and computes the report itself (provider.QuotaReport) rather than asking the gateway: both read the file, so they say the same, and a restart keeps it. TestQuotasLastServedAt (gateway: a request answered by one key, its balance card has lastServedAt and last, the other key's neither) and TestQuotaJSONLastServedAt (the CLI's --json, fake CLIs and keychain on PATH) fail without the change; TestQuotasLastServed covers the matching (account by user, key by name and by mask, a card with no user, the tie, read back from the file alone). README tells the fields. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass. | ||
|
|
c2222d17bc |
otel: bodies can go whole to the trace, not cut at 256 KB (#538) (#555)
* otel: bodies can go whole to the trace, not cut at 256 KB (#538) The OTLP export sends a call's request and reply as the trace's Langfuse observation input and output, but took them from the Recent-calls capture, which keeps the first 256 KiB of each: a longer prompt or reply reached the trace cut, ending in "… (cut here by magpie)". Bodies can now go whole, behind a switch: Settings → Observability → "Include the whole bodies" (otel.bodiesWhole, MAGPIE_OTEL_BODIES_WHOLE for magpie serve, which needs Include request and response bodies on too). The gateway keeps the whole request (already in memory for the call) and spools the reply to a temporary file as it streams, read back for the export. Nothing changes for Recent calls (still 256 KiB), the request archive or usage.jsonl. The export holds queued bodies within a byte budget and splits an OTLP batch by body size, so a whole body can neither fill memory nor blow up one collector request. Secrets are masked as before. TestOTelExportsWholeBodiesWhenOn (a 300 KiB request and reply reach the span whole with the switch on, cut and marked with it off), TestOTelQueueBodiesBudget, and the settings/env and otel.test.cjs switches. * otel: release recent-call bodies and enforce queue budget |
||
|
|
5332c0c638 | usage: each subscription account's tokens and cost, by the account that answered (#557: 希望用量账本能按订阅账号统计,每个 Codex / Claude 账号各用了多少 token、多少钱) — a call's record named the upstream key it went out on (ProviderKeyID/Name, #500) but never the subscription account, so with two Codex accounts signed in, or a Claude account that took over after a failover, the ledger could say what Codex cost and not what each account did. A record now carries providerAccount, the account that answered as the Routing trace names it (Account.User: its email or login, never a token), set by the gateway at the try that went out (the candidate loop's last try, so a failover's answer is the account that took over and the refused try stays the first account's; an X-Magpie-Account pin is the pinned one), and on the decide, draw and video paths too. Record.Account() gives it, or for a record written before the field the account its host named (Where() has long written "chatgpt.com as dee@example.com" for an account), and otherwise "" — shown as "account not recorded", never inferred from today's sign-in. The summary groups calls by provider and account (Summary.Accounts, the same email on Codex and on Claude being two accounts), priced as every other group; the ledger's Filter has Account, the search finds a call by its account, the CSV has a provider_account column beside provider_key_name (the provider's identity columns together, the caller's still last), and the Requests page's packed rows keep it and offer the period's accounts. magpie usage prints an accounts table after the provider keys, and magpie usage --csv --account <name> keeps one account's calls. The app's Usage page lists Accounts on the Overview (tokens and cost per account, the provider under its name, an unrecorded one in the faint colour, no stripe down a row's side) and has an Account filter on Requests, each account noting the providers it answered for, which asks for and exports that account's calls without moving the page. OTLP export is an allowlist and does not carry the account. TestUsageNamesPinnedAccount (both pinned accounts on magpie's endpoint and on Codex's own, the trace's Who and the record agreeing, no token in usage.jsonl) and TestUsageNamesAccountThatTookOver (Ordered routing, the first account refusing: the answer recorded as the spare, the refusal as the first) fail without the gateway change; TestSummaryByAccount, TestLedgerByAccount and TestRequestPageByAccount cover the grouping, the host fallback, "not recorded", keyed providers kept out, the filter, the search, the CSV column and the packed rows; TestLedger and TestLedgerWithSessionLogCalls take the new column. usage-accounts.test.cjs (WebKit and Chromium, en and zh: the Overview's accounts and costs, the Account picker's notes, its request and export, All accounts clearing it, scrollTop unchanged) fails without the asset change; api-key-usage, click-scroll, panel-usage, request-archive-usage, tray-usages, claude-usage-asked and every usage-* test pass in WebKit. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass (TestFXInState failed once on its TempDir cleanup, passes alone). | ||
|
|
ba0b2d54bf |
routing: 新增路由选项「重置前用完」(pace)——按周额度剩余比例 ÷ 距重置小时数排序 (#517)
* routing: Least used puts first the account with the most of its week left per hour until it renews — it ranked by the share used alone, so with two subscriptions, one 80% through its week and renewing in an hour, the other fresh and renewing in five days, every new conversation went to the fresh one and the first's remaining 20% was lost at its reset. Allowance.Pace: over the windows that count the model and run a day or longer (the five hours are a rate cap, not a budget; Kiro's month counts), (100 − used) / hours to reset — a week not started or not known runs its whole span from now, one whose reset has passed is full again, of several (Opus's own beside the general) the tightest; an account whose vendor tells the five hours alone (Claude's own usage command) counts as a week not started with their share used, so those go by what they have used, as they did. weigh's LeastUsed case: one at 98% or more last whatever its week, by how far; then pace, highest first, in bands alike within a tenth drawn from the top before sorting (a pairwise tenth isn't an order: three paces a twelfth apart go round in a circle); within a band the tokens sent lately, then their order, for the cache; an account not known counts as a fresh week (before, "used 0" put it first), a key follows the subscriptions. Renewing the same hour, pace is most-left-first, as before. Smart untouched. The trace carries pace; the Routing tab's story, the provider and group editors' hints and the docs' routing table (en, zh) say what the option goes by now. TestAllowancePace, TestLeastUsedPace (fails on the old weigh: "imminent renewal first: ab"). go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider/ ./internal/gui/ pass; ./internal/gateway/ passes but TestPluginModelListThroughAccountProxy, which fails on main too (a concurrent Body.Read panic in its fixture). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * routing: weekly pace is its own option, "pace"; Least used keeps its meaning (yetone, #517 review) — a user who had picked Least used was being switched to another strategy under the same name. Weekly pace: the account with the most of its week left per hour until it renews goes first, the one with the most to lose at its reset; Least used is most-left-first again, Smart untouched; the CLI takes routing=pace (weekly-pace), the TUI cycles to it, the group and provider editors, the Routing tab, README and the docs' routing tables (en, zh) list it. Review points: the tiers are Smart's — one at 90% or more of any window that counts the model, the five hours too, waits behind the healthy ones, by share, one all but used up behind those (an account 95% through its five hours with a healthy week ranked ahead of a fresh one until 98%, and the allowance is read about once a minute, so it was sent into 429s); among the healthy, one not known that tells what it has left as it answers (a Claude account) goes first while it isn't known, as in Smart, else it would never answer and never be known; a window that says no span (a plugin may not) is a budget for as long as its reset says, else a week, where it fell among the five hours and made the week not started; the hours until a reset are never fewer than one, or a window thirty seconds from renewing paced at 6000; the trace carries due, the reset of the window the pace went by, and the Routing tab's story says the week's share left over the hours until that, not the first reset it finds; Routing's comment in provider.go and README's routing= line name the option. The bands of pace alike are drawn among the healthy known accounts alone: one running low would otherwise set a band's top and part two alike behind it. TestWeeklyPace (the tiers and their 90% edge, the probe and its end, the bands drawn without the low, keys by tokens and after subscriptions, the trace's pace and due, Least used unchanged), TestAllowancePace (due, the span-less budget at 48 h and at 90 min, a day the shortest budget, the hour floor, normalize keeps pace). go vet ./internal/... ., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./internal/provider/ ./internal/gui/ ./internal/tui/ . pass, the two new tests 25 times over; ./internal/gateway/ passes but TestPluginModelListThroughAccountProxy, which fails on main too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |