🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Token 账本 u-fw/dsh-token-ledger

DeepSeek Harness
u-fw27 天前有提交已提供中文文档扫描:未发现敏感能力

按项目汇总所有会话的计费 token 用量

30/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/4 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add u-fw/dsh-token-ledger
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · model
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 27 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 按项目汇总token用量
  • 统计服务商计费token

跨项目 token 账本,给 DeepSeek Harness 汇总每个项目由服务商计费的 token 用量

README

由 DeepSeek 最新模型翻译生成
dsh-token-ledger — DSH 跨项目 Token 统计 一个 DSH 插件:统计这个 DSH 安装里所有项目累计消耗的 token,并在设置页里以图形界面呈现。 打开方式:侧边栏 → 设置 → 「Token 统计」(位于 Models / Plugins / Agent Presets 之后)。 运行要求 这是一个 DSH profile bundle,不是独立程序。它被安装进某个 DSH profile(通过 plugin_manager 或 dsh plugin 命令),读取的是那个安装的会话语料——属于它被载入的那个 profile 的持久化会话。面板上每个数字都是按需从该语料推导出来的,插件自己没有别的数据存储。 宿主半边会注册一个 HTTP 路由(GET /token-ledger/summary),面板是 Web 客户端,所以 DSH 的 Web 服务必须在运行,否则界面不会出现。包内声明了 "engines": { "node": ">=22.15" }——宿主半边只用内置模块,语料相关测试另需 Node 的 zstd 解码能力(22.15 起提供)。
展开完整文档(README 剩余 1 节)
dsh-token-ledger — DSH 跨项目 Token 统计

一个 DSH 插件:统计这个 DSH 安装里所有项目累计消耗的 token,并在设置页里以图形界面呈现。

打开方式:侧边栏 → 设置 → 「Token 统计」(位于 Models / Plugins / Agent Presets 之后)。

运行要求

这是一个 DSH profile bundle,不是独立程序。它被安装进某个 DSH profile(通过 plugin_manager 或 dsh plugin 命令),读取的是那个安装的会话语料——属于它被载入的那个 profile 的持久化会话。面板上每个数字都是按需从该语料推导出来的,插件自己没有别的数据存储。

宿主半边会注册一个 HTTP 路由(GET /token-ledger/summary),面板是 Web 客户端,所以 DSH 的 Web 服务必须在运行,否则界面不会出现。包内声明了 "engines": { "node": ">=22.15" }——宿主半边只用内置模块,语料相关测试另需 Node 的 zstd 解码能力(22.15 起提供)。

它统计什么

DSH 里每个会话(Session)都有一条 tokenUsage 投影,记录四类计费桶:

| 字段 | 含义 |
| uncachedInputTokens | 未命中缓存的输入 token |
| cacheReadTokens | 命中缓存读取的输入 token |
| cacheWriteTokens | 写入缓存的 token |
| outputTokens | 模型生成的 token |

这些数字来自 provider 每次调用的 usage 回报,是真实计费口径,不是启发式估算(启发式估算只用于上下文占用显示)。

本插件按会话的工作目录(header.cwd)把全部会话分组到项目,再给出总数。

表格各列

| 列 | 含义 |
| 会话 | 该项目的会话总数 |
| 子代理 | 其中由子代理(header.origin === 'subagent')产生的会话数。其中多数是全新上下文,不会重复计入任何别处的 token |
| 继承历史 | 其中「日志开头复制了父会话历史」的会话数。这一列才是重复量的来源 |
| 输入 | 未命中缓存 + 缓存读取 + 缓存写入——三者都是计费的输入侧 |
| 输出 | outputTokens——模型生成的 token |
| 消耗 token | 去重后的 token(快速模式下表头为「记录 token」,显示未去重值) |
| 缓存率 | 缓存读取 ÷ 输入——与 DSH 聊天统计里的「缓存命中」同一口径,且不会把部分命中显示成 100% |
| 占比 | 相对该项目里最大值的条形比例 |

