dsh-usage-stats
在 DeepSeek Harness 的界面里看这台 harness 花了多少 token:按 Today / 近 7 天 / 近 30 天 / 全部统计,并拆出服务方上报的四个桶(未缓存输入 / 输出 / 缓存读取 / 缓存写入)。
它的重点是「删了也不丢」:用量按会话逐个备份进一份只增不减的台账,
所以删除会话(dsh-session-cleanup、手删目录、归档清理)不会把它那部分用量从统计里抹掉。
适配 dsh 0.1.6-alpha.1 ~ 0.2.1-rc.2(含桌面版 0.2.1-rc.2):会话日志的代际
(0.1.6 写 v3、0.1.7 写 v4,升级后两代可以并存)在磁盘上按代际识别,而不是写死一个文件名;
页脚会写出这次到底读到了哪几代。逐条实测见 docs/compatibility.md。
只读会话日志、不持有凭据、不联系 provider、不改模型路由;除了自己那份台账,不写任何东西。
一、这个插件解决什么问题
DSH 已经在 $DSH_HOME/sessions/ 下为每个会话精确记录了服务方上报的 token 用量,
但那是一个每会话累计值 —— 它回答不了「我今天花了多少」。本插件折叠那些日志,
把它变成按天、按供应商、按模型的报告。
而会话日志是可以被删掉的(dsh-session-cleanup 就是 rm -rf 会话目录)。
日志一没,按日志现场折叠出来的统计就跟着少一块 —— 用户清理一次侧边栏,
历史用量就凭空下降一次,这种数字没法用来做判断。
所以 0.3.0 起:每次扫描折叠出来的结果会按会话备份到
$DSH_HOME/storages/dsh-usage-stats/usage-ledger.json
规则只有两条:
| 规则 | 含义 |
|---|---|
| 只增不减 | 同一个会话、同一个 (天, 供应商, 模型) 格子,取「台账里记的」与「日志现在说的」中较大的一份。日志被压缩、截断、改写,都不能把已经计过费的数字降下来 |
| 从不删除 | 记录写进去就只被向上更新。没有淘汰、没有 TTL、没有上限。想清空统计就删掉那个目录,下次扫描以当前日志为基线重新开始 |
后台每 30 秒巡检一次(只对 mtime/size 变过的日志重新折叠),
所以不需要有人打开设置页,用量也会被记下来:会话建了又删、中间没人看过页面,同样不丢。
二、安装
| 项 | 值 |
|---|---|
| 包名 | dsh-usage-stats |
| 版本 | 0.4.1 |
| dsh 基线 | 0.1.6-alpha.1 ~ 0.2.1-rc.2(engines.dsh 按 0.1.6 / 0.1.7 / 0.2.0 / 0.2.1 四条已测发布线声明) |
| 源码 与分发 | 本仓库(github.com/HaydenSmith1121/dsh-usage-stats) |
方式 A(推荐,也是以后的默认路径):装一次插件市场面板(dsh-plugins-market),
在左侧「插件市场」里点安装。市场里的这一条就是本仓库的 github: 规格 ——
不经过任何中间产物仓库(dsh-plugin-collection 已停止维护、将被丢弃)。
方式 B(手动,等价):直接从本仓库安装:
dsh plugin --profile web add github:HaydenSmith1121/dsh-usage-stats # dsh web
dsh plugin --profile desktop add github:HaydenSmith1121/dsh-usage-stats # 桌面版
profile 选哪个:
dsh web用web,桌面版(DeepSeek Harness 应用)用desktop—— 桌面版的 profile 里带着@deepseek-ai/dsh-web-app与它自己的 bundle 清单 ($DSH_HOME/profiles/desktop/package.json),插件装错 profile 就不会被加载。 不确定时看一眼$DSH_HOME/profiles/下有哪些目录。
lib/(构建产物)是提交进仓库的,所以 git 安装不需要跑任何构建脚本 —— 本包也故意没有prepare:有它的话 pnpm 10+ 会以ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED拦下安装,逼每个用户手改allowBuilds(0.3.1 修的就是这个)。要自己从源码打包:npm run pack。
方式 C(离线 tarball):npm run pack 自己打一个,或从可信副本取 —— 装法同上,
把参数换成 .tgz 的绝对路径。
装完重启(dsh web,或直接退出再打开桌面版):设置页是客户端半,重启后立刻出现;
读会话日志并维护台账的路由由宿主半注册,只在启动时加载 —— 重启之前页面会明说这一点,
而不是给一个裸 404。
三、配置
没有配置项:没有 API Key、没有设置项、没有环境变量。
唯一的路径约定是台账的位置(由 DSH 主目录推导):
| 文件 | 说明 |
|---|---|
$DSH_HOME/storages/dsh-usage-stats/usage-ledger.json |
用量台账(只增不减) |
$DSH_HOME/storages/dsh-usage-stats/usage-ledger.json.corrupt.json |
台账损坏时被挪到这里的原件 |
$DSH_HOME 未设置时用 ~/.dsh(与 DSH 自己的解析顺序一致)。
四、页面显示什么
- 总 tokens,以及构成它的四个桶;
- 缓存读占比(有缓存的 harness 上它常远超其它桶,只给裸总数会显得吓人);
- 该范围内的模型调用次数;
- 页脚老实交代:读到几个会话日志、几个读不到、几个已删除会话的用量是从台账保留的,
以及读到的是哪几代会话日志格式(
· 读到的日志格式:v4 × 12)。
最后那一项是 0.4.0 加的:harness 给会话日志的文件名和表头都打了格式代际,而这个代际
在发布线之间会动(0.1.6 是 v3,0.1.7 是 v4)。把读到的代际印出来,
「它在这台机器上到底读懂了没有」才是一个可以自己核对的事实,而不是一句承诺。
表头读不出代际时显示「未标版本 × N」—— 不猜。
范围是含端点的本地日历窗口且以今天结束,所以它们嵌套:7 天的数字必然包含 1 天的。
台账本身的状态也从不暗示,而是直接写出来:
| 状态 | 页面怎么写 |
|---|---|
ok |
「删除会话不会丢用量:各会话的用量已逐个备份,只增不减。」 |
rebuilt |
「用量台账曾不可用,已重建(原因)—— 重建之前保留的用量不再计入。」 |
unavailable |
「用量台账 <路径> 写不进去(原因),删除会话会同时删掉它的用量。」并给出该路径 |
会话目录整体读不到时(临时故障),页面明说「本次读不到会话目录 —— 所有数字都来自备份」, 并且不会把在场会话谎报成「已删除」。
五、数字从哪来(口径)
折叠刻意复刻 DSH 自己的 tokenUsage 投影(@deepseek-ai/dsh-token-meter),
而不是自己发明一套看起来合理的规则 —— 一份和框架对不上的报告,比没有报告更糟。
三个 load-bearing 的细节:
| # | 细节 | 不这么做会怎样 |
|---|---|---|
| 1 | 同一步骤内最后一条样本胜出 | 一个流式步骤会为同一个 (turn, step) 记录多条 usage 样本,每条替换前一条。朴素求和会把一个流式步骤重复计很多遍 |
| 2 | 一次重试开一个新槽 | llm/retry-started 为它自己的 (turn, step) 关闭当前槽,所以被重试的那一次会与它替换掉的那次并存相加 —— 它确实被计费了两次 |
| 3 | assistant/message 与 assistant/attempt 都带 usage |
后者是它内嵌流里最后一条 usage chunk |
node scripts/verify-fold.mjs --reference <0.2.0 的 lib/index.js> 会重新推导每个会话的合计,
并与上一个发布的真实产物逐字节比对(npm test 用仓库内冻结的那份参考实现)。
磁盘格式:按代际识别,不按版本号
折叠规则在两条发布线上没变,但读日志这件事会变,所以它是单独一层
(src/usage/format.js),并且只认磁盘上的事实:
| 代际 | 文件名 | 表头 | fork 继承切点 |
|---|---|---|---|
| 0–1 | session.jsonl[.zstd] |
扁平,带 seedLength |
表头里的 seedLength |
| 2、3、4 | session.v<N>.jsonl[.zstd] |
扁平,只有 isSeeded |
最后一条带 inherited: true 的 session/end-seed 事件的 seq |
一个会话目录里有多代文件时(升级后就是如此),读数值最高的那一代 —— 与 harness
自己的选择一致。声明了 isSeeded 却找不到继承标记的日志,记为读不到,
而不是猜一个切点:harness 自己的解码器对这种日志同样抛错,而猜错的代价是把父会话的整段
前缀再计一次费。逐条实测见 docs/compatibility.md。
已知统计边界(诚实登记)
| 边界 | 说明 |
|---|---|
| 标题生成不计入 | session/title-llm-request 会消耗 token 但不记录 usage 样本,任何折叠都看不见。DSH 自己的投影有同样的盲点 |
| fork 的会话不向父会话重复计费 | 继承前缀(session/end-seed { inherited: true } 之前的事件)被跳过,因为那些事件保留父会话的时间戳、已经随父会话计过。0.4.0 之前这一条实际上没生效(切点读错了字段),fork 会话的父前缀被重复计费 |
| 台账启用之前的删除救不回来 | 0.3.0 第一次运行之前就被删掉的会话,日志已经没了,任何实现都无法复原。台账从第一次扫描开始积累 |
| 删除前最后 ≤30 秒的用量 | 极端情况下(会话在被折叠之前就被删掉)可能来不及记账。巡检间隔是 30 秒,它决定这个窗口 |
| 读失败会被报出来,不会被藏起来 | 页脚显示读了多少个日志、点名几个读不到;不完整的数字永远不会被当成完整的呈现 |
| 未知代际的日志 | 事件形状没变时照常折叠(并在页脚标出代际);连表头都读不出来时按「未 fork」折叠并标为「未标版本」。没有为将来可能出现的形状变化做预测 |
| 同一个会话 id 出现在两个工作区目录下 | 台账按会话 id 记账(这是「删了也不丢」的前提),所以两处同名目录会被合并成一条记录(格子取较大者),而报告会把两份相加 —— 两者不一致。dsh 自己把这种布局判为损坏(duplicate JSONL session id),本插件不额外处理,也不假装它不会发生 |
六、卸载 / 回滚
dsh plugin --profile web remove dsh-usage-stats # dsh web
dsh plugin --profile desktop remove dsh-usage-stats # 桌面版
# 然后重启(dsh web / 重开桌面版)
卸载不会删掉台账:那是你花掉的用量的记录,删不删由你决定。
想清空统计:删掉 $DSH_HOME/storages/dsh-usage-stats/(整目录或那一个 json),
下次扫描会以当前在场的会话日志为基线重建 —— 报告会随之下降,这是预期行为。
回滚到旧版:dsh plugin --profile <profile> add <上一版的 tarball> 即可 ——
本插件不迁移、不改写任何 DSH 数据,所以降级只是换回旧代码,台账原样留着(旧版不认识它,忽略)。
注意 0.3.1 及更早的版本在 0.1.7 上读不到会话日志(写死了 v3 文件名),
回滚到它们只会看到全 0 —— 需要旧版就在 0.1.6 那侧用。
(dsh-plugin-collection 停止维护后,0.3.0 及更早的 tarball 不再有公开托管点;
需要旧版就 git checkout 0.3.0 的提交 && npm run pack 自己打一个。)
⚠️ 与
dsh-workbuddy-quota的关系:本包是它的继任者(改名 + 只保留用量统计)。 两者不要同时装 —— 它们注册的设置分区 id 不同(usage-stats/token-usage), 同时装会出现两个 Token 用量页。旧包仍然可用,但不再维护。
七、开发
npm run build # src/ → lib/(无打包器:宿主半是拷贝,客户端半加外壳)
npm run build:check # 校验 lib/ 与 src/ 一致(CI 用)
npm test # 六个套件:构建一致 / 折叠一致 / 跨版本格式 / 删除保留 / 宿主路由 / 客户端渲染
npm test -- --sessions <dir> # 再加上真实会话日志的逐字节比对与代际核对
npm test -- --reference <解包后的 0.2.0 包目录> # 折叠比对改用真实产物作参考
# 声明面:本包的 peer 范围、客户端 inject、bundle patch 对得上这棵 harness 树吗
node scripts/compat-check.mjs --tree <放着 @deepseek-ai/* 包的目录>
目录:
src/
├─ index.js 宿主半入口:注册路由 + 启动巡检
├─ shared/buckets.js 桶运算与日历窗口(构建时内联进客户端半,两边不会漂移)
├─ usage/
│ ├─ decode.js zstd 多帧 + JSONL 解码
│ ├─ format.js 会话日志的磁盘格式:代际文件名 + 表头 + fork 切点
│ ├─ fold.js 折叠算法(复刻 DSH 的 tokenUsage 投影)+ 只增不减的合并
│ ├─ ledger.js 台账:读取 / 校验 / 原子写入 / 损坏隔离
│ ├─ scan.js 扫描会话目录(按代际选日志,按 mtime+size+文件名记忆化)
│ ├─ report.js 合并「在场日志」与「台账」,产出报告(含读到的日志代际)
│ └─ host.js 扫描→对账→落盘→报告,外加那条 HTTP 路由
└─ client.js 客户端半(ModuleLoader 工厂体:设置页)
scripts/ 构建、六个校验套件、声明面审计、打包
test/ 合成会话日志构造器(逐代真实形状)+ 冻结的 0.2.0 折叠参考实现
docs/compatibility.md harness 版本兼容:实测矩阵与边界
docs/verification.md 验证记录
lib/ 是提交进仓库的:本包也支持 github: 直接安装,而 git 安装不会跑我们的构建步骤;
npm run build:check 是保证它与 src/ 不脱节的那道闸门。
八、许可
MIT(见 LICENSE)。作者:HaydenSmith1121。
No comments yet. Be the first to write one.