main
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7f62dbf3ed |
gateway, agents, gui: a model's reply limit is published within the window beside it, on /v1/models, Cursor's and Muse's lists, the Gateway page and magpie model output (#1438, yhong91)
#1438 (yhong91): GET /v1/models listed grok/grok-4.7 with context_window 256000 (the Grok backend's own) and max_output_tokens 500000 (models.dev's x-ai entry, which catalog.Decorate fills in when the backend gives no output). A client that reads both sizes its replies past what the window holds. The agents' files already kept output within the window (maxTokens, #338/#347), but the gateway's lists, the GUI and the CLI did not. What changed: - provider.OutputWithin(window, output) and Entry.PublishedOutput(): the output is kept within the window; an unknown window or output (0) is left as it is. - Every place that publishes the pair uses it: modelObject (/v1/models), cursorLocalModel (Cursor Private Inference), Muse Code's list, the Gateway page's models and groups (providerInfo, providersState), the agents' maxTokens, and `magpie model output` (which also says "500k, kept within its 200k window" when it lowers one). - Entry.Output stays the model's own limit. The request-path cap (outputLimit/withMaxOutput) and the user's Max output setting are unchanged, so no request is lowered further than before. This is why the clamp is not in replyLimit/entryFor as the issue suggests, and why the commenter's patch (modelObject only) was not taken as is: it was right but missed Cursor, Muse, the GUI and the CLI. - docs/subsystems/gateway-routing.md: the rule and its routes. Tests (each fails without the change): - gateway TestModelListsKeepOutputWithinTheWindow: models.dev 500000 over a backend 256000 window, plus output within the window, unknown window and unknown output, on /v1/models, Cursor's capabilities and Muse's limit; a group request with max_tokens 300000 still reaches upstream as 300000. - provider TestOutputWithin, gui TestProviderModelOutputWithinTheWindow, TestModelOutputCmdWithinTheWindow. Breaking it by hand: OutputWithin returning output unclamped turns all of them red, plus agent TestMaxTokensWithinContextWindow; gateway sites back on e.Output turn the gateway test red on all three lists; moving the clamp into Entry.Output turns the fixture and the group-request guard red. Real data: an offline dump of the owner's catalog (copies of ~/.config/magpie and its caches, 203 entries) has 3 models with output over their window, grok/grok-4.5, 4.6 and 4.7 (256000 vs 500000), now published as 256000; 0 are not Grok. The live gateway on 3425 was not running, so /v1/models itself was not read, and no real Grok request was made. Run: gofmt -l; go vet -tags nogui ./... on darwin, linux and windows; go test -tags nogui ./... -count=1: all green except TestLoopEndsWorkBuddysReply/plugin/anthropic ("let go only after 3207 lines", bound 2000) at load average ~35 with other suites running; it passes 5 of 5 alone and does not touch the output limit or request path. |
||
|
|
0361189f9e | test: every package's tests pass on Windows, and none writes outside its sandbox (the user: 把 Windows 上那几个失败的测试修掉) — on a Windows box the suite failed in most packages, and the root package hung for 25 minutes. Tests set HOME, which os.UserHomeDir ignores on Windows (it reads USERPROFILE), so they read and wrote the runner's own home; each now sets USERPROFILE beside HOME, and Devin's, Goose's, Crush's and Claude Desktop's tests set APPDATA/LOCALAPPDATA, where those keep their files there. said() read the command's output only once it returned, and a pipe holds a few KB on Windows, so magpie model --help blocked forever; it reads while the command writes. Unix permission bits (the gateway key store's 0600 too), exec bits and symlinks Windows lacks are checked only off Windows; TOML strings hold Windows paths as literal strings, JSON as marshalled ones; a URL's path is compared as one. The provider and usage packages run in a home of their own (TestMain, as the gateway's already does), since a CLI's identity kept by a refresh still going when its test ended landed in the runner's ~/.config/magpie — and on a Mac in the user's own. Two product fixes came out of it: imagemcp took a file:///C:/… root or image for the path /C:/…, which doesn't exist on Windows, and now makes a Windows path of it (a host for a UNC share); and session discovery could keep a new Cowork session hidden on Linux, where a folder's mtime is a kernel tick and its size doesn't change, so an entry added within the tick of the last listing left both as they were — a listing made within 2s of its folder's last change is read again, as git does for racy files. TestFilePath and TestRacyDirectoryListedAgain fail without the change. On Windows 11 every package passes (32 of 32) and a run of each package in a fresh home leaves nothing in home, APPDATA or LOCALAPPDATA; on Linux (WSL) and macOS go test -tags nogui ./... passes. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build pass. | ||
|
|
024f494bc4 |
Allow provider-specific upstream model names (#280)
A provider may serve a model under an id of its own: namespaced with a prefix, dated, a `-latest` that is not what models.dev calls it, or simply a different spelling. magpie sends the name it knows the model by, and the provider answers that it has no such model. magpie model wire relay-b/model-2 vendor-c/model-2-preview magpie model wire 'relay-b/*' 'vendor-c/*' magpie model wire relay-b/model-2 --reset A `*` in the **value** stands for the model, so `relay-b/*` set to `vendor-c/*` sends `vendor-c/model-3` for model-3. A value with no `*` sends every model of that provider under one name, which is the case where a relay really does serve them alike. The CLI shows the pattern the way it was given, with a concrete line for one of the models, so what is in force is not something you have to imagine. An exact key wins over the provider's. The canonical id stays canonical. `Entry.Model` is not rewritten: it is the key automatic routing groups are built from, what `Resolve` returns as the model's identity, what `GET /models` lists, what an agent asks for, and what a call is recorded and priced as. The alias goes into the request's model field and nowhere else — which is the part that was wrong at first. Handing it to the serializer gave the provider's name to the code that decides what a request should look like: `buildChat` checks the model for Gemini compatibility, `buildAnthropic` matches it for adaptive-only and effort handling, `buildCodeAssist` maps by model. A provider serving a model under a namespaced id got a request shaped as if it were that other model. So the request is shaped from the model magpie knows and the field is rewritten once at the end, with `rewriteModel` — which already existed to put one provider's own model name in a body and keeps every other byte as it was. It has to reach every path. A request the provider understands as it came is relayed by `passthrough` without being rebuilt, and the Anthropic token count is asked of the provider directly; both rewrite the field too, so on a provider speaking the client's own protocol — the common case for a relay fronting one vendor — the alias still applies. "Swapped" compared the canonical id with what the vendor's reply named, so an alias that is not a prefix or a dated variant marked every call swapped. It compares the name that was actually sent, in the gateway and in the ledger alike; the ledger reads the map once for the lot, since its rows are what a reader sets beside a bill. The Codex backend path is left alone: it sends magpie's own pick and never an alias. `provider.Test` asked with the canonical id, so a provider that only accepts the alias reported a failure while its real requests worked. It asks with the alias now and still reports the canonical id, since that is the one the user picked. A blank name was a hole: the setter trimmed what it was given, so a value of only whitespace was removed on the way in, while the read path would have used it had anything else written it — a hand-edited file, or a build from before the trim. Both ends treat a blank as no name now. The key is checked where it is written rather than in Save, so one hand-edited entry cannot block every save, and #288's walk moves it on a provider rename without a line here. Co-authored-by: yetone <yetoneful@gmail.com> |
||
|
|
49f40b463f |
Configure per-model context windows and output limits (#279)
A provider that serves a model the catalogue does not list, or lists at the wrong size, has a window and a reply limit magpie cannot know: both fall back to whatever the catalogue says about the model's maker, which for such a provider is nothing at all. magpie model context relay-a/gpt-5.5 262144 magpie model output relay-a/gpt-5.5 131072 magpie model context 'relay-a/*' 200000 magpie model output relay-a/gpt-5.5 --reset Both are looked for in the same order as each other: this model, then `<provider id>/*`, then the provider's own list, then the catalogue. A model's own value wins over its provider's. The reply limit is new; the window is not — `Provider.Contexts` has been there, with a `"*"` of its own, settable through `magpie provider set <id> context.<model>=`, and this reuses it rather than shadowing it. That reuse is the point rather than a detail. A context is a routing input and not only a number agents are shown: at 95% of a held member's window a request moves to a member that takes more, so two sources of truth would mean the gateway routing on one number while agents are told another. `magpie model context` is a second way to set the same field, not a second field. Both reach the agents. Pi keeps a model's window and reply limit in its own models.json, OpenCode keeps its limit, and Crush, Droid, Cline, Qoder and Zcode keep their own; an agent reads those files when it starts, so a limit set here that never reaches them is a limit that does not take effect. `magpie provider set` has always told the agents, and this behaves the same way rather than introducing a second, quieter path. The test asserts each setter reaches them exactly once — the two paths differ (one saves the provider file, the other settings and then says so) and that difference is worth pinning. An end-to-end pass confirms it: with a Pi models.json in place, `magpie model context relay-a/sol 999999` writes `contextWindow: 999999` into `providers.magpie.models`. A `0` is refused rather than read as a reset, naming `--reset`; a mistyped model id is refused the way `magpie model name` refuses the same id; a reset says what is actually in force instead of claiming to be back to the vendor's list while a provider-wide value still applies; and `magpie model context <provider>/*` reports that value rather than saying the provider serves no such model. Validation happens where the entry is written, so one hand-edited bad key cannot stop a theme or a window size from saving, and #288's walk moves these on a provider rename without a line here. |
||
|
|
901ee08280 |
Count usage at the configured provider/model price (#278)
Magpie reports models.dev's list price for every call. That is the right number for a vendor's own key and the wrong one for a provider that resells or exposes a model at a different tariff, and it leaves a model the catalogue does not list with no cost at all — a provider's own /models gives ids, not economics. magpie model price relay-a/gpt-5.5 0.12,1.20,0.01,0.15 magpie model price relay-a/* 0.12,1.20,0.01,0.15 magpie model price relay-a/gpt-5.5 --reset magpie model prices Absolute USD per million tokens. All four parts are required: a price missing one would understate the rest of every call, and a hand-edited file that left a part out would bill it at zero, so each part is a pointer and the setter refuses a price that is negative, NaN or infinite, naming the part at fault. Zero stays distinct throughout — a model served at no cost has a price, not the absence of one. A `<provider>/*` entry covers every model of that provider and an exact entry wins over it; --reset removes only the exact entry and says so when a provider-wide one is still in force. The order a price is looked for in is the price for this model, then `<provider>/*`, then the provider's own catalogue, then models.dev for the maker. A price is a provider's tariff, not the model's: the same model through two providers has two prices, and each keeps its own. A provider is resolved by its id, or by an id it was renamed from, and never by its display name — Find answers those too, and a record naming one was counted at another provider's price. provider.EffectivePrice is the one lookup every cost caller shares, so the usage ledger and a session's totals both go through it, and a session naming a model bare is still priced at its maker's list price, which is a different question from what one provider charges. catalog.PriceOf and PricedBy keep returning the models.dev price for anything wanting an unbiased number. The background-task pickers (draw, search, vision) still rank by catalogue price: they choose which model answers, not what a call is reported to cost. Unlike every other per-model preference this does not call catalog.Touched. What a call costs is not what an agent picks a model by, and the model lists magpie keeps in the agents' own files are not its to rewrite over a number in a cost report. A rename moves the prices with it through the walk #288 adds, so nothing here has to say so, and an id recorded before a rename still resolves. Validation happens where the entry is written, not in Save: one hand-edited bad price in the file must not make saving a theme or a window size fail. A price for a model the provider does not serve is refused the way `magpie model name` refuses the same id, since it is a price that never applies and nothing reports it as dead; a removal is exempt, so an entry left for a model that has since gone can still be taken away. |
||
|
|
c3052fe43c |
test: magpie model efforts gives a model whose reasoning levels aren't known the ones typed — TestModelNameCmd still expected it refused, as before c1d38cc
|
||
|
|
47e12df418 | models: a provider's model can go by a name of the user's, and offer only some of its reasoning levels — Claude Opus 5.5 · Claude Code can be Opus 5.5, and a model with six levels can offer just low, medium and high. Both are kept in settings.json by provider/model (modelNames, modelEfforts), apart from the vendor's list, so a Refresh, a restart or an upgrade leaves them, and the same model from another provider keeps its own; a provider renamed takes them along. They are applied where the catalog is made, so the gateway's /v1/models, the lists magpie writes into the agents (Codex's model catalog, ZCode, Pi, OpenCode, OMP …) and magpie's own pages all show them, and a change rewrites the agents' lists as any other change of the catalog does. Only what is shown changes: the id, where requests go, and the levels the vendor is asked at stay as they were; a found routing group keeps the model's own name. The provider's editor has Names & levels under its models — a name typed in place, a tick per level, Restore default — and the terminal has magpie model name <provider/model> <name>|--reset and magpie model efforts <provider/model> low,medium,high|--reset. Issue #92 |