为什么是「继承历史」而不是「子代理」:两者不是一回事。示例语料里 105 个子代理会话只有 40 个带继承前缀;反过来,手动 fork 一个对话也会计入「继承历史」但它不是子代理。这一列量的始终是「日志是否以别人的历史开头」。

判定依据是 header 的 isSeeded——它是会话自己对该事实的声明,两条读取路径都免费可得;而精确的 inheritedEventCount 只有读日志才知道(去重本身仍然使用这个实测值,不用 isSeeded 代替)。

两种口径(面板上可切换)

面板顶部有两个按钮:

精确(默认)

对每个会话只累计它自己的事件(seq >= inheritedEventCount),跳过从父会话继承来的日志前缀。

为什么必须这样做:DSH 的 fork / 子代理会话会复制父会话的历史,而 tokenUsage 投影是对整条日志(含继承前缀)折叠的。所以一个 fork 会话自己记录的 token 数已经包含了父会话那部分。把 120+ 个会话的记录直接相加,会把同一批计费调用重复计算。

示例语料(约 120 个会话,其中约三分之一是继承式会话):

直接相加 30.0 亿 token ← 虚高约 70% 去重后 17.5 亿 token ← 每笔计费调用只算一次

代价:需要读取每个会话的日志(zstd 压缩,解压后约为压缩比的 4 倍)。首次统计在示例规模下约 3 秒;此后每个会话按 sessionPersistence 的 revision 做增量复用——只有日志变过的会话才会被重读,所以再次统计通常在一百多毫秒,无需等待 TTL。点「重新统计」会强制清空缓存、全量重读。

快速

直接读取持久化投影缓存(ctx.sessionProjectionCache),不读任何日志,约 60 毫秒返回。

代价:拿不到 inheritedEventCount 的分界,因此无法去重,也没有模型路由归属,数字会偏高。面板会明确标注。

去重口径是可信的

插件里的折叠逻辑是 @deepseek-ai/dsh-token-meter 中 tokenUsage 投影的镜像。自检结果:

- 65 / 65 个无继承前缀的会话,自算结果与 DSH 自己持久化的投影值完全一致(逐字节相同)。
- 40 / 40 个 fork 会话,自算值 ≤ 其记录值。
- 面板底部会显示 foldMismatches;非 0 通常意味着某个会话正在写入。

两个入口

| 入口 | 位置 | 说明 |
| 侧边栏图标 | 侧边栏顶部全局面板行(Plugins 下面) | 点击把中间列切到统计页;页面左上角有「返回对话」 |
| 设置页 | 设置 → Token 统计 | 与侧边栏面板是同一个组件,只是宿主容器不同 |

侧边栏入口用的是 DSH 官方两个插件(Plugins、Task manager)同款机制:在 sidebar.panellist 注册一个图标行,再在 main 槽注册同 id 的 key。行按钮、标签、tooltip、点击行为都由侧边栏自己拥有,插件只提供图标字形和面板内容——所以不要自己包一层 span,那会破坏该行的图标基线。

「返回对话」调用 ctx.layout.selectPanel(null),并且是用 ctx.get('layout') 可选读取、而不是写进 inject:声明的服务若缺失会让 fiber 停在 pending,而客户端插件 pending 或 apply 抛错都会让整个页面启动失败,一个返回按钮不值得冒这个风险。

页面上的四个视图

| 控件 | 作用 |
| 精确 / 快速 | 切换统计口径(见上) |
| 全部时间 / 近 30 天 / 近 7 天 | 按会话创建时间筛选;四个视图都会跟着重算 |
| 重新统计 | 清空增量缓存,全量重读(排查用)。5 秒内重复点击会被节流(见下) |
| 导出 CSV | 导出当前视图的会话级长表(含四个计费桶、自身/记录合计、来源标记),带 BOM,Excel 直接打开不乱码 |

表格部分:

- 项目表 —— 列:项目 / 会话 / 子代理 / 继承历史 / 输入 / 输出 / 消耗 token(快速模式下表头为「记录 token」)/ 缓存率 / 占比。点项目名可展开该项目的会话明细(时间、类型标记、入 X · 出 Y · 缓存 Z%、合计),最多列 50 个会话。
- 按模型路由 —— 列:路由 / 输入 / 输出 / 消耗 token / 缓存率 / 占比。每一笔计费调用归属到实际服务的 provider/model(优先取该次结算自己声明的 message.source,没有则沿用最近一次 request/header/request/context 的路由)。仅在精确模式下可用,因为快速模式不读日志、拿不到归属。
- 各列含义见上面的表格各列;输入 / 输出两列是四个计费桶相加而来,鼠标悬停会展开四个桶的精确值。顶部卡片则把四个桶分开列出(未命中 / 缓存读 / 缓存写 / 输出),所以既能看"输入 vs 输出"的总账,也能看缓存命中情况。值为 0 的桶不渲染(provider 不上报缓存写入时该桶恒为 0)。
- 三种行(项目、路由、会话)在视图模型里形状一致:都平铺四个桶,且 totalTokens 就是当前视图实际展示的那个数(精确模式 = 去重值,快速模式 = 记录值),于是"输入 + 输出 = 合计"在每一行都成立。
- 页面上的所有视图都由宿主返回的会话级明细在本地聚合,与宿主自己的汇总做一致性校验;不一致会在页脚显示警告而不是悄悄放过。

两个容器的宽度差一倍,所以布局是响应式的

同一个组件被塞进两个宽度完全不同的地方:

| 入口 | 容器 | 宽度 |
| 侧边栏图标 → 中间列面板 | 应用主列 | 随窗口变化(作者实测宽屏约 960px 以上) |
| 设置 → Token 统计 | 设置弹窗内容列 | ≈564px |

564px 这个数是量出来的,不是猜的——设置弹窗自身的 CSS 是 width:800px,左侧导航 width:188px,内容区 padding:0 24px 24px,所以 800 − 188 − 48 = 564。

9 列的表格天然需要约 716px,所以在设置里会被裁掉(这就是"从设置进去布局错了"的原因)。设置弹窗是应用自己的外壳,固定 800px 且不可拖拽,插件改不了它——只能让内容适应。做法是把 .dtl-section 变成 CSS 容器查询容器(container-type:inline-size),按容器宽度分档:

| 容器宽度 | 列数 | 估算宽度 |
| > 760px | 全部 9 列 | ~716px |
| ≤ 760px | 隐藏「占比」条(表头与单元格一起隐藏,不留空列) | ~620px |
| ≤ 640px | 再隐藏「子代理」「继承历史」两列诊断列 | ~490px |

按实测的 564px 容器验算:落到第三档,需要 ~490px,余量 74px,因此不需要横向滚动。各档余量分别为 74 / 80 / 84px,能吸收列宽估算的误差。

兜底:所有表格都套了 overflow-x:auto 包装层——真到极窄的情况只会出现横向滚动条,而不会被裁掉;浏览器不支持容器查询时会忽略这些规则并直接走兜底,不会坏。

被隐藏的列在宿主面板里照旧显示,而且它们的完整数值始终在悬停提示和导出的 CSV 里。

顺带:卡片网格最小宽度从 180px 降到 150px,这样在 564px 下两行卡片各排 3 张,不会出现"第三张孤零零掉到下一行"。

缓存率是怎么显示的

口径刻意与 DSH 自己一致:缓存读取 ÷ 输入侧计费总量,也就是 cacheRead / (未命中 + 缓存读 + 缓存写)。这样面板上的数字和聊天里「会话统计」/「本轮用量」显示的缓存命中必然相同,不会出现两个都叫"缓存率"却不一样的值。

三个细节是照搬 DSH 的实现(不是我自己定的):

1. 部分命中绝不显示成 100%。 如果按常规精度四舍五入会得到 100(例如 99.996%),它会自动提升小数位来保持诚实——测试断言了 999999/1000000 会显示成 99.9999 而不是 100。
2. 没有计费输入时显示 —,而不是编一个 0%。一个只产生输出、没有 prompt 的条目不该被显示成"缓存全miss"。
3. 保留一位小数,但末尾的 0 不显示:93.0% 显示为 93%,97.6% 保持 97.6%。这与聊天的「本轮用量」面板精度一致。

