Files
wizzy-yangandwizzy-yang e95852e301 usage, gui: 用量页补上各 agent 会话文件里的用量,请求页按「经过 / 不经过 magpie」拆开 (#948)
* sessions: dsh、ZCode、WorkBuddy 的会话文件里有各自花掉的 token,用量台账从来不读

Sessions 页面一直都在读这些 agent 自己的会话文件,用量台账没有:用量页只显示网关本
身应答过的调用——也就是装上 magpie 之前的什么都没有,而一个直接指向厂商的 dsh 更
是什么都没有。本机上装 magpie 之前就已经在用这些 agent(用它们自带的模型),那段
用量一直对不上账。

  * 新增三类"整文件读取"的调用来源,走 wholeCallReaders(它们的调用不是一行一条,
    或者文件不能从中间接着读):dsh 的会话是 zstd 分帧追加的;ZCode 用的是 OpenCode
    那几张表;WorkBuddy 的同一句回复会在多行里重复同一个 messageId。三者的 token
    语义各自换算成 Call 的 Input/Output/CacheRead/CacheWrite,调用缓存跟着记一份(v9)。
  * 每个来源都跳过经由 magpie 网关发出的调用:网关自己记过那一本账,还带着真正应答
    的供应商和账号;dsh 发往网关的请求不带会话头,两边事后对不上(token 数每次不同、
    又没有可配对的 id),硬读就是同一通调用记两次。dsh 认它被配置去的那个 provider
    (dshGatewayProvider:magpie / dial),ZCode 和 OpenCode 认路由里写的 provider
    (ocGatewayProvider)。WorkBuddy 那部分见下一个提交。
  * WorkBuddy 的调用里,同一句回复的多行只记最后一次(它带这个回复的总量);而它从
    旧历史搬过来的回复一条 messageId 都没有,每一条都是独立的一通调用——会话汇总的
    读法(workbuddyLine)就是这么数的,这里照它来,否则台账会比 Sessions 页面少算,
    正是本次要消灭的那类"两页对不上",只是方向相反。
  * dsh 的一通调用保留它文件里记的两样东西:被配置去的供应商(Upstream,作为历史,
    从来不当成路由的证据),以及厂商真正应答的模型——回复的 replayState 里那个和
    请求模型不一致时,Model 记应答的、Requested 记被请求的,和 Claude Code 的文件
    一样的读法,好让请求表的若干列有东西可填。
  * 会话的 fork:dsh 的会话从另一个会话续起来时会带上对方已写入的事件(isSeeded /
    seedLength,session/end-seed 标记之后的不算),parser 跳过的这里同样跳过,否则
    同一通调用会在每个 fork 上再花一次。
  * WorkBuddy 的 subagents/ 子目录是父会话自己的调用来源(它带自己的消息 id,会话
    归到父会话名下);调用这条路不读 file.main,那个标记是 Sessions 页面给"会话自己的
    文件、不是子代理的"用的。

夹具:dsh 的三个会话(普通、带供应商与应答模型、以及一通经网关的),ZCode 的两条
(其中 ses_zcA 那条的 providerID 是 magpie,应被跳过),WorkBuddy 的既有会话(m1 的
usage 同时落在 assistant message 和它最后一个 function_call 上,只能记一次;旧历史
三条无 messageId 的回复各记一次)和一通经网关、一通走用户自建端点的会话。
调用缓存升到 v9:v8 里既没有 dsh 的供应商和应答模型,也没有"跳过网关调用"这件事,
旧缓存会继续把旧账 Serve 出来。

TestDshCalls、TestZCodeCalls、TestWorkBuddyCalls 是新的;把跳过网关调用那几行、以及
WorkBuddy 旧历史那条判据去掉,它们必挂、加上必过(都实际跑过反向验证)。

验证:go vet;go test ./internal/sessions(只余 TestCursor,Windows 路径前缀问题,
改动前同样失败);go build(windows)。

* usage: 请求页把"magpie 转的"和"它只读到的"分成两个数,GUI 跟着拆成两栏

用量页一直只显示网关自己应答过的调用,而现在它还要读 agent 自己会话文件里的调用:
两本账混在一行里,读者分不清哪一段是 magpie 转的、哪一段只是它从文件里读到的,
而总数事实上几乎全部来自后者(本机 30 天 847M token 里,网关只转了 2.4%)。Summary
里的两个来源卡片也加不出这个数来:它们只算 input 和 output,上面的 KPI 把缓存的
token 也算在内,于是"全部请求"读作 13.6M,而"Tokens"是 847M。

  * usage.RequestPage 和 usage.Filter 各多一样东西:前者在 Sum 之外带上 Through 和
    Direct(网关应答的,和从会话文件读到的),后者多一个 Through 维度。两种建页路径
    (流式的 queryPage 和治疗桌用的 pageFromLedger)都做同一个拆分,两边一致——
    TestCompactPageMatchesLedger 现在也覆盖两个来源筛选。
  * 每一行本来就是按 Source 区分这两类的("log" 是会话文件里的调用,其余走的网关),
    拆分就落在它上面;网关自己拒掉的调用两边都不算(页面在总数下面也这么说),所以
    SourceOf 对它返回 "",点哪一栏都同样把它排除,两栏自己的计数本来也不含它。
  * 两栏自己的数字是在"来源"这个挑选被清空的情况下数的,和各维度分组一样:三栏是读者
    来回切换的地方,各自得一直报着自己的数,否则点了其中一栏另一栏读作 0,且没有回
    到全部的路。
  * /api/usage/requests 支持 via=through|direct;GUI 那一侧的三个格子、计数单位、
    方块样式在下一个提交里。
  * Filter.Via 这个名字和 Record.Via(转发调用的那台电脑,#542)重了,改叫 Filter.Through。

Sessions 页列表长度那件事(sessionListLimit)拆到单独的 PR 里,不在这里。

验证:go vet;go test ./internal/usage(只余 TestOTelRetryAfterBound,计时的 ±30ms,
origin/main 同样失败);go build(windows)。

* gui: 请求页把来源拆成三栏,汇总那一排也改成方块

用途页的请求栏一直只有一本账(Sum),而现在它既显示网关应答的调用,也显示从各 agent
自己会话文件里读到的调用。三个格子画在上方汇总那一排方块里,样式一致:三张各自带
边框、彼此留缝的卡片,窄屏(≤620px)下竖排。

  * 每栏是一个 button:点一下就只列这一来源的请求,再点一下、或点"全部请求"回到
    全部;被选中的那一栏底下有一道本主题色的线。aria-pressed 标出当前选中的是哪一栏。
  * 计数后面跟单位("147 请求"),免得一个光秃秃的数字被读成下面的 token;token 是
    四项合计,和上面 KPI 的算法一致。
  * 三个格子在重绘之间是同一批元素、原地改(class / aria / 文本 / onclick),不是每次
    replaceChildren 重建——同一份 app.js 里 5810 行的规定就是这么写的("The switch is
    the same element across redraws, changed in place, so one being pressed or focused is
    never swapped out"),正在点的那个不该被换掉。
  * 汇总那一排本身(五张 KPI 卡)改成同样的方块:各自一张卡、有缝,边框写成
    var(--line) 而不是 --card-edge(那是面板用的变量,这些卡在独立窗口里也有)。
    ≤860px 两列排布时卡片之间的那几条线照旧:奇数位去掉左边框、第三张起加上边框,
    是 e0eaf5b4 带 #799 的老规则,这里只是跟着搬了位置,没有删。三张来源格是 button,
    不参与这套分隔线。
  * 供应商·账号 一栏:来自会话文件的行显示它文件里记的那个供应商 id(Codex 一向
    如此),有账号时仍是账号优先,什么都没有才说"本地会话"。"本地会话"角标的条件
    跟着放宽到账号或供应商任一存在,提示分两种说法:文件记了供应商的,说明这是它
    被配置去调用的那个记录、不是 magpie 看到的路由(不等于官方供应商);只认得出
    账号的,沿用原来那句"仅当本地元数据能明确识别时显示账号"。
  * 窗口参数支持 via,用后从地址栏清掉,和 tab/provider/agent 等一致。
  * 新增的串子在 en/zh/ja/de 四种语言里都有("{n} tokens" 上游已有,不重复加,
    i18n-keys 绿)。

验证:node --check(app.js、i18n.js);go vet;go build(windows)。
界面效果在本机用改动后的源码构建的二进制上过目:三栏的切换与计数、方块样式、
窄屏竖排、请求表新增的会话文件行(含供应商 id)。
浏览器套件(chromium 与 webkit、en/zh):session-identity、usage-ledger、
ledger-protocol、number-units、usage-ledger-chart、usage-ttft-rounding、
usage-chart-axis、usage-fold、web-page、usage-accounts、i18n-keys 全绿;
ledger-hscroll、usage-speed、click-scroll 全绿;gui-ja、gui-de 各一条失败、
usage-ledger-detail 六条失败,均在本机 origin/main 上同样失败,与本次改动无关。
usage-ledger-chart 新钉了 ≤620px 下 KPI 卡与来源格的分隔线(把 app.css 换回上游版本
它必挂,实测过)。

---------

Co-authored-by: wizzy-yang <wizzy-yang@users.noreply.github.com>
2026-10-07 19:43:01 +08:00
..