SanChou0627/dsh-token-bill

dsh-token-bill

Live DeepSeek balance and per-hour token costing for DSH: two agent tools read the live balance and the ledger-observed daily charge, then write Markdown/HTML/JSON statements.

待分类dsh-token-monitor

dsh-token-bill

English | 中文

DeepSeek Harness(DSH)的实时 token 账单插件:实时账户余额、当日真实消费、按小时归集的 token 用量与金额柱状图,以及可归档的 Markdown / HTML / JSON 账单。

两个面向模型的工具,不需要任何界面,也不需要额外常驻进程:

  • token_status — 实时余额、今日账本实扣、今日 token 分桶。
  • token_bill — 按小时柱状图、峰谷拆分、分时段明细表,并写出账单文件。
📊 2026-09-28 token 账单
余额 ¥52.9100 · 今日消耗 ¥4.6900(账本实扣)
Token 合计 79.65M(输入 1.02M · 缓存命中 78.14M · 输出 492.7K) · 缓存命中率 98.1% · 716 次调用
最忙时段 15:00(29.00M tokens) · 最贵时段 15:00(¥2.3380)

按小时 token 消耗(6 个时段有用量)
00:00 │█████████▃                │ 9.89M
01:00 │███████▂                  │ 7.72M
11:00 │██▆                       │ 2.92M
12:00 │██████████████▇           │ 15.80M
13:00 │█████████████▄            │ 14.33M
15:00 │██████████████████████████│ 29.00M

按小时金额(已按账本实扣校准)
00:00 │██████▃                   │ 0.5607
01:00 │██▅                       │ 0.2259
...

为什么需要这个插件

DSH 有意把 token 数当作度量而不是账单:ctx.tokenMeter 只提供请求压力快照,整个 harness 里没有任何一处把 token 换算成钱,也完全不内置价目表。而你真想要的三个数字散落在三个互不相通的地方:

数据位置权威性
账户余额GET https://api.deepseek.com/user/balance(API key 鉴权)实时、权威
当日真实消费$DSH_HOME/.dshw-usage.json 余额记账本已观测、权威
各时段 token 用量$DSH_HOME/sessions/**/session.v*.jsonl.zstd服务商上报、精确

本插件用一条规则把它们对齐:钱以观测为准,token 以计数为准。 当日金额取自账户自身的余额变动,价目表只用来把这份已观测的总额分摊到各小时。

特性

  • 💰 实时余额,来自官方余额接口;网络失败时回落到账本的最后一次观测,并明确标注「非实时」。
  • 📊 按小时的真实 token 用量,读自持久化会话日志 —— 服务商上报的四个互不重叠的桶(inputTokens、cacheReadTokens、cacheWriteTokens、outputTokens),不是按字符数的启发式估算。
  • 🧾 落盘账单:自包含 HTML(内联 SVG 柱状图)、可归档的 Markdown、可脚本化处理的原始 JSON。
  • ⛰️ 峰谷感知:北京时间高峰时段、周末与法定节假日全天谷价,报告中给出峰谷拆分。
  • 🎯 精确金额运算:金额全程以整数 1e-8 单位承载,因此 sum(小时金额) === 当日金额 是恒等式,而不是四舍五入的巧合。
  • 🔒 只读:插件不写入你的账本、会话日志或凭据。

安装

# 已发布到 registry 后
dsh plugin --profile web add dsh-token-bill

# 从本地目录安装
dsh plugin --profile web add link:/绝对路径/dsh-token-bill

安装后,新会话即可让模型使用上面两个工具。若你的 profile 不会自动重载组合,请随后重启 DSH web 服务。

环境要求:Node ≥ 22.12(插件通过 require(esm) 解析 DSH 的 peer 依赖),以及挂载了 tools 服务的 DSH profile(任意标准 Web profile 均可)。

余额记账本

当日真实消费来自 $DSH_HOME/.dshw-usage.json。这个文件由优秀的 dsh-whale-widget 余额挂件写入,不是本插件写的。每个日期行记录期初余额、最后观测余额,以及累计的借(消费)与贷(充值):

{
  "openingUnits": 760000000,
  "lastUnits": 5525000000,
  "debitUnits": 388000000,
  "creditUnits": 5000000000,
  "firstAt": 1790529962822,
  "lastAt": 1790580387568
}

金额是整数 1e-8 元单位。已观测消费只统计余额下降,所以充值只累加到 creditUnits,永远不会虚增你的消费。如果该行同时能对上账(期初 − 消费 + 充值 = 期末),充值会作为说明信息给出;如果对不上,说明有账本无法归类的余额变动,插件会直接说明,而不是悄悄报一个错数字。

如果你没有装那个挂件,账本不存在,插件会干净地退化为按价目表估算并明确标注为估算 —— 余额仍然来自实时接口。

工具

token_status

参数类型默认说明
includeLedgerbooleantrue附上账本路径、更新新鲜度与事件条数。
includePricingbooleanfalse附上峰谷价目表。

返回 day、balance、currency、balanceSource(api 或 ledger)、balanceStale、todayObserved、todayTokenEstimate、totalTokens、inputTokens、cacheReadTokens、outputTokens、calls、ledgerPath、text。

token_bill

参数类型默认说明
daystring今天账期,YYYY-MM-DD,北京时间。
daysinteger1同时给出最近 N 天的按日汇总。
costModeledger | token | bothledger见金额口径。
writebooleantrue是否把账单写到磁盘。
formatshtml | md | json 数组["html","md"]写出哪些账单文件。

返回 day、amount、amountSource(ledger 或 token-estimate)、currency、balance、totalTokens、calls、activeHourCount、peakTokens、valleyTokens、files,以及完整的 text 文本账单。

账单写出到 $DSH_HOME/token-bill/,文件名为 token-bill-<day>.{html,md,json}。宿主进程的工作目录就是 DSH 安装目录本身,所以默认值刻意不取 process.cwd();可用 DSH_TOKEN_BILL_DIR 环境变量或配置项 outDir 覆盖。

数据来源

api.deepseek.com/user/balance ──► 实时余额 ──┐
                                             ├──► 报告 ──► 柱状图 + 账单
$DSH_HOME/.dshw-usage.json ─────► 已观测 ¥ ──┤
$DSH_HOME/sessions/**/*.zstd ───► 精确 token ─┘

余额 —— GET https://api.deepseek.com/user/balance,请求头 Authorization: Bearer <DEEPSEEK_API_KEY>,返回 balance_infos[],每币种含 total_balance / granted_balance / topped_up_balance。这不需要浏览器登录态。API key 依次从 DEEPSEEK_API_KEY(以及 DSH_DEEPSEEK_API_KEY、DEEPSEEK_KEY)环境变量,或 $DSH_HOME/.credentials.yaml 里的 refs.DEEPSEEK_API_KEY 读取。

token 用量 —— 每条持久化的 assistant/message 事件都同时带着适配器上报的 usage 和毫秒级 time,后者正是按小时归集得以成立的前提。四个桶严格按服务商口径互不重叠:

  • inputTokens —— 未命中缓存的输入(cache miss)
  • cacheReadTokens —— 命中缓存的输入(cache hit)
  • cacheWriteTokens —— 写入缓存的输入
  • outputTokens —— 输出,reasoning 已包含在内

会话日志位于 $DSH_HOME/sessions/<projectKey>/<sessionId>/,文件名 session.v<generation>.jsonl.zstd,首行是 header,之后每次追加写成一个独立的校验 Zstandard 帧。更早的未压缩 session.v<generation>.jsonl 同样可读。

金额口径

costMode 决定金额数字的来源:

模式当日金额小时金额token 估算 列
ledger(默认)账本观测到的实扣按 token 分摊并校准到该总额显示
token价目表估算价目表估算不适用
both账本观测到的实扣按 token 分摊并校准到该总额显示

ledger 模式下,小时金额采用按账单自身精度的最大余额法分摊,因此账单打印出来的各小时金额之和,永远等于它打印出来的当日总额。当账本当天没有观测时,插件退化为价目表估算并如实标注 —— 它绝不会把估算当作实扣呈现。

峰谷定价

北京时间。高峰(标准价)适用于周一至周五 09:00–12:00、14:00–18:00。其余所有小时,加上全部周末与法定节假日,按谷价(高峰价的一半)计费。价格为「元 / 百万 token」,下表为 谷价 / 峰价:

模型缓存命中缓存未命中输出
deepseek-flash、deepseek-v4-flash、deepseek-v4-flash-vision-exp、deepseek-chat0.02 / 0.041 / 24 / 8
deepseek-pro、deepseek-v4-pro、deepseek-reasoner0.15 / 0.34.5 / 913.5 / 27

无法识别的模型名会回落到 Flash 系列计价,这样新模型上线时仍能给出可用的估算。法定节假日表(HOLIDAYS_2026,34 天)在 lib/pricing.mjs,新一年国务院放假安排公布后可通过配置扩充。

配置

在 profile 的 cordis.patch.yml 里给插件那一行加 config:

