Bundle
dsh-cost-audit
DSH Cost Audit — per-turn and per-session token/CNY cost pills for the DeepSeek Harness Web GUI, including the compaction bill nothing else counts, plus a one-click advisor that re-measures whether its own advice actually saved money.
- Source
- Pingze-github
- License
- MIT
- Updated
- Updated 2 hours ago
Readme
**中文** | [English](README.en.md)
# DSH Cost Audit
一个 [DeepSeek Harness](https://github.com/deepseek-ai/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 天」**。
分母只留两个,都在**产出侧** —— 不拿 agent 自己的流程当工作量:
| 行 | 为什么用它 |
| --- | --- |
| 每产出编辑 | 每 write / edit / present 一次多少钱 —— 交付物的分母;纯聊天、纯调研的日子没有产出,显示 — |
| 每 1K 回答 token | 产出 1000 token 回答要付多少(不含思考)—— 输入是回答的很多倍时它就高,缓存和上下文的问题都在这里显形 |
「每步」和「每回合」已经删掉:**步数是 agent 自己的过程**(把零碎调用合并成一个脚本会让它合理地上升),
**回合数是你的习惯**(说一句「你好」和一整个大任务都算一回合)—— 两个都不是工作量。四个读数里有两个会误导,
不如只留两个对的。曲线也只画这两条。
| 重读 / 冷输入 / 输出 | 三项加起来才是总额;只看总数看不出"为什么动了" |
| 其中压缩摘要 | **是上面三项的子集,不是第四项** —— 摘要调用的 token 本来就是缓存重读或未命中输入 |
| 高峰占比 | 只报数字,不做建议 |
每一行都自带一句人话解释,中英双语。一个没人看得懂的分母比没有数字更糟:这个面板收到的第一个问题就是
「这几个里到底哪个才是工作量」。
三条边界会直接印在面板上,别处也请记住:
1. 金额按**配置里的列表价**计算。
2. `web/deepseek-search-llm-request` 与 `session/title-llm-request` 这两个调用的日志里**没有用量**,
所以这是**下界**,不是精确值。
3. 报表能显示花费变了,**但不能证明是你采纳的建议带来的**。它把分母选成建议改不动的量、把成本拆到可解释;
归因永远要你自己把采纳的时间点和趋势对齐。
因为整份表是从持久日志折叠出来的,**历史会回填** —— 不用等一周才能看到一周(上限是最近 90 天)。
## 细粒度视图
日粒度回答不了"我 11 点改的那个设置有没有用" —— 一天里混着好几种策略,而一个会话和下一个会话干的根本不是
同一件活。所以账单卡片里还有**第三档「细粒度」**,把同一批事件按**分钟/小时**折开:
- **一行一个量度**(¥ / 次、命中 tok / 次、冷输入 tok / 次、输出 tok / 次),全都除以**调用次数** —— 桶里的工作
量不一样,画总量只会跟着工作量涨。
- **窗口即粒度**:2 小时=1 分钟桶、24 小时=5 分钟桶、7 天=1 小时桶。要两个开关才能表达一件事,就只做一个。
- **标记**:改思考档位、换模型、起子 agent、`/compact`、压缩摘要、切 preset / sandbox / approval,都画成竖虚线,
最近几条列在下面(带时刻)。
- **两个能控制的变量**做成开关:**上下文档位**(<100K / 100–200K / 200–350K / ≥350K)和**并发**(只有本会话 /
有并行)。曲线只有在**同一档位内**比较才说明策略优劣 —— 否则你量到的是上下文变重,不是策略变差。
边界写清楚:任务类型(写代码 / 调研 / 聊天)**不是任何人能定的变量**,所以这里不提供跨任务类型的对比;标记全部
从日志推出,因此**在会话外改配置**(编辑 preset、改 `settings.yaml`)不会留痕,只会表现为"下一个会话以不同档位
开始"。某个会话折不动时会在卡片上写出来,不会假装序列是完整的。
## 价格
金额用的是 **DeepSeek 官方人民币列表价,单位:元 / 100 万 token**
([api-docs.deepseek.com](https://api-docs.deepseek.com/zh-cn/quick_start/pricing)),按计费时段分开。
高峰是北京时间(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 补丁层。
```yaml
- 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 }
```
## 安装
```bash
dsh plugin --profile web add github:Pingze-github/dsh-cost-audit
```
然后**刷新浏览器页面**以加载客户端 bundle。如果你是在自己改的 checkout 里,用实时链接装:
```bash
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` 把所有会话的按日桶合并成一份日历,`/api/dsh-cost-audit.fine` 把同一批
事件按分钟/小时折成**每次调用的均值**并标出策略改动的时刻(改档位、换模型、起子 agent、压缩、切 preset / sandbox)。按需折叠存在的原因是:投影管线
只会在一个会话已经有物化单元之后才把它发给客户端 —— 持久投影检查点早于本插件的会话没有 `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 / fine 三条路由
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。
Install
dsh plugin --profile web add github:Pingze-github/dsh-cost-audit
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-cost-audit from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.