DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

361533652 /

361533652/dsh-token-bi

Verified

Token spend dashboard for DeepSeek Harness — daily columns, hourly rhythm, model/workspace splits, call tree, per-turn timeline.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@dceaf964

dsh-token-bi

DeepSeek Harness 的 token 消耗看板 —— 把散落在各个会话里的用量汇总成一张表:每天烧了多少、哪个模型最贵、哪些工作目录在消耗、父子会话各占几成、每一轮具体花在哪。

DSH 自带单会话的 token 统计,但没有跨会话的视图。这个插件补上这一层,并对齐 Host 自己的计量口径,不会出现"插件显示的数字和会话里对不上"。

中文 | English

看板总览


目录

  • 它解决什么问题
  • 安装
  • 界面说明
  • 数据从哪来、怎么算
  • HTTP 接口
  • 配置
  • 设计取舍
  • 已知限制
  • 开发
  • 许可

它解决什么问题

用 DSH 一段时间后,几个问题会冒出来:

  • 这个月一共花了多少 token?散在 20 多个会话里,没有一个地方能看总量。
  • 哪天突然变多了?是哪次任务把用量拉高的?
  • 缓存命中率怎么样?—— 这一项容易被忽略但影响最大:cacheRead 通常是总量的绝大部分,因为每轮都要把整个上下文重新读一遍。
  • 让子代理并行干活时,父会话和子会话各占多少?

这个插件把会话日志里 provider 上报的用量折叠成上述维度。


安装

前置条件

  • DSH Desktop 或 dsh CLI(Web 界面)
  • Node.js ≥ 22.15(会话日志是 zstd 压缩的,需要 zlib 的 zstd 支持)

从本地目录安装(当前可用方式)

git clone https://github.com/361533652/dsh-token-bi.git
dsh plugin --profile desktop add "link:/绝对路径/dsh-token-bi"

Windows 路径示例:

dsh plugin --profile desktop add "link:E:\gitbub-project\dsh-token-bi"

link: 表示软链安装,改动仓库里的代码无需重装。安装完成后完全退出并重启 DSH Desktop(不是刷新页面)——客户端 bundle 的路径表在进程启动时构建一次。

验证

重启后左侧栏面板列表里会出现「Token 账单」,位置紧挨着「插件」。

如果没出现,按顺序检查:

  1. 设置 → 插件 → 已安装插件 里是否有 Token 账单 且开关为开
  2. dsh --profile desktop --dump-config 输出里是否有 id: token-bi 这一行
  3. Host 日志(%APPDATA%\DSH Desktop\logs\host\)里搜 token-bi,看有无激活失败

插件在加载时会把 [token-bi] factory、[token-bi] apply、[token-bi] slot:xxx 写到浏览器控制台(用 console.error,因此会进 Host 日志)。这些标记能区分"代码没下载"、"下载了没激活"、"激活了没注册成功"。


界面说明

入口是左侧栏的全局面板(和「插件」「自动化任务」同级),不绑定某个会话——汇总数据天生属于全局层。

顶部数字

指标 含义
累计 token 当前时间窗口内的四类 token 总和
今日消耗 本地时区下今天的总和
会话 窗口内的会话数,副标题是其中正在运行的个数
轮次 去重后的 turn 数
缓存命中占比 cacheRead / 总量。这个数通常很高(80%+),说明上下文复用得多

图表

  • 每日消耗 —— 堆叠柱状图,四段分别是输入、缓存读、缓存写、输出。悬停看当天明细。
  • 最近 7 天节奏 —— 按小时聚合的折线,用来看"什么时候在烧 token"。
  • 模型分布 —— 按 provider/model 拆分,附带占比。
  • 工作区分布 —— 按会话的 cwd 分组,超过 8 个时折叠并显示被折叠部分的合计(避免让人误以为数据缺失)。
  • 会话明细 —— 主表。点任意一行展开该会话的逐轮时间线(每轮一条三段式条形)。
  • 调用树 —— 父会话下挂子会话,用深浅两种颜色区分"这个会话自己花的"和"它派生的子会话花的"。

