dsh-token-total
DSH(DeepSeek Harness)Web 插件:Token 用量统计看板 + 单会话用量(含子智能体)。
三个入口,一个插件:
- 统计看板 — 侧边栏插件区下方的入口,点开是完整的用量看板(快捷筛选 / 六卡片 / 活跃热力图 / 缓存命中率趋势 + 分模型柱状图 / 按模型与按供应商 Top5)。
- 单会话统计 — 增强会话输入框下方的原生信息栏,把「Token 用量」从「只有主智能体」改成「主智能体 + 全部子智能体」,让一次会话的真实消耗有地方可看。
- 会话用量页 — 在对话旁新增「用量」tab,展示两行统计卡(请求数量 / 总 tokens / 命中率 / token 速度,及输入 / 输出 / 缓存读取 / 缓存写入)、子代理与模型明细、按轮折叠的请求记录;子代理与模型明细之下、请求记录之上是并排两张实时卡——「当前上下文」画出窗口里现在压着什么,「Token 统计」把整场会话的累计花费按内容拆开。
所有图表都是手写 SVG,不依赖任何图表库。全局看板、底部胶囊与会话用量页全部跟随 DSH 明暗主题:浅色下画布是宿主的纯白底,暗色下换用宿主暗色面板色,颜色全部取自 DSH 语义令牌。
功能
统计看板
入口在左侧边栏的插件区(与原生「插件」页并列的独立页面),点开即为看板,分上中下三部分。
上部
- 快捷筛选:今日 / 近 3 天 / 近 7 天 / 近 1 月 / 近 3 月 / 自定义(起止日期)。
- 一行六个卡片:总 Tokens、输入 Tokens、输出 Tokens、缓存 Tokens、缓存命中率、请求数量。
Token 数量用 K / M / B 紧凑单位显示主值,下方一行给出精确数字。「请求数量」在本时间段出现过失败调用时,下方会多一行「失败 N · 占比」,没有失败则保持单行显示。
- 活跃热力图:GitHub 风格,格子颜色深浅表示当天 token 消耗的多少;图例「较少 → 较多」五档。
- 右上角保留 刷新(只重取聚合结果),不再提供重建统计按钮。
中部
- 缓存命中率趋势:一条平滑折线(Catmull-Rom 转贝塞尔)。
- 分模型柱状图:堆叠柱表示实际 token 消耗,不同模型不同颜色;右轴为 0–100% 缓存命中率。
- 不显示图例:鼠标悬浮时 tooltip 逐项列出该时间桶内各模型的 token 与命中率。
- 横轴粒度:今日 / 近 3 天按小时分桶,更长的区间按天分桶。
下部
- 按模型:按模型选择器的显示名称(不是路由 id)跨供应商聚合,取前 5,展示模型名、总 Tokens、缓存命中率(跨多个供应商时附供应商列表)。排行与趋势使用同一归一化规则:忽略大小写,将空格、下划线和连字符统一为
-,例如 gpt-6.1-sol、GPT 6.1 sol、gpt 6.1 sol 合并为 gpt-6.1-sol;版本号和后缀仍区分,避免误合并不同模型。无法读取历史路由的模型元数据时,回退到历史引用中的模型名称;不改写原始记录,不需要重建统计。
- 按供应商:一个供应商下所有模型聚合,取前 5,展示供应商名、总 Tokens、缓存命中率。
单会话统计
会话输入框下方原生的「用时与速度 / Token 用量」两枚胶囊被接管并增强:
- Token 胶囊显示的是整棵会话树(主智能体 + 全部子智能体)的合计,点开弹窗分为「主智能体 / 子智能体 / 会话合计」三行,并给出子智能体会话数。
- 子智能体是独立持久化的会话,通过日志头的
parentSession 串成树;插件按需重放这些日志汇总(同一套 (turn, step) 去重规则,且跳过 fork 继承来的事件前缀,避免与父会话重复计数)。
- 会话 Token 明细始终开启:未命中缓存、缓存读 / 写、输出分别列出;底部胶囊的缓存命中率始终显示两位小数。
- 流式吞吐速度始终开启:输出过程中显示当前这一步的实时解码速度——按已到达的流式增量实测(每步开始约 0.2 秒内出首个读数);一旦没有步骤在流(一步刚结算、工具执行中、步间空档),就交回本轮会话的加权平均解码速度(Σ 输出 token ÷ Σ 解码耗时,与会话「用量」页里的平均速率同口径),而不是停在最后那次短窗口读数上——那正是底部 57 tok/s 与请求记录 156 token/秒看起来互相矛盾的原因。
- 不再提供底部增强功能的开关;旧版本保存的关闭设置不再影响显示。
会话「用量」页
在每个会话原生的「对话 / 轨迹」旁新增 用量 tab;沿用会话切换与视图导航,不另造顶部栏或输入框。
-
两张实时卡(形制参考 magpie)并排位于子代理/模型明细之下、请求记录之上,回答「提示词里现在有什么」与「整场会话到底花了什么」两个不同的问题:
- 左卡「当前上下文」:
已用 / 模型窗口 tokens 大字附占用百分比,标题行右侧是当前供应商 · 模型;下方一条整条即模型窗口的堆叠条——有颜色的部分是已用的七类(按类别占比分色),尾部是还没用上的余量,所以条本身就是占用率;再下面按统一图例列出系统提示词、用户消息、技能注入、工具结果、工具定义、注入内容、助手消息各自的占比与估算 token(输出不占窗口,故不列)。鼠标悬浮可读精确 token 数与占上下文比例;模型窗口未知时只写已用数。
- 右卡「Token 统计」:圆环中心是整场会话精确计费的总 token,右上角徽标是精确缓存命中率(命中 ÷ 输入);圆环与图例把花费拆成系统提示词、用户消息、技能注入、工具结果、工具定义、注入内容、助手消息、输出八类。每次请求都会重放整份消息列表,所以每一类的数字是该内容在全部请求上的累计,而非此刻窗口里的存量;分桶值由字符数估算并在前缀
≈,圆环中心的总量与缓存命中率直接来自 usage 记录、保持精确。
- 两张卡都由会话明细轮询驱动(每 1.5 秒一次),不额外起计时器;旧宿主只提供其中一份数据时,另一张卡显示空态而非整组消失,两份都缺时整组不渲染。窄视口下自动上下堆叠。
-
浅色页面两行统计卡:第一行请求数量(附失败数)、总 tokens(附精确值)、缓存命中率(附命中 token)、token 速度(附平均首 token);第二行输入、输出、缓存读取、缓存写入四个 token 桶。不显示费用。
-
子代理明细使用每个子代理的真实名称(取自子代理标签,缺失时回退到会话标题、工作目录或会话 id)。
-
中部为等宽、等高的 子代理明细 与 按模型明细;窄视口自动堆叠。使用固定列布局而非横向滚动,长名称省略并可悬浮查看全名,token 数同样可悬浮查看精确值。按模型明细的名称使用模型选择器的显示名称(路由 id 保留在悬浮提示中,无法解析时回退为 id);当多行显示名称相同(忽略大小写与分隔符)时,在名称下方用灰色小字标明各自供应商以便区分。
-
请求记录按轮折叠(压缩调用归入独立分组),默认全部折叠;展开某一轮可查看该轮每一次请求的时间、模型、tokens、命中率、状态与入 / 出 / 读 / 写明细,支持单组展开和全部展开 / 收起。失败尝试、实际重试、压缩调用纳入统计;仅继承的父会话历史不重复计算。
-
页面每 1.5 秒串行读取当前会话明细,切换或离开 tab 会取消请求和计时器。统计含本会话及所有后代的自有历史日志,缺失的首 token / 速度显示 —,读取错误保留上次成功结果。
安装
dsh plugin --profile <profile> add dsh-token-total@latest
本地开发时直接指向目录:
dsh plugin --profile <profile> add "file:D:\work\dsh-token-total"
package.json 里的 dsh.bundle.patch 会让 CLI 在安装后把本插件追加进该 profile 的 dsh.profile.bundles,重启后自动挂载,无需手改 profile 配置。若该 profile 的 cordis.patch.yml 里还留着旧的手工挂载行,请先删掉,否则会重复挂载(两个宿主实例、两条重复路由)。
Electron 桌面端的 desktop profile 由应用独占管理,CLI 会拒绝写入(profile "desktop" is managed exclusively by the Electron application)。请用应用内的插件管理器安装,或另建一个 profile 验证。
pnpm run build 只更新本项目的构建产物,不会自动覆盖桌面 profile 已安装的插件副本。桌面端请在应用内插件管理器更新 / 安装本地构建后的插件,再按提示重启宿主并刷新页面;不要直接覆盖运行中的 profile。
数据来源与统计口径
- 事件来源:
sessionPersistence 的会话日志。兼容 assistant/message 的顶层 usage,也读取消息及失败 assistant/attempt 的原始流式用量;同一次请求以最后一份用量为准,修正值按差额更新。插件订阅 session/event 实时增量累加,同时一次性回扫已有历史会话;llm/retry-started 会清空当前 (turn, step) 槽位,因此失败尝试和后续重试分别计入,不重复累加同一次请求的多份报告。
- 本地持久化:聚合结果写入 storage-domain 的
token_total_history 域($DSH_HOME/storages/ 下),纯本地、无外部依赖。回扫游标按会话记录已消费的事件序号,重启后从断点继续。
- 上下文与花费估算:两张卡都从该会话自身的日志重放「下一次请求会带上什么」——被
compaction/prune / compaction/summary 遮蔽的事件剔除、压缩摘要按注入记忆回放、工具目录取最近一次 request/header——再按字符类估算 token(ASCII 约 4 字符 / token,CJK 约 1 token / 字符),所以分桶上的数字都是近似值。左卡的真实占用取最近一次回答上报的 totalTokens − outputTokens;仅当两者相差超过 15%(说明上游有压缩或裁剪,日志里的历史比模型实际收到的多)时,才多出一行说明,并把「已用上下文」改用供应商实测值。右卡的分桶是「每次请求重放内容之和」,圆环中心的总量与缓存命中率直接取自 usage 记录,保持精确。
- 单会话统计:不走聚合存储(那里没有会话维度),而是按需重放该会话及全部子孙会话的自有日志;已加载会话优先使用实时事件快照,避免持久化缓冲造成漏算。结果缓存 4 秒,任意会话的新事件都会使缓存失效,包含此前尚未发现的子会话。前端从首次读取起每 4 秒刷新,显示时使用原生实时主会话用量、主会话压缩调用用量与子树用量之和,胶囊与弹窗共用同一口径;切换会话时立即清除旧会话读数。读取失败不会把部分子树当作成功结果覆盖已有读数。
- 缓存命中率 = 缓存命中 token ÷ 输入 token 总量(命中 + 未命中)。
- 失败数量:以宿主记录的
assistant/attempt 事件为准。宿主只有在流以错误结束、或调用在提交任何内容之前被取消时才写 assistant/attempt,其余调用一律写 assistant/message,两者恰好划分全部调用,所以该事件就是「一次失败的 provider 调用」的精确边界(interrupted: true 但已有内容的 assistant/message 不计为失败)。失败数是请求数的子集,不额外计入请求;失败调用若在流中断前已上报用量,token 仍按用量计入、不会丢失。该计数晚于日 / 小时两份游标引入,因此老版本升级后会额外做一次只写失败数的增量回扫(failuresReady 标记完成,不重复累加 token)。
HTTP 接口
宿主侧注册一个前缀路由 /token-total/api,全部为 POST + JSON:
| 路径 | 作用 |
|---|
/token-total/api/range | 取某个时间范围的聚合({range, from?, to?}) |
/token-total/api/session | 取某会话树的用量({sessionId},返回 self / subagents / total / subagentSessions) |
/token-total/api/session-detail | 取会话用量页的统计、子代理(真实名称)、模型与按轮折叠的请求记录({sessionId}) |
/token-total/api/status | 回扫进度 |
/token-total/api/reset | 清空统计并重新回扫 |
请求经过与官方 /api 网关同源的信任栅栏(Host / Origin / sec-fetch-site 三重校验,防 DNS rebinding)。
配置
| 键 | 默认 | 说明 |
|---|
source | session-log | 统计来源 |
backfillConcurrency | 4 | 历史回扫的并发会话数 |
兼容性
- DSH
>= 0.1.7-rc.1(peerDependencies);实测运行于 0.2.1-rc.2,会话日志格式 v3 与 v4 均可读取(v3 走 alpha 的 open/read 接口,v4 走句柄分页读取)。
- 浏览器半区只依赖
@deepseek-ai/dsh-client-{locale,ui-slots,ui-primitives} 与 React 18,不打入任何 Node 内置模块。
开发
pnpm install
pnpm run build # tsc 声明 + 宿主/浏览器两个 bundle
pnpm run typecheck # 宿主半区
pnpm run typecheck:client
pnpm test # 模型/供应商聚合、会话树、重试用量、底部展示与刷新回归测试
产物:lib/index.js(宿主,ESM)、lib/client.js(浏览器,CJS 包装进 __ModuleLoader__.load)、lib/types/**/*.d.ts。CSS Modules 在构建时被内联成 <style data-plugin-css> 注入,因此 Web 外壳不需要 CSS 管线。
许可
MIT。本项目的宿主与浏览器实现改编自 dsh-usage-statistics-panel(MIT, Copyright (c) 2026 HaoyueQin),在其基础上按本项目的需求重做了卡片、筛选、图表与列表,并新增了整棵会话树(含子智能体)的用量统计。原始版权声明保留在 LICENSE 中。