Token Cost & Balance (dsh-token-cost)
在 Harness Web UI 的每一轮已完成对话下方显示本轮 token 用量、费用、会话累计和当前余额。数据按“会话 + 轮次”持久化到工作区内的 JSON 账本,后续对话不会覆盖旧记录;刷新页面、重开 Harness 或重新打开历史会话后仍可还原。
功能
- 每轮用量条:显示本轮总 tokens、费用、本会话累计和当前余额;可展开查看输入、输出、缓存读取、缓存写入、推理 tokens。
- 精确到模型路由:按每条 Assistant 消息的
provider/model选择单价;同轮混用多个模型时分别计价并汇总。每次 attempt 按其事件时间判断高峰/空闲价,跨时段也能正确拆分。 - 余额三种来源:
account:通过 Host 的deepseekAccount.getBalance()读取 DeepSeek 账户余额,含充值钱包与赠送钱包;budget:配置初始预算,显示“初始预算 − 工作区账本累计消费”;manual:显示固定余额。auto会依次尝试 account → budget → manual。
- 持久化:默认写入
<工作区>/.dsh-token-cost/ledger.json,按 Session 和 Turn 去重合并;插件重载时会回填当前存活会话中已完成的轮次。 - 完整配置:单价、按模型覆盖单价、货币、显示密度、小数位、余额来源、刷新周期、账本目录/文件名、是否记录子代理等都可配置。
- 双语:内置简体中文与英文界面;数字、时间和费用按当前 Harness 语言格式化。
- 安全路由:Host 只暴露一个摘要路由,并复用 Harness 的
connection.requestRejection鉴权;没有 connection 服务时仅允许回环地址与同源请求。
界面位置
注册在 conversation.chat.turnTail 槽位,order = 30,位于内置交付文件卡片、计划卡片之后、完成轮次操作行之前。条目 id 为 token-cost,不会替换任何内置条目。
展开后可以看到:
- 本轮 token 分项与请求数、模型;
- 本轮费用、会话累计、工作区账本累计;
- 每个模型路由的 tokens、费用与每百万 token 单价;
- 余额来源、钱包明细、更新时间;
- 账本文件绝对路径和累计已保存轮数。
数据文件
默认:
<session workspace>/.dsh-token-cost/ledger.json
结构(节选):
{
"version": 1,
"updatedAt": 1760000000000,
"sessions": {
"<sessionId>": {
"updatedAt": 1760000000000,
"turns": {
"3": {
"turn": 3,
"status": "completed",
"inputTokens": 12000,
"outputTokens": 800,
"cacheReadTokens": 9000,
"cacheWriteTokens": 0,
"reasoningTokens": 200,
"totalTokens": 21800,
"cost": 0.0216,
"currency": "CNY",
"routes": [],
"incomplete": false,
"recordedAt": 1760000000000
}
}
}
}
}
- 账本使用“临时文件 + rename”原子替换;写入按文件串行化。
- 如果已有文件损坏,会先改名为
ledger.json.corrupt-<timestamp>再重建,避免静默丢数据。 storage.dir只会落在工作区内;配置成绝对路径或..越界时回退到.dsh-token-cost。- 费用是写入时的快照;之后修改单价不会重写历史账本,符合“账本”语义。界面上的本轮/会话费用会按当前配置实时重算,便于调价后对比。
配置
在 profile 的 cordis.patch.yml 中覆盖 token-cost 行,或在插件配置页中编辑。所有字段都有默认值。
- id: token-cost
name: 'dsh-token-cost'
config:
enabled: true
currency: CNY
currencySymbol: '¥'
pricing:
inputPerMillion: 1
outputPerMillion: 4
cacheReadPerMillion: 0.02
cacheWritePerMillion: 1
peak:
inputPerMillion: 2
outputPerMillion: 8
cacheReadPerMillion: 0.04
cacheWritePerMillion: 2
offPeak:
inputPerMillion: 1
outputPerMillion: 4
cacheReadPerMillion: 0.02
cacheWritePerMillion: 1
pricingSchedule:
enabled: true
timeZone: Asia/Shanghai
peakWindows:
- { start: '09:00', end: '12:00' }
- { start: '14:00', end: '18:00' }
weekdays: [1, 2, 3, 4, 5]
holidays: []
modelPricing:
deepseek-account/deepseek-v4-pro:
inputPerMillion: 4.5
outputPerMillion: 13.5
cacheReadPerMillion: 0.15
cacheWritePerMillion: 4.5
peak:
inputPerMillion: 9
outputPerMillion: 27
cacheReadPerMillion: 0.3
cacheWritePerMillion: 9
'openai/*':
inputPerMillion: 2.5
outputPerMillion: 10
display:
showInput: true
showOutput: true
showCache: true
showCost: true
showSessionCost: true
showBalance: true
showDetails: true
compact: false
decimals: 4
balance:
mode: auto
refreshSeconds: 60
timeoutMs: 5000
locale: zh
budgetInitial: 100
manualAmount: null
showBonus: true
storage:
enabled: true
dir: .dsh-token-cost
file: ledger.json
backfill: true
includeSubagents: true
字段说明
| 字段 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关;关闭后不注册投影、路由和账本。 |
currency |
CNY |
费用与预算使用的币种;账户余额钱包保留自己的币种。 |
currencySymbol |
空 | 自定义符号;空值按币种推导(CNY/JPY ¥、USD $ 等)。 |
pricing.inputPerMillion |
1 |
空闲时段未缓存输入,每百万 tokens 单价。 |
pricing.outputPerMillion |
4 |
空闲时段输出(含推理)每百万 tokens 单价。 |
pricing.cacheReadPerMillion |
0.02 |
空闲时段缓存命中读取每百万 tokens 单价。 |
pricing.cacheWritePerMillion |
1 |
空闲时段缓存写入每百万 tokens 单价(DeepSeek 未单列写入价时按未命中输入价)。 |
pricing.peak |
见上 | 高峰时段单价表;字段同平级四项,未填字段继承 pricing 平级值。 |
pricing.offPeak |
见上 | 空闲时段单价表;字段同平级四项。 |
pricingSchedule.enabled |
true |
是否按时间窗口选择 peak/offPeak。关闭后只使用 pricing 平级值。 |
pricingSchedule.timeZone |
Asia/Shanghai |
高峰窗口所在 IANA 时区。 |
pricingSchedule.peakWindows |
09:00–12:00、14:00–18:00 |
高峰窗口,含起点不含终点。 |
pricingSchedule.weekdays |
[1,2,3,4,5] |
可能为高峰的 ISO 星期(1=周一,7=周日)。 |
pricingSchedule.holidays |
[] |
YYYY-MM-DD 节假日,全日按空闲价。 |
modelPricing |
{} |
按键覆盖单价。匹配顺序:provider/model → provider::model → provider/* → model → * → 默认。每项可含平级字段、peak、offPeak;未提供的子字段继承该模型的平级值。 |
display.showInput |
true |
明细中显示未缓存输入。 |
display.showOutput |
true |
明细中显示输出。 |
display.showCache |
true |
明细中显示缓存读取/写入。 |
display.showCost |
true |
收起状态显示费用。 |
display.showSessionCost |
true |
收起状态显示会话累计。 |
display.showBalance |
true |
收起状态显示余额。 |
display.showDetails |
true |
允许展开明细。 |
display.compact |
false |
使用更紧凑的间距。 |
display.decimals |
4 |
费用小数位(0–8)。 |
balance.mode |
auto |
auto / account / budget / manual / off。 |
balance.refreshSeconds |
60 |
账户余额缓存时间;0 表示每次请求都重新读取。前端最短 15 秒刷新。 |
balance.timeoutMs |
5000 |
单次账户余额读取超时。 |
balance.locale |
zh |
传给账户 API 的语言元数据。 |
balance.budgetInitial |
空 | 预算模式初始值;auto 下作为账户不可用时的回退。 |
balance.manualAmount |
空 | 手动余额;auto 下作为最后回退。 |
balance.showBonus |
true |
是否显示赠送钱包。 |
storage.enabled |
true |
是否写工作区账本。 |
storage.dir |
.dsh-token-cost |
工作区相对目录。 |
storage.file |
ledger.json |
账本文件名(只取 basename)。 |
storage.backfill |
true |
插件激活时回填当前存活会话的已完成轮次。 |
storage.includeSubagents |
true |
是否把子代理会话写入账本。子代理仍会显示自己的用量条。 |
pricing.peak/pricing.offPeak是可选对象。省略整个 profile 时:若平级单价仍是默认值,peak 使用官方高峰价、offPeak 使用官方空闲价;若平级单价已自定义,则二者继承平级值。显式字段优先。这样自定义固定单价时不会意外套用 DeepSeek 高峰价。
修改
cordis.patch.yml后由 Loader 热重载;如果浏览器没有立即更新,刷新页面即可。修改客户端 UI 代码需要重启 Host 或重新加载插件。
计价口径
每条 Assistant 使用的 token 口径与 Harness 内置 token-meter 一致:
inputTokens:未缓存输入 tokens;cacheReadTokens:缓存命中读取;cacheWriteTokens:缓存写入;outputTokens:输出 tokens(reasoningTokens是其中的子集,不额外重复计费)。
每个 usage 样本记录在其 assistant/message / assistant/attempt 事件时间上;计价时用该时间判断高峰/空闲,再用对应单价:
window = isPeak(event.time, pricingSchedule) ? peak : offPeak
cost(attempt) =
uncachedInput / 1e6 * window.inputRate
+ output / 1e6 * window.outputRate
+ cacheRead / 1e6 * window.cacheReadRate
+ cacheWrite / 1e6 * window.cacheWriteRate
turn.cost = Σ cost(attempt)
默认单价取自 DeepSeek 开放平台 2026-10-24 的公开价(deepseek-flash:空闲输入 1 元 / 输出 4 元 / 缓存命中 0.02 元,高峰翻倍;每百万 tokens,人民币)官方价格页。DeepSeek 的高峰时段为北京时间周一至周五(不含法定节假日)09:00–12:00、14:00–18:00;pricingSchedule.holidays 可补充当年法定节假日。价格会调整,部署时请以官方页面为准并覆盖配置;deepseek-v4-pro 等其它模型请在 modelPricing 中配置。
pricingSchedule.enabled: false时忽略 peak/offPeak,只使用pricing平级值;适用于固定单价或其它供应商。- 同一轮内跨高峰/空闲,或跨多个模型时,费用会按每次 attempt 分别计算,详情按“模型 + 价格窗口”拆行。
- 重试(
llm/retry-started)会结束当前替换槽,重试尝试的用量会再次累加,避免只取最后一次而漏计费。若某条 Assistant 没有上报 usage,界面会标“部分未上报”,费用显示为下限(≥)。余额与费用都只用于展示,不参与 Harness 的上下文或压缩决策。
工作原理
Session event log
│
▼
tokenCost 投影(Host)── 每轮 attempts / tokens / cost / session totals
│ │
│ └── turn/end → ledger.json(工作区)
▼
Web Client turnTail 条目 ── 订阅投影,显示每轮条
│
└── GET /api/token-cost/summary?sessionId=… → 余额 + 账本摘要
- 投影:纯同步 fold,状态是 plain JSON;无关注事件返回同一引用,避免无谓刷新。状态被 Harness 的 projection cache 检查点化。
- 客户端:不自己解析 Session 日志;通过
session.projections.faceOf('tokenCost')读取 Host 计算好的视图。 - 余额路由:只允许 GET;会话必须仍存活于 Host;返回
Cache-Control: no-store。账户余额在 Host 进程内缓存,避免多个浏览器标签重复打 Platform。 - 持久化:投影是派生数据;账本是独立的工作区文件。即使投影缓存被清空,Session 日志仍可在下次打开时重新折叠出每轮数据。
安装
前置:DSH(DeepSeek Harness)本体、Node ≥ 22。
Web(dsh web)
dsh plugin --profile web add github:BigFatFish4Wm/dsh-token-cost
装完重启 dsh web,再按 Ctrl+F5 刷新浏览器。
克隆到本地后也可以用本地目录安装:
dsh plugin --profile web add link:<本目录绝对路径>
桌面端(官方 Electron 客户端)
dsh plugin 按设计不碰 desktop profile。在客户端的插件市场里,来源填:
https://github.com/BigFatFish4Wm/dsh-token-cost
装完若界面里没有出现,完全退出客户端(含托盘)再打开。
安装后在插件页启用(若未自动启用),或在 profile 的 cordis.patch.yml 中保留 token-cost 行。替换已安装包通常需要重启 Host 以加载新的 Host JavaScript。
故障排查
- 条形条没有出现:确认
conversation.chat.turnTail槽位有token-cost占用者;确认本轮至少有一条 Assistant 消息或未上报标记。空白会话不显示。 - 余额显示“不可用”:账户未登录、
deepseekAccount未组合、Platform 请求失败,或balance.mode配置为account且没有可用钱包。切换到auto并配置budgetInitial/manualAmount作为回退。 - 余额没有更新:前端最短 15 秒刷新一次;点开明细后点“刷新”可立即重读。Host 端还会按
refreshSeconds缓存。 - 账本没有写入:检查
storage.enabled、工作区是否只读、storage.dir是否可写;展开明细查看ledger.error。如果账本损坏,会在同目录留下.corrupt-*副本。 - 费用不对:确认
currency与pricing一致;用modelPricing覆盖具体模型。缓存读取/写入的单价是独立字段,不是“输入折扣”。若使用了高峰/空闲价,检查pricingSchedule.timeZone、peakWindows、weekdays和holidays;当前 attempt 事件时间是否落在高峰窗口决定单价。关闭pricingSchedule.enabled可退回固定单价。
验证状态
- Host fold / pricing / ledger 有独立单元测试:
node test/core.test.mjs。 - Client 组件有零依赖的渲染探针(mock React + mock Host 报告):
node test/client-render.test.mjs。它覆盖收起态、展开态、余额钱包、账本路径和 Turn 定位。 - 插件 JS 语法、JSON、patch YAML、manifest 和 SVG 在安装前校验。
- 已安装到当前 profile 并激活;Cordis Inspect 确认
tokenCost投影所在 Host 插件行 active、conversation.chat.turnTail中token-cost占用者 active。 - 已从真实会话日志回填并持续写入
.dsh-token-cost/ledger.json;跨轮次数据保留。 - 余额摘要路由通过 Host mock 测试覆盖 200/403/账本/账户钱包;真实路由受 Harness 的 HttpOnly 会话 cookie 保护,外部
Invoke-WebRequest正确返回 401。视觉渲染(不同主题、窄屏、真实浏览器控制台)仍以当前环境的浏览器控制能力为准;没有浏览器控制时只能给出注册级与组件级验证,不能声称已截图验证。
No comments yet. Be the first to write one.