中文 | English
DSH Cost Audit
一个 DeepSeek Harness 插件,只回答一个问题:
钱花到哪去了,照它说的做到底有没有用?
它把人民币花费放进 harness 本来就在用的两个统计位置 —— 每一轮下面和整个会话下面;同时算了一些
别处都算不到的东西,首先是上下文压缩那次摘要调用自己的账单(并且把你手动压的和系统自动压的分开算)。
在这之上才是让它成为「审计」而不是「看板」的部分:一个建议引擎,会指出到底是什么在花钱、给一个一键采纳的
动作,然后在你采纳之后重新测量那个指标 —— 于是「我照做了」变成一个数字,而不是一种感觉。
所有界面都用 harness 自己的统计形态渲染:同一个图标胶囊、同一个随锚点定位的 dt/dd 面板、同一套设计
token、同一套几何。官方胶囊一个像素都不动,这个插件只是把自己的加在旁边。
它加了什么
每轮 —— 助手动作行里的一个胶囊,位于复制按钮和分支按钮之间,紧挨官方的"消耗"和"耗时"胶囊。它显示
¥0.01,点开是:
| 行 | 含义 |
|---|
| Model | 这一轮实际走的模型路由 |
| 缓存命中 | 提示词输入里走缓存的比例,绝不四舍五入成假的 100% |
| 缓存读取 | 走缓存的输入 token(缓存输入) |
| 未命中输入 | 按未命中价计费的输入 token |
| 缓存写入 | 写入缓存的输入 token(非零时才显示) |
| 总输入 | 三个互不重叠的输入桶之和 |
| 输出 | 输出 token,含推理内容 |
| 本轮费用 | 人民币 |
| 耗时分布 | 本轮耗时去向 —— 见下 |
每会话 —— 一个与官方会话统计同一行的胶囊(1 turns 297 steps · 237 tok/s · 73.2M tok · Cache hit 99.7%),
就在它们右边:¥2.72 · today ¥0.41 · Account balance ¥113.45 —— 会话总额、其中今天花了多少、以及
实时账户余额。隔几天又捡起来的会话,今日花费从零开始算,而不是拿生命周期数字糊弄你。点开是整个持久日志
的同一套拆解,加上耗时分布,以及从 DeepSeek 账单接口实时读取的余额:
| 行 | 含义 |
|---|
| 会话总费用 | 人民币 |
| today | 今日 —— 这个会话在本地日历日上的花费 |
| 其中缓存重读 | 账单里那部分"同一份上下文又读了一遍" |
| 其中压缩摘要 | 摘要调用自己的账单 —— 没有任何别的显示会统计它 |
| 缓存命中 / 读取 / 未命中 / 写入 / 总输入 / 输出 | 整个会话的 token 桶 |
| 耗时分布 | 见下 |
| 账户余额 | 实时 |
| 赠送 / 充值 | 余额构成 |
耗时分布
两个面板的用量部分都以"时间到底去哪了"结尾,工具行按自己的名字排序:
| 行 | 含义 |
|---|
| 总耗时 | turn/start → turn/end |
| 模型用时 | step/start → assistant/message,带调用次数 |
| 首 token 平均(TTFT) | step/start → 第一个输出 token |
| 生成阶段 | 首 token → 结算,带 tok/s |
| 工具用时 | tool/call → tool/result,带调用次数 |
| 其他开销 | 上面两项没覆盖的墙钟时间 |
| 工具明细 | 最忙的 6 个工具,其余合并成一行 |
省 Token 建议
只在真的有话可说时才出现第四个胶囊 —— 健康的会话不为它付任何空间。点开是一串有数据支撑的建议,每条带
严重程度、一行修法、以及一个按会话记忆的"忽略":
| 代号 | 触发条件 | 一键做什么 |
|---|
context-reread | 缓存重读 ≥ 会话花费的 35%(且模型调用 ≥ 30 次) | 立即压缩本会话 —— 提交 /compact |
fragmented-tools | 某个工具调用 ≥ 30 次,其中 ≥ 60% 不到 2 秒 | 让 agent 把那一批合并成一个脚本 |
repeated-target | 同一个工具对同一目标调用 ≥ 4 次 | 让它读一次就记下结论,之后用 grep 定位 |
idle-grinding | 连续 ≥ 30 步没有 write / edit / 交付物 | 要一份进度汇报,而不是继续摸 |
tool-failures | 同一工具连续失败 3 次 | 让它停下来把错误读完 |
cache-hit-drop | 50 次以上调用里命中率低于 85% | 让它查是什么在每轮改请求头 |
compaction-churn | 系统自动压缩 ≥ 3 次,或那些摘要花掉 ≥ 会话 10% | ——(宿主配置项) |
model-retries | 模型重试 ≥ 5 次 | —— |
balance-low | 按当前烧钱速度,余额撑不过五个会话 | ——(充值) |
你自己压的那次不算"频繁"。 上面那条重读建议推荐的就是 /compact,所以把紧随其后的那次压缩也算进去,
会让顾问跟自己吵架:它让你压,压完又嫌你压得太勤。计数门槛一开始是 2 —— 意思是"一次系统自动 + 一次本面板
请求的"就已经算频繁了。现在只有 harness 自己决定的压缩会喂给这条规则(从摘要前面那个 command/run
归属出来),计数门槛提到 3,成本门槛只看它们那一份。这不是纸上谈兵 —— 这台机器上就有三个会话分别是
53 / 35 / 17 次自动压缩,手动触发零次。
这条建议的正文也不再引用任何 thresholdRatio 数值了。它曾经写着"从 0.8 降到 0.3",错了两次:这台机器的
preset 早就是 standard-half 的 0.5,而且插件本来就读不到宿主的压缩配置。现在它只讲取舍(阈值越低,
每次摘要要回放的历史越短、单次越便宜,但压得越勤),而"要改去哪改"交给那条手动说明。
金额一律渲染成两位小数 —— 万分之一元不是一个有人会据此行动的数字。唯一的例外是真花了钱却小到过不了
这个四舍五入的,显示 <¥0.01,而不是假装它免费。
采纳一条建议
每条可执行的建议都有一个按钮,通过输入框自己的动作面(setDraft + submit)提交进本会话 —— 走的是
发送按钮同一条路,所以消息会落在对话记录里,agent 在下一步就会读到(正在跑的轮次会排队作为"引导")。
没有诚实自动修法的建议会带上自己那一行说明该做什么 —— 要改哪个 agent preset 键、去控制台看什么 ——
因为原来那句通用的"这条需要你手动处理"没说出任何动作,读起来像耸肩。
两道护栏,都是刻意的:输入框里还有内容时按钮是禁用的,因为"执行"意味着写输入框,一次点击绝不能丢掉
人已经打的东西;每条建议发出后自己禁用。忽略是按会话记在这个浏览器里的。
采纳之后
被采纳的建议离开胶囊里的计数、但留在列表里,带一条会随会话继续更新的判定:
| 判定 | 含义 |
|---|
| 已采纳 · 有改善 | 指标往好的方向动了,超过它的门槛 |
| 已采纳 · 基本持平 | 动得比门槛小 —— 这次改变没被量出来 |
| 已采纳 · 反而变差 | 往反方向动了,超过门槛 |
| 已采纳 · 还在观察 | 采纳之后的样本还不够判;面板会告诉你还差多少 |
判定块从不只印一个状态。它总会说明实际执行了什么(已执行 /compact、已发出合并指令……),因为
单独一句"已采纳 · 还在观察"和"点了一下什么也没发生"完全没法区分 —— 而第一次上线时读起来正是后者。样本还
不够时它同时印出基线读数和还差多少证据;对 /compact 还会在摘要调用落地后补上这条命令自己的
花费(本次花费 ¥0.42)。最后这行需要点击那一刻就把压缩计数快照下来,所以更早的采纳记录没有它。
读数是自采纳以来的,不是会被历史冲淡的生命周期平均:浏览器在你点击的那一刻快照累计计数器,之后用新
读数相减。每条建议量的是它自己关心的那个指标 —— 每请求上下文 token、短调用占比、重复调用占比、工具失败
占比、缓存命中率 —— 而每个门槛都是基线的比例,这样 token 计数和比率能在同一把尺上判。这里没有任何地方
调用模型;它只是在胶囊已经读的那份折叠结果上做算术。
判定是一次测量,不是承诺:指标可能因为跟这条建议毫无关系的原因变好。请把"基本持平"当作诚实的默认值,
把数字当作证据。
每一条都是从持久日志里折叠出来的 —— 建议引擎从不调用模型,因为一个靠烧 token 来省 token 的功能是自相
矛盾的。门槛刻意保守,每条规则都要一个持续成立、而不是一次倒霉的模式:会喊狼来了的顾问很快就没人读了。
界面跟随 harness 的语言设置:zh 下简体中文,en 下英文。
全账号报表
会话级数字回答的是"这次对话花了多少",回答不了"我是不是比以前花得少了" —— 一个会话就是一件活,两件活
没法比。所以还有第二层读数:一个路由把所有会话的按日桶合并成一份日历,成本面板底部多出一段
「全账号 · 最近 7 天」。
分母是特意挑的,让建议没法给自己刷分:
| 行 | 为什么用它 |
|---|
| 每回合 | 分母是你自己发的消息数 —— 我们的建议改不动它,所以看趋势最公平 |
| 每产出编辑 | 每 write / edit / present 一次多少钱 —— 真正的"工作量"分母;纯聊天、纯调研的日子没有产出,显示 — |
| 每 1K 输出 token | 输入是输出的很多倍时它就高 —— 缓存和上下文的问题都在这里显形 |
| 重读 / 冷输入 / 输出 | 三项加起来才是总额;只看总数看不出"为什么动了" |
| 其中压缩摘要 | 是上面三项的子集,不是第四项 —— 摘要调用的 token 本来就是缓存重读或未命中输入 |
| 高峰占比 | 只报数字,不做建议 |
每一行都自带一句人话解释,中英双语。一个没人看得懂的分母比没有数字更糟:这个面板收到的第一个问题就是
「这几个里到底哪个才是工作量」。
三条边界会直接印在面板上,别处也请记住:
- 金额按配置里的列表价计算。
web/deepseek-search-llm-request 与 session/title-llm-request 这两个调用的日志里没有用量,
所以这是下界,不是精确值。
- 报表能显示花费变了,但不能证明是你采纳的建议带来的。它把分母选成建议改不动的量、把成本拆到可解释;
归因永远要你自己把采纳的时间点和趋势对齐。
因为整份表是从持久日志折叠出来的,历史会回填 —— 不用等一周才能看到一周(上限是最近 90 天)。
价格
金额用的是 DeepSeek 官方人民币列表价,单位:元 / 100 万 token
(api-docs.deepseek.com),按计费时段分开。
高峰是北京时间(UTC+8)周一至周五 09:00–12:00 与 14:00–18:00;其余时段以及整个周末都是半价。
| 模型族 | 缓存命中(空闲 / 高峰) | 缓存未命中(空闲 / 高峰) | 输出(空闲 / 高峰) |
|---|
deepseek-flash、deepseek-v4.1-flash、deepseek-v4-flash | 0.02 / 0.04 | 1 / 2 | 4 / 8 |
deepseek-v4-pro、deepseek-pro | 0.15 / 0.30 | 4.5 / 9 | 13.5 / 27 |
一个既不带 v4 也不带 deepseek 标记的模型名会被计数但永不计价 —— 面板给它的费用显示 —,并说明有
多少 token 未计价,而不是拿 DeepSeek 的价目表去给一个国外模型计费。
费用是估算:它由供应商回报的用量算出,不是从账单对回来的。但账户余额不是估算 —— 它是实时读取的,
所以拿它去校准估算。
配置
每个字段都是可选的;覆盖写进同 id 的 profile 补丁层。
- id: dsh-cost-audit
config:
baseUrl: "https://api.deepseek.com" # 账单接口的源
credentialRef: "DEEPSEEK_API_KEY" # 经 ctx.credentials 解析的引用
balanceCacheMs: 60000 # 一次余额读取复用多久
requestTimeoutMs: 8000 # 上游超时
pricing: # 元 / 100 万 token
flash:
peak: { cacheHit: 0.04, cacheMiss: 2, output: 8 }
off: { cacheHit: 0.02, cacheMiss: 1, output: 4 }
pro:
peak: { cacheHit: 0.30, cacheMiss: 9, output: 27 }
off: { cacheHit: 0.15, cacheMiss: 4.5, output: 13.5 }
安装
dsh plugin --profile web add github:Pingze-github/dsh-cost-audit
然后刷新浏览器页面以加载客户端 bundle。如果你是在自己改的 checkout 里,用实时链接装:
dsh plugin --profile web add link:/path/to/dsh-cost-audit
link: 让 checkout 保持实时,改 index.js / client.js 不需要重装。装了 dsh-hotswap 的话,它会根据写进
dsh.profile.bundles 的新条目热挂载 —— 不需要重启 dsh;这一点很重要,因为重启 dsh web 会杀掉正在
承载它的那个会话。
scripts/ 下的脚本是对着正在运行的部署验证的,不是对着夹具:check.sh 是离线闸门,smoke.sh 通过实时
路由扫过这台机器上的每一个会话,gui-probe.mjs 用无头 Chromium 渲染真实界面。smoke.sh 需要 DSH_HOME 和
一个带认证的 URL(它会从 /var/log/dsh-web.log 里读一个,或者用 DSH_STATS_URL)。
⚠️ 重命名一个 link: 安装的插件会留下一个卸不掉的开机条目,而新旧两个名字都指向同一个 client.js,浏览器
会把同一个 bundle 执行两次并报 duplicate factory registration,整个插件列表都会加载失败。补救办法是把
bundle 从 profile 清单里摘掉再加回去(每条条目会重新解析自己的路径),不是重启 dsh web。
它是怎么工作的
- 宿主半边(
index.js)注册一个会话投影 dshCostAudit,把整份持久日志折叠成"每轮"和"整会话"的计费桶
及其人民币费用。它走的是 harness 自己的 tokenUsage / sessionStats 同一条管线,所以客户端翻了多长的
历史,数字都是完整的。重试记账对齐 token-meter:一次助手结算替换它自己那个 (turn, step) 槽位,而
llm/retry-started 会先把槽位关掉,于是重试那次是相加。
- 宿主半边还注册两条 exact Connection Fetch 路由:
/api/dsh-cost-audit.balance 提供账户余额和任意会话的
按需折叠,/api/dsh-cost-audit.report 把所有会话的按日桶合并成一份日历。按需折叠存在的原因是:投影管线
只会在一个会话已经有物化单元之后才把它发给客户端 —— 持久投影检查点早于本插件的会话没有 dshCostAudit
那一行,而这条路由用同一个单元定义把这个缺口补上。报表路由要折叠一百多个会话,所以它缓存 60 秒。
- 客户端半边(
client.js)注册进 harness 的 conversation.chat.assistant-actions 与
conversation.composer.dock 两个插槽。它没有构建步骤:是一个手写的
window.__ModuleLoader__.load({ id, factory }) 形式的 bundle,所以这个包可以直接从 checkout 装。
- 按日花费按本地日历日折叠(宿主的时钟,也是浏览器的时钟),保留最近 90 天,于是一个跨了几个月还在
用的会话不会让检查点无限膨胀。每个日桶带三套互不混淆的口径:token 轴(重读 + 冷输入 + 输出)等于当日总额,
费率轴(高峰 + 非高峰)也等于当日总额,而压缩摘要是 token 轴的子集、不是并列的第四项。
- 会话行的位置是量出来的,不是写死的,并且两个胶囊整体保持居中。输入框 dock 是堆叠它的插槽条目的,而
官方统计行是一个本插件并不拥有的居中 flex 行,所以要量三件事:整行按官方行的高度上提、内容缩进到官方内容
结束的位置、官方行再向左平移本胶囊宽度的一半(用本插件设置和清除的
translateX,绝不改布局),这样两个才
读起来像一个居中的整体。官方标签变长、字号变了、窗口缩放,都会自动落在对的位置;当整组一行放不下时,本行
回退成自己的一行居中,官方行保持 harness 画的原样。
目录
index.js 宿主半边:dshCostAudit 投影 + balance / report 两条路由
index.d.ts 公开类型 + SessionProjectionMap 的模块增强
client.js 浏览器半边:两个插槽条目
cordis.patch.yml bundle 补丁(挂载宿主条目)
scripts/check.sh 本项目唯一的成功标准
scripts/check.mjs 宿主半边行为:计价、重试记账、路由、折叠
scripts/smoke.sh 一次调用做完运行时验证(闸门 + 全机会话不变式 + 可选渲染)
scripts/link-deps.sh 把 node_modules 指向正在运行的 harness,供 check.sh 用
scripts/gui-probe.mjs 真实界面的无头 Chromium 探针
授权
MIT。