显示位置:

| 位置 | 形式 |
| 顶部卡片 | 缓存命中率 94.4%,副标题写出分子/分母(缓存读 9.44 亿 / 输入 10.00 亿) |
| 两张表 | 独立的缓存率列,鼠标悬停显示精确的分子/分母 |
| 会话明细 | 每行 入 X · 出 Y · 缓存 Z% |

之所以给它一整列而不只是放个总览数字:缓存率是成本杠杆,而它在不同项目/路由之间差异极大——同一份语料里既有命中率 99% 的路由,也有只有 44% 的路由,单位输入成本相差一倍以上;只看总量永远看不出这件事。

单位为中文的亿/万或英文的 B/M/K,随界面语言切换(插件注册了自己的 locale 词典 dsh-token-ledger,中英各一份)。

两层请求保护

- 同模式并发合并:同一模式的并发请求共享一次统计,不会各跑一遍。
- 强制刷新节流:?refresh=1 的全量重读有 5 秒冷却。第一次一定执行(并由此开始冷却),冷却期内的后续刷新仍然返回结果、只是走缓存,并在 payload 里标记 refreshThrottled。

为什么需要第二层:一次全量统计要读完整语料(示例规模约 70 MiB zstd)。这个路由不受 /api 的鉴权闸门保护(DSH 的自定义路由都不受,官方 open-in-app 也是自己 opt-in 才加),回环地址上的任何进程或页面都能打它。节流把"狂点刷新"这类误用和恶意循环都限制在冷却窗口一次。

测试

npm test # 等价于 node --test test/*.test.mjs
npm run verify:live # 对运行中的 DSH 做端到端验收(见下)

npm run verify:live

宿主半边只有重启 DSH 才会重新加载(Node 的 ESM 模块按 URL 缓存),所以每次改宿主都需要一次验收。这条命令把它变成一条指令:

1. 拉取实时 payload,检查 schemaVersion、sessions[]、routes 是否为新版(否则直接提示"宿主未重载,请重启");
2. 逐个会话把实时结果与本地对同一份日志的折叠结果对比(仍在写入的会话按 mtime 排除并列出);
3. 校验路由合计 = 总计、项目合计 = 总计、复用 + 重读 = 会话总数;
4. 再拉一次,要求 sessionsRead === 0 —— 这是增量缓存生效的直接证据;
5. 最后确认快速模式仍可用。

失败会逐条列出并以退出码 1 结束。地址取值顺序为 --url → 环境变量 DSH_WEB_URL(DSH 会话会导出它)→ 桌面端默认端口。

八组测试:

| 文件 | 用例 | 内容 | 是否需要真实语料 |
| test/fold.test.mjs | 11 | 折叠语义:同步替换、llm/retry-started 退位、继承前缀跳过、路由归属、byRoute 合计等于总计 | 否(合成事件) |
| test/aggregate.test.mjs | 8 | 聚合不变量:项目合计 = 总计、路由合计 = 总计、overlap = 未去重 − 去重、计数分类、快速模式形态 | 否 |
| test/sweep.test.mjs | 10 | 增量缓存与租约纪律(用假 ctx 驱动):revision 未变则不重读、只有变动的会话被重读、未落盘的会话每次都读、refresh 清缓存、每个 observation 都被释放、失败的读取不写缓存而是下轮重试、缓存按语料剪枝、单个会话失败不中断整轮、快速模式不读日志 | 否(假 ctx) |
| test/runner.test.mjs | 6 | 请求层:同模式并发合并为一次统计、两种模式不互相合并、冷却期内的强制刷新被节流且照常返回结果、冷却结束后放行 | 否(假 ctx + 可控时钟) |
| test/client-view.test.mjs | 8 | 客户端派生逻辑、旧宿主降级路径、CSV 转义、行形状一致性 | 否 |
| test/cache-hit.test.mjs | 9 | 缓存命中率口径:分母是输入侧而非总量、无计费输入返回空、满命中为 100、部分命中的精度提升、小数位取舍 | 否 |
| test/client-host-agreement.test.mjs | 3 | 两个半边在同一份真实语料上必须得出相同数字(项目/路由/总计/CSV 逐项对齐) | 是 |
| test/projection-parity.test.mjs | 4 | 与 DSH 自己的持久化投影逐会话对比 | 是(缺语料时自动跳过,可用 DSH_HOME 指定) |

