main
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0bf9d77d3f |
agent tests: TestDriftBypassed's request reaching the gateway is kept to its run, so -count=2 passes
The test stood for a request with usage.Saw("codex"). Saw's record lasts
the process, which the gateway relies on (LastSeen answers from it before
a request is written to the log), so a second run started with a request
seconds old, newer than its prompt five minutes back, and found nothing
bypassed ("bypassed: <nil>", also on origin/main).
Drift reads LastSeen through a package variable the test replaces with its
own clock; production still reads usage.LastSeen. That Saw is LastSeen's
answer at once moves to usage's own TestSawIsLastSeen, which removes its
record afterwards.
-run 'TestDrift|TestSawIsLastSeen' -race -count=5 passes; before, -count=2
failed.
|
||
|
|
e961836fb0 |
usage, gateway, gui, provider, cli: the tests pass when a run crosses midnight, and a usage answer reads the clock once (#1046)
* usage: the usage tests pass at any time of day, midnight included The usage tests stamped their calls with the machine's time and then asked for today, 7 or 30 days, or a cached answer, which the code worked out from its own time.Now a moment later. A minute after midnight a call made "a minute ago" was yesterday's, and when midnight fell between the test's read and the code's, the call and the period were of two days. magpie usage, the TUI's Usage page, the two Codex cache-write tests and the computers' shared days failed in the first minutes after midnight (the TUI's after midnight UTC, the zone its package's tests run in since |
||
|
|
117b6c7fb4 |
usage tests: TestReasoningReplyTimedByItsAnswer's timeline rows are made now, as LedgerSeries buckets today by the clock
The test gave LedgerSeries records dated 2026-10-06 14:30 UTC while
LedgerSeries reads today from time.Now(), so it passed only on 2026-10-06
and has failed on every machine since ("part: {Calls:0 ...}" just after
midnight CST on 10-07). The summary and row checks keep the fixed date;
the rows given to LedgerSeries take the clock's. go test -race
./internal/usage passes; gofmt and vet are clean.
|
||
|
|
e6dd5d20bb | usage, gateway, gui: a reply that reasoned is timed by its answer, from its first text, not by all its output from its first token (tony on Discord #general: Usage › Requests read "gpt-6.1-sol 163 token/秒 · 8,036 个请求 · 首字 14 秒" and asked whether subagents were added up) — speed was every output token over the time after the first content (text, reasoning or a tool call). Output counts the reasoning, and a model whose reasoning is hidden (OpenAI's encrypted reasoning, a summary sent when it is done, Claude's omitted thinking) writes it before the stream shows anything, inside the TTFT; so a 2,000-token think and a 200-token answer over the 1.5 s after the first token read 2,200 in 1.5 s, 1,467 tok/s. Concurrency was not the cause: the aggregate is Σ tokens over Σ per-request windows, so subagents running at once never add up. In the owner's own usage.jsonl codex/gpt-6-luna turns read up to 600 tok/s (out 496, reasoning 224, 11,055 ms, ttft 10,228) and its aggregate 133; it is 80 now. usage.DecodeOf is the one rule: a reply with reasoning counts out − reasoning over ms − firstText, one that reasoned and wrote only tool calls tells no speed, one without reasoning is timed from its TTFT as before; DecodeWindow's burst cut (#731) still applies. Record.Decode feeds summarize, the ledger's Totals and its timeline Parts; a route keeps its reply's reasoning (Route.Reasoning, from gateway.go and codex_backend.go), and routing.js's decodeOf/speedOf times its rows, the story and a session's average by it (an older route takes its served usage's reasoning); app.js's ledDecode does the Usage page's rows, whose tooltip says the reasoning left out, and the Output speed help says the rule, in en, zh, ja and de. History is read the same way, as it keeps the reasoning and the first text. Routing never ranks by speed. TestReasoningReplyTimedByItsAnswer (the 2200/2000 turn, a summary 10 ms before the text, a concurrent subagent, tool calls only, reasoning over output, no reasoning, not streamed: 1,200 tokens in 11 s through summarize, addRow, LedgerSeries and Decode) fails without the change and passes -race -count=10; TestUsageKeepsTimeToFirstToken now has its paced replies report reasoning and checks the route keeps it. reasoning-speed.test.cjs (Routing rows 133, 100, none and 100, the story never 1,467, the session 105; the Usage rows, tooltip and KPI the same; no sideways scroll) passes on Chromium and WebKit in en, zh, ja and de, and fails against the old assets (1467 tok/s); routing-request-metrics, routing-burst-speed, usage-speed, usage-ledger-detail, i18n-keys, gui-ja, gui-de, ledger-hscroll and click-scroll pass on both. go test -race -tags nogui ./internal/... under a temp HOME passes but internal/proc's TestUserPath ("the login shell's PATH never came"), which failed under the full run's load, passes 3/3 alone and is not touched here; go vet, gofmt -l, linux and windows (nogui) builds pass. Not tried with tony's data. docs/reference.md says how output speed is counted. | ||
|
|
91fbf15fec | usage: a reply that came in one burst at its end tells no tok/s, and is left out of the summed speed (#731, Arcadia822: a Gemini turn through Antigravity that was one write tool call, out 8264, ms 24360, ttft_ms 24359, read 8,264,000 tok/s and lifted the Usage page's average) — a reply's speed is out ÷ (ms − ttft_ms) since #196, and the sums are Σout ÷ Σ(ms − ttft_ms) (Totals.DecodeOut/DecodeMs). codeassist hands a native functionCall over whole, so a turn whose only content is one big tool call has its first content event a millisecond or two before the end: the window covers delivery, not writing, and its tokens over next to no time read millions a second and pull the summed speed up (the test's three calls: 100 tok/s each in truth, 616 with the burst counted). A decode window now counts only when it timed the writing: usage.DecodeWindow gives 0 when it is under MinDecodeMs (100 ms) or the tokens over it would be past MaxDecodeSpeed (10,000 tok/s), both far from any real stream (the fastest vendors write a few thousand a second); Totals.add (records) and Totals.addRow (ledger rows, which the Usage page sums) both go through it. Such a reply still counts as timed and its TTFT in the mean; only its speed is left out, so a model with only bursts shows no tok/s (the Usage page and the CLI already skip a speed with no decode window). The Routing page's story, which works out a try's speed itself (routing.js speedOf), applies the same bounds, so the burst says "first token in 24 s" and no tok/s. app.js's figure is decode_out ÷ decode_ms from the Go sums and needed no change. Tests: TestBurstTellsNoSpeed (the report's record with two 100 tok/s calls: 100 tok/s summed through summarize and addRow, TTFT mean keeps the burst; DecodeWindow's edges: 1 ms, 0 ms, 3 tokens in 50 ms, 8264 in 500 ms, exactly 10,000 tok/s over 100 ms, nothing written, not streamed) fails on the old rule with 616 tok/s; TestSummarizeTimesFirstTokens unchanged. routing-burst-speed.test.cjs (webkit, en and zh: the report's burst and a 500 ms one tell no speed, 100 and 50 tok/s replies keep theirs, picking a request doesn't move the page) fails on the old routing.js with "8264000 tok/s". routing-sessions, routing-served, routing-effort-change, usage-accounts, panel-usage and click-scroll GUI tests pass in webkit (routing-served failed once in a parallel run and passes on its own twice; its fixture has no TTFT). go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried with the reporter's ledger or a live Gemini stream; a short reply under 100 ms (a title, a few tokens) no longer counts toward the speed either, which moves a summed figure by next to nothing. | ||
|
|
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. | ||
|
|
c641c1da2d | usage: a built-in moved onto its plugin keeps one history per account (要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — the built-in's calls were kept as going to "dee@example.com" (or "api.factory.ai as dee@example.com"), the plugin's as "kiro as dee@example.com", so Usage split an account's model rows at the move, the older half as if the id had gone elsewhere. A subscription's calls are now told apart by the account they went as, the host only for the rest; records already kept group together with no rewrite. TestSummarizeKeepsMovedAccountTogether fails without the change. go vet, go test ./internal/usage pass. | ||
|
|
6aa99ef1c0 |
usage: costs show in RMB at a live rate, when Settings picks it (#212 L1cardo: 添加费用用人名币结算的选项,最好是能根据实时汇率来计算) — the Usage page, the tray panel, the TUI and the CLI all price a call in USD, magpie's list prices being dollars; Settings now has a Currency row (after Usage shows) offering $ USD, kept by default, and ¥ CNY, which converts every one of those at display time only — usage.Totals.Cost and the JSONL records it comes from stay in USD always, and a vendor's own balance (already in its own currency, a relay's ¥) is never touched.
The rate is internal/fx: a free, no-key endpoint (open.er-api.com/v6/latest/USD), cached in memory and in a small file beside magpie's other caches (XDG_CACHE_HOME/magpie/fxrate.json) for 12h, refetched only once that's stale, and falling back to the last rate it ever had — or a fixed 7.2 — while offline; the currency row's tooltip names the rate and when it was learned. usage.FormatCost carries the $/¥ formatting (2 decimals above $1, 3 below, whole numbers above 100) that usage_cli.go and internal/tui/pages.go used to each have their own copy of; both now read Settings' currency once per command/redraw rather than per row. internal/gui/api.go exposes the rate on /api/settings always and on /api/state only when cny is actually chosen, so usd users' hot path never touches the network for a rate nothing shows with. Tested with new internal/fx/fx_test.go (a fake server: fetches and caches, answers from memory then from the cache file with no further calls, refetches once stale, falls back to a stale cache or Fallback when offline, ignores a reply with no CNY rate), TestFXInState in internal/gui/fx_test.go, TestFmtCostShowsChosenCurrency in internal/tui/usage_test.go, and a new internal/gui/tests/currency.test.cjs in Chromium and WebKit, English and Chinese (the Usage page's $ by default and ¥12.34×7.2 once cny is picked, the currency row's labels/state/tooltip, a real wheel scroll deep into Settings left alone by picking a currency, the choice surviving a reload). go build -tags nogui, GOOS=linux and GOOS=windows builds, and go test -tags nogui -count=1 ./... all pass clean. A fetch that failed is not tried again for 10 minutes (the stale or fixed rate shown at once meanwhile), so offline each state() does not wait up to 4s on the network: TestGetDoesNotRetryAFailedFetchAtOnce. |
||
|
|
d924802de5 |
usage: a streamed reply's time to its first token is kept, and its first text's (#196: usage 记录首字延迟(TTFT),用于计算模型的真实生成速度) — usage.jsonl had only a call's whole ms and its out tokens, so how fast a model writes couldn't be told from how long it waited or thought. The reply is now read as it goes to the agent, in the agent's protocol whatever the vendor spoke (Anthropic content_block_delta text/thinking/input_json and a tool_use's start, Chat content/reasoning_content/reasoning/tool_calls, Responses output_text/reasoning/function_call_arguments deltas and a function call's start, Gemini parts), and each record gets ttft_ms to the first content and first_text_ms to the first text, counted from the request as ms is, so ms − ttft_ms is the writing time; a reply that isn't streamed gets neither, and omitempty leaves old records as they were. Only the stream's events up to its first text are read (a JSON reply, or past 8 MB, not at all). Each try in the routing trace keeps its own from when it was sent, so a group that fails over shows which member was slow, and the route, the request log and the Codex backend's relay keep them too. Shown on the Routing page (TTFT in the list, first token / first text / tok/s in a try's story), in the Gateway's request log, and on the Usage page and in magpie usage per model as the mean TTFT and tok/s = out ÷ (ms − ttft) over the answered streamed calls. Tested with fake vendors streaming a gap apart for Anthropic, Chat and Responses, as they came and translated (Chat from Anthropic, Responses from Chat), the ChatGPT backend without a Content-Type, a whole reply, a group whose first member fails (its try untimed, the second's timed from its own start, the request's including the failed try), each protocol's events and split writes, and the summary's sums; the GUI's browser tests pass
|
||
|
|
8dae6aa25b | usage: each call is kept with the session it was part of — the X-Magpie-Session header when the caller sends one, else the session its agent names itself (Claude Code's x-claude-code-session-id, Codex's session_id, OpenCode's) — as "session" in usage.jsonl, and magpie usage lists the sessions that used the most, so several sessions on one model can be told apart | ||
|
|
4f0b889ec1 | usage: TestSummarizeTellsPlacesApart holds at any hour — its calls of hours before were yesterday's when it ran after midnight, and Today left them out; it now runs at noon | ||
|
|
4790c06b02 | usage: each call keeps where it went — the provider's host, or the account it was — so a provider id later pointed somewhere else no longer puts the earlier place's tokens and cost under the new one: a model's usage is told apart by host (provider/model @ host) when its provider went to more than one place in the period, or to somewhere other than where it goes now. Asked on Discord | ||
|
|
a9e1a6769a | agent + usage: each agent says what its User-Agent begins with, beside its name and icon, and the gateway tells requests apart by that — usage kept a list of its own, which had to be kept in step with the agents by hand and wasn't (DeepSeek Harness, omp and Command Code were missing from it, so they were drawn with no logo); a new agent is now known by its requests as soon as it is described | ||
|
|
658d264e14 | usage: DeepSeek Harness, omp and Command Code are known by their User-Agent — deepseek-harness/… was kept as it came, not as the agent dsh, so the Routing view and Usage drew it as an unknown agent with no logo; records kept that way are read as the agent's too | ||
|
|
30c489a000 |
usage: a token statistics page
The gateway now records every call's token counts (read from the provider's own usage block, in both the translated and the pass-through paths) with the calling agent taken from the client's User-Agent, into ~/.config/dial/usage.jsonl. A Usage tab sums them by period, agent and model, with a per-hour/day/week timeline and cost at models.dev list prices; dial usage does the same in the terminal. Also nudges the window header 1pt up so the brand sits level with the traffic lights, and lets ?view= open the window on a tab. |