正文
起因
用 DSH 写代码的时候,我总有个焦虑:这一轮到底烧了多少 token?现在解码速度正常吗?这个月离破产还有多远?
DSH 右下角有个小圆环能看上下文占用,但太小了,而且我要的是「一眼扫过去就知道状态」——就像开车时看仪表盘那样,不用思考。
所以我做了这个:三个表盘浮在界面上,想放哪拖哪。
长这样
中间大、两边小,跟汽车仪表盘一个逻辑:
| 表盘 | 量程 | 我在看什么 |
|---|---|---|
| Token Usage(左) | 0 – 300M | 累计 token。指针进黄区,说明模型读的比产出的多太多,通常是会话已经长到超出模型舒适区了 |
| tok/s(中,最大) | 0 – 500 | 解码速度。读数忽高忽低,就是后端不稳的信号 |
| Total Cost(右) | 0 – $12 | 估算花费 / 预算。指针到红区,公司破产 |
每个表盘都有真实刻度(不是百分比进度条)、绿黄红三段预警区、指针、盘内大号数字,盘下小字写细节(In/Out 拆分、会话均值、预算)。量程是我自己定的真实业务阈值——超了就是出事了,所以指针的位置本身就有意义。
另外:面板可拖拽、可缩放、可折叠,位置和尺寸会记住;标题栏那行 binding · 78f8f077 · out 297,179 是诊断信息,写着它正在读哪个会话、走的哪条解析路径。
装一下(两条命令)
dsh plugin --profile web add @stone100010/dsh-token-gauge
没有 pnpm,或者 dsh 不在 PATH(用 npx 启动的 DSH 很常见这种情况)的话,克隆下来用自带脚本:
git clone https://github.com/stone100010/dsh-token-gauge.git
cd dsh-token-gauge
./setup.sh install && ./restart-and-verify.sh
装完重启 dsh web,浏览器 Ctrl+Shift+R 硬刷一次。面板出现在右下角。
- GitHub:GitHub - stone100010/dsh-token-gauge: dsh-token-gauge · GitHub
- npm:https://www.npmjs.com/package/@stone100010/dsh-token-gauge
- MIT 协议,DSH
0.1.5-rc.2实测
几个技术点(同行可能感兴趣)
1. 数据是现成的,一行模型调用都不用加
我一开始以为要做个宿主插件去采集数据,翻了下 DSH 源码发现 @deepseek-ai/dsh-token-meter 已经把 tokenUsage / contextPressure / contextBreakdown 做成 session projection 推给每个客户端了。所以我这插件是纯只读视图:不产生会话事件、不注册工具、不影响模型,最坏情况就是面板渲染失败,少一个面板而已。
2. 挂载点是 shell.overlay
@deepseek-ai/dsh-client-ui-layout 声明了一个 frame 级、点击穿透的 list 座位。贡献进去是加法(新 id 和已有条目并存),而且穿透意味着面板之外的区域完全不挡下面的应用。
3. 客户端插件是运行时加载的,不用重建前端
@deepseek-ai/dsh-client-modules 在启动时扫描 Loader 条目,把每个声明了 dsh.client 的包打成 bundle 走 /plugins 给浏览器。所以只要装进 profile + 重启就行,不需要源码仓库、不需要 pnpm run build。
踩过的坑(血泪)
这几个都是我实际 debug 出来的,写出来给做同类插件的朋友省时间:
① 投影的「线上形状」和「宿主状态形状」不一样
tokenUsage 的 wire view 是 view: state => state.totals——客户端收到的是扁平的四个桶,我却按宿主侧的嵌套形状读了 usage.totals.uncachedInputTokens,结果全部落空、面板显示 0。
更坑的是:我的测试夹具用的也是嵌套形状,所以这个 bug 一路绿灯通过测试,直到我在真实界面上看到 0 / 227 / 0 才发现。教训:夹具必须用真实 wire 形状。
② useSyncExternalStore 会保留上一个快照
切换会话时,它会对新的 face 重新订阅,但仍持有旧快照直到新 face 推送——于是面板会短暂显示上一个会话的数字。修法是让派生的 subscribe/getSnapshot 以投影面的身份为键。
顺带一个决策:解析路径失败时我显示空值而不是冻结的旧数字。冻结的数字比空值更危险,因为它看起来是活的。
③ (26.95).toFixed(1) 在 JS 里是 "26.9"
浮点表示误差。面板上 In 26.9K 和总量 27.84K 对不上,看着像 bug。修法是四舍五入前加几个 ULP 的偏移。同类陷阱还有 1.005 → "1.00"。
④ 手写 ModuleLoader bundle 是可行的
客户端插件要求的其实是一个固定外壳:
window.__ModuleLoader__.load({
id: "包名", // 必须等于包名
factory: (require) => { /* 内部用 require() 声明外部依赖 */ }
});
所以这个包没有构建步骤、没有依赖,lib/client.js 是手写的。改完代码重启服务即可生效。
测试
node test/harness.mjs # 61 checks
harness 用忠实的替身跑真实 bundle(ModuleLoader 外壳、带 Object.is 依赖比较的 React hook 调度器、ctx.slots、ctx.sessions),覆盖注册、投影订阅、wire 形状归一化、会话切换、路由回退与重试、拖拽、缩放、边界夹取、持久化、销毁。
CI 跑这个,绿着的。
一个诚实的说明
右盘的金额是估算值。 deepseek-v4.1-flash 不在 DSH 内置的模型 catalog 里,DSH 不为它算成本,也没有成本投影可读。默认单价参考的是 catalog 里 deepseek 系列的条目,所以表盘上标了 est.。
建议你把自己的真实单价填进去(lib/client.js 顶部的 PRICES)——在 $12 这个量程下,单价准不准直接决定这个盘能不能信。
最后
如果你也在用 DSH,欢迎试试,有问题直接回帖或者去 GitHub 开 issue。量程(300M / 500 / $12)和预警区间都是常量,你的使用场景跟我不一样的话告诉我,我可以做成可配置的。
觉得有用的话给个 star
就挺好哈哈。
标签建议:#开源 #DSH #DeepSeek #AI编程 #工具分享