时间窗口

右上角三档:7 天 / 30 天 / 全部。

切换会向 Host 重新取数并按日重算总量,不是只过滤已有数据——所以数字、图表、会话列表会一起变。

导出

右上角「导出 CSV」,导出当前窗口内的会话列表,带 UTF-8 BOM,Excel 直接打开不乱码。


数据从哪来、怎么算

数据源

两个来源,按会话 id 合并,活跃会话优先:

来源 读什么 作用
ctx.sessions 内存中的会话事件 当前打开的会话,永远最新
ctx.sessionQuery 冷启动的持久日志 历史会话,让没打开的会话也能统计

不需要读原始 .jsonl.zstd 文件——都走 DSH 的公开服务。

计量口径

这是本插件最需要讲清楚的部分。规则直接对齐 Host 的 tokenUsage 投影(@deepseek-ai/dsh-token-meter):

  1. 只看 provider 上报的用量,不估算。来源是 assistant/message 事件的 usage 字段,或 stream 里最后一个 type: 'usage' 的 chunk。
  2. 同一 (turn, step) 后到的样本替换先到的。一次重试会产生多个样本,这条规则保证重试的用量只计一次,不会重复累加。
  3. 四个桶分开记:inputTokens / outputTokens / cacheReadTokens / cacheWriteTokens,总量是四者之和。

第 2 条是关键。如果不做替换,失败重试的会话数字会虚高。校验方式:拿一个会话跑一遍折叠,四个桶的数字应当和会话界面里显示的完全一致。

时间归属

按事件时间戳落到本地时区的日期。浏览器把 getTimezoneOffset() 传给 Host,Host 用它给每条记录分日——所以跨时区使用、或跨零点运行的长任务,日期归属都是对的。

缓存

  • 单会话结果按 (会话 id, revision) 缓存,revision 是活跃会话的 seq 或持久会话的元数据;没变就复用。
  • 整个报告缓存 10 秒,refresh=1 强制重建。
  • 缓存键包含时间窗口,切换档位不会串数据。

HTTP 接口

插件在 Host 上注册一个前缀路由,浏览器用同源请求读数据。

GET /api/dsh-token-bi/report?offset=<分钟>&days=<窗口>&refresh=1
GET /api/dsh-token-bi/session?id=<会话 id>&offset=<分钟>
参数 说明
offset 浏览器时区偏移(分钟),默认取 Host 时区
days 窗口天数;0 或 all 表示全部
refresh 1 时跳过 10 秒缓存

report 返回:

{
  "generatedAt": 1792500000000,
  "offsetMinutes": 480,
  "rangeDays": 30,
  "cutoffDay": "2026-09-22",   // 窗口起点;全部时为省略
  "totals": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0, "total": 0,
              "sessions": 0, "live": 0, "turns": 0, "steps": 0 },
  "sessions": [ /* 每会话:id/title/cwd/parentSession/totals/models/days/... */ ],
  "models":   [ /* provider/model 汇总 */ ],
  "days":     [ /* 按日,含缺失日补零 */ ],
  "hours":    [ /* 最近 7 天按小时 */ ],
  "cwds":     [ /* 按工作目录 */ ],
  "errors":   [ /* 读失败的会话,不阻断整体 */ ]
}

session 返回单个会话的逐轮 rows(turn/step/time/input/output/cacheRead/cacheWrite/total),供展开行按需拉取——所以首屏不会因为历史很长而变慢。

这个路由沿用 DSH 插件惯用的 webServer 前缀注册。注意:路由在进程级只注册一次,重载时替换 handler 而非重复注册——重复注册会让 webServer 抛错并使整个插件激活失败(这是开发过程中踩过的真实坑,见 test/reload.mjs)。


配置

没有配置项。 所有参数(时间窗口、筛选)都在界面上切换,不需要改 profile 配置。

插件在 profile 里只有一行:

- insert:
    - id: token-bi
      name: dsh-token-bi

设计取舍

几个刻意的决定,写下来免得后来的人以为可以随手改:

汇总放在全局面板,不放在会话里。 跨会话的数据放进单会话的界面是位置错误,用户会以为那是"这个会话的统计"。