- id: token-bill
  name: dsh-token-bill
  config:
    costMode: ledger        # ledger | token | both
    decimals: 4             # 打印金额的小数位
    chartWidth: 26          # 终端柱状图宽度
    historyDays: 1          # 按日汇总包含的天数
    writeFiles: true        # 设为 false 则永不写账单文件
    timeoutMs: 10000        # 实时余额请求超时
    outDir: !!js dshHomePath('token-bill')
    sessionsRoot: !!js dshHomePath('sessions')
    # 覆盖或扩充价目表(元 / 百万 token):
    # models:
    #   my-model: { hit: { off: 0.01, peak: 0.02 }, miss: { off: 1, peak: 2 }, out: { off: 4, peak: 8 } }
    # 追加法定节假日(全天谷价):
    # holidays: ['2027-01-01']
字段默认说明
dshHome$DSH_HOME 或 ~/.dshDSH 主目录。
profile$DSH_PROFILE 或 web同时检查哪个 profile 目录下的账本副本。
sessionsRoot<dshHome>/sessions会话日志根目录。
outDir$DSH_TOKEN_BILL_DIR 或 <dshHome>/token-bill账单输出目录。
decimals4打印金额的小数位。
chartWidth26终端柱状图宽度。
costModeledger金额来源,见上。
historyDays1按日汇总包含的天数。
maxBytes134217728(128 MB)超过此大小的单个会话日志会被跳过。
timeoutMs10000余额请求超时。
writeFilestrue是否允许写出账单文件。
models内置价目表价目表覆盖。
holidaysHOLIDAYS_2026全天按谷价计费的法定节假日日期。

架构

lib/index.js      插件入口:name / inject / Config / apply,注册两个工具
lib/service.mjs   编排:解析配置 → 采集 → 折叠 → 生成报告 → 落盘
lib/account.mjs   API key 解析、余额接口、实时到账本的降级策略
lib/ledger.mjs    只读读取 $DSH_HOME/.dshw-usage.json
lib/logs.mjs      会话枚举、多帧 zstd 解码、按小时/按日折叠
lib/pricing.mjs   峰谷时段判定与 DeepSeek 价目表
lib/money.mjs     精确十进制金额(整数 1e-8 单位)
lib/report.mjs    报告装配、终端柱状图、HTML 账单
lib/markdown.mjs  Markdown 账单
lib/chart.mjs     终端柱状图

几个值得说明的实现决定:

  • 金额全程不经过二进制浮点。 每个金额都是整数个 1e-8 单位。模型单价极小,且要在几千次调用上累加 —— 这恰恰是浮点漂移最容易出现的场景,也正是分摊恒等式能够成立的原因。
  • 会话日志是多帧 Zstandard。 Node 的 zstdDecompressSync 只解第一帧且不返回消耗字节数,直接读会把日志悄悄截断成一行 header。decodeZstdFrames() 按帧魔数定位并逐片验证解码;末尾被截断的那一帧直接结束扫描 —— 对于一份正在被写入的日志,这正是想要的行为。
  • 账本是读,不是轮询。 余额挂件已经在按定时器观测你的账户;复用它的记录可以避免两个写入方争抢同一个文件,而且只有余额显示会发起网络请求。
  • 只打开近期改动过的日志(按 mtime 过滤),一天的账单不会为整段历史付出解压代价。

隐私

全部数据留在本地。唯一的网络请求是向 api.deepseek.com 查询余额,用你自己的 API key 鉴权,由 DSH 宿主进程直接发出。账单写在 $DSH_HOME/token-bill/ 下。不会上传任何数据给插件作者,插件也不含任何遥测。

测试

node test/unit.mjs     # 21 项纯函数测试:金额精度、峰谷时段、zstd 多帧、账本行
node test/report.mjs   #  8 项报告层测试:分摊恒等式、精度网格、HTML 转义
node test/smoke.mjs    # 对真实账本 / 余额接口 / 会话日志跑端到端
node test/verify.mjs   # 走真实 execute(),校验输出 schema 与 HTML 结构

npm test 只跑三个不依赖外部数据的单测集(docs、unit、report)。smoke 与 verify 会读取你真实的 DSH 数据,并把账单写到包内的 .dsh-token-bill/(已 gitignore),因此需要一个可用的 DSH 主目录,且建议在源码检出处运行 —— test/ 不在发布包内(files 只发布 lib/、patch、两份 README 与许可证)。

已知边界

  • 账本依赖余额挂件。 没有它时,金额列变成价目表估算而非观测实扣;余额本身始终是实时的。
  • 会话日志是追加落盘的,最新一轮可能尚未写入。 token_status 的新鲜度取决于日志与账本,而非瞬时值。
  • 小时金额假设当天使用同一套价目表。 在服务商调价当天,按小时拆分是观测总额的比例分摊,不是逐笔对账。
  • 节假日表需要人工维护。 未列入的法定假日会按高峰价计费。
  • 目前只支持 DeepSeek。 余额来源与价目表都是 DeepSeek 专有的;接入其它提供方需要自带余额来源,并补一条 models 配置。

许可

MIT。