其中「每个 observation 都被释放」这条值得单说:observeSession 返回的是租约,不释放会永久钉住整份已解析的日志(查询服务的缓存容量是 5,被钉住的条目不会被淘汰)。所以这条不变量有专门的断言。

等价性测试的关键技巧:把日志只折叠到该会话 tokenUsage 行的水位线(row.seq)再比较。全量折叠会把投影尚未观察到的事件也算进去——那对正在写入的会话是正常的——所以按水位线切齐之后,这个测试是确定性的,不会因为并发写入而偶发失败。

数据来源

宿主半边只用公开服务:

- ctx.sessionQuery.listSessions() — 列出全部会话(含 header.cwd)。
- ctx.sessionQuery.observeSession(id, { projectionMode: 'all' }) — 读取单个会话的事件与投影;用完立即释放 lease(不释放会永久钉住整份已解析日志)。
- ctx.sessionProjectionCache.cachedSnapshot(header, ['tokenUsage']) — 快速模式的零 I/O 读取。

组成

dsh-token-ledger/
├── package.json # dsh.bundle.patch + dsh.client 声明 ├── cordis.patch.yml # 唯一一行:宿主半边 └── lib/ ├── index.js # 宿主半边:汇总 + GET /token-ledger/summary └── client.js # 客户端半边:settings.section 面板

数据通道是一个同源 HTTP JSON 路由,而不是生成的 Remote:这样客户端半边不需要代码生成,也不必引入 DSH 内部包。

安装 / 卸载

需要一个可用的 DSH 安装(它提供 plugin_manager 与 dsh plugin 命令)以及本仓库的一份检出。

安装到某个 profile,两种方式等价:

- 通过插件管理器 —— 让 DSH 里的 agent 调用 plugin_manager 的 install_bundle,目标指向本仓库的检出目录;或在 Web 的 设置 → Plugins 页面完成同样的操作。
- 手动 —— 把本包目录加为该 profile 的依赖(包管理器会写成 link:),并把它列进 profile package.json 的 dsh.profile.bundles。

装好后它会出现在 设置 → Plugins 里,可以随时停用或卸载。

注册本身只是两处声明:

- profile 通过 link: 指向本包目录,因此加载的是检出目录里的代码,而不是发布副本;
- package.json 的 dsh.bundle.patch 指向 cordis.patch.yml,其中只插入一行:

- insert:
- id: token-ledger
name: dsh-token-ledger

这一行就是宿主半边。客户端半边不需要自己的行:模块扫描器通过本包的 dsh.client 声明找到它,作为同一个插件的一部分提供。

要改动这个插件本身的代码,见 CONTRIBUTING.md——那里说明了两半边各自的重载方式、以及仓库的提交约定。

已知边界

- 只统计日志仍在磁盘上的会话。 被删除/归档掉日志的会话不会出现——listSessions() 以持久化列表为准。
- 无 cwd 的会话归入「(no project directory)」一行,仍计入总数。
- 精确模式是 O(全部日志字节数),全量冷读约 3 秒且不可并行加速(observeSession 没有批量接口)。会话数增长后耗时会线性上升。
- 数字不是账单:以 provider 回报的 usage 为准,DSH 未做价格换算。

许可证

MIT © u-fw · <https://github.com/u-fw/dsh-token-ledger>
预览分享卡片长什么样 ↓u-fw/dsh-token-ledger
分享 · DeepSeek Harness Hub
u-fw/dsh-token-ledger

按项目汇总所有会话的计费 token 用量

回到顶部