只读日志,不写任何东西。 插件不追加会话事件、不落自己的数据库。DSH 的会话日志是唯一事实来源,任何派生数据都应该是可重建的缓存。

对齐 Host 口径而非自创公式。 用户会拿插件数字和会话界面比对;两边不一致比数字不准更糟。

不做成本估算。 不同 provider、不同模型、不同缓存策略的单价差异极大,硬编码一张价格表会给出误导性的确定性。想知道钱花了多少,看 provider 的账单。

没有设置页。 没有任何可配置项,加一个设置页会多出一个什么都不做的入口。


已知限制

  • 只统计上报了用量的步。 如果某个 provider 不返回 usage,那一步对统计不可见。这不是漏算,是无从算起。
  • 压缩掉的历史仍然计入。 统计是"日志口径"而非"当前可见上下文口径"——被压缩的消息仍然算在会话总量里,这符合"这轮对话花了多少"的直觉,但和"当前上下文多大"是两件事。
  • 子会话独立计费、独立统计。 父会话的数字不包含子会话;调用树里两者分层显示,叠加才是整棵子树的花费。
  • 极长的会话会截断逐轮列表。 展开行最多显示最近 60 轮,避免长会话拖慢渲染;总量不受影响。
  • 数据只覆盖本机。 读的是本机 DSH 的会话存储;多机部署需要分别查看。
  • 需要 Web 界面。 依赖 webServer 服务,纯 TUI profile 下不会激活。

开发

目录结构

index.js              Host 半:注册 HTTP 路由
lib/collector.mjs     聚合:合并活跃/持久会话,产出报告
lib/usage.mjs         口径:把一份会话日志折叠成用量记录(纯函数,无 IO)
lib/client.js         浏览器半:面板注册 + 页面与图表
cordis.patch.yml      bundle patch:插入 Host 行
locale/{zh,en}.json   插件元信息(标题、描述、图标)
test/                 离线测试,见下

lib/usage.mjs 是纯函数模块,不碰文件系统和 Cordis,可以直接拿任意事件数组调用——口径逻辑都在这里,改动应当先补测试。

测试

npm test              # locale 一致性、重载竞态、槽位注册、页面渲染
npm run test:store    # 用本机会话存储跑一遍聚合(只读)
npm run test:route    # 用桩 context 驱动 Host 路由
测试 覆盖什么 需要什么
locale-check.cjs 中英文字典键集一致、t() 用到的键都有定义 无
reload.mjs 热重载时不重复注册路由(回归:曾经导致插件消失) 无
slots.mjs 对真实 SlotCore 跑注册,且不再注册设置页 DSH_APP_DIR
render.mjs 用最小 hooks 运行时把页面真渲染一遍,含展开行交互 本机会话存储
harness.mjs 聚合结果的人眼核对输出 本机会话存储
route.mjs 路由的 200/400/404 与 JSON 契约 本机会话存储

测试默认读 $DSH_HOME/sessions(默认 ~/.dsh/sessions),可用 DSH_SESSIONS 或命令行参数指向别处;没有历史数据时会建空报告而不是报错。

slots.mjs 需要 DSH 自带的 @deepseek-ai/dsh-client-ui-slots:

DSH_APP_DIR="E:\dsh\DSH Desktop\resources\app" npm test

找不到时该测试会打印 SKIP 并以 0 退出,不会假装通过。

改动时的注意事项

  • lib/client.js 不能 require DSH 的客户端包(如 ui-primitives)。样式和控件要么自己写,要么照抄 Host 的实现后改成自己的前缀类名,只引用 --dsw-alias-* 主题变量。
  • 样式必须走主题 token,否则深色模式下会瞎。
  • dsh.client.inject 填的是包名(如 @deepseek-ai/dsh-client-ui-renderer),不是运行时服务名(slots)——写错会导致模块一直停在 loading 并把整个页面拖成 "Failed to load plugins"。
  • 客户端入口放在 lib/client.js,与生态里其他插件一致。

许可

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit dceaf964a774

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout