zhujunzhujunzhu/ai-token-report

dsh-plugin

DSH 的 token 用量面板:输入框上方一条用量条,点开是计费总量、趋势图、按模型/项目/来源的分组明细与自定义日期范围,数据来自本机 DSH / Codex / Claude Code / Trae / WorkBuddy 的会话日志;填了自建服务端地址与 appKey 后,用量事件会实时上报到那个服务端,两者都没配时一个字节都不采集;另提供 token_usage 工具与 ctx.tokenReport 服务。不采集对话内容。

用量与计费

dsh-plugin-token-report

在 DeepSeek Harness(DSH) 里直接查看本机 token 用量:输入框上方一条用量条,点开就是趋势图、 模型排行与自定义日期范围;要往部门看板汇总时,再填「服务端地址 + appKey」开始上报。

不填就不发:未署名 / 未配 appKey 时,插件只读本机日志 —— 不采集上报事件,也不上报。

  • 📊 界面用量面板 —— 输入框上方的用量条(0.3.0 起是默认位置)或会话标题栏右上角的胶囊,共用同一个详情面板
  • 🧮 只展示真值 —— 计费总量 = 未缓存输入 + 输出 + 缓存读 + 缓存写;cacheRead 不是 input 的一部分
  • 💰 费用(估算) —— 按本机单价快照现算;未计价的用量写「未计价」而不是 ¥0.00,金额绝不跨币种相加
  • 🔗 多套 DSH 并集 —— 命令行版与 DSH Desktop 的会话日志一起统计;互为镜像的会话按 event_id 去重,只算一次
  • 🔌 本机客户端全覆盖 —— DSH / Codex / Claude Code / Trae(国际版与国内版)/ WorkBuddy 缺省就一起统计、也一起上报;装了哪个就有哪个,不用配置
  • 🔍 按来源筛选 —— 工具栏下拉默认「全部来源」(= 什么参数都不带,与以前逐字节相同);想看「这些几乎全是 Codex 跑的」就选一个客户端,整块面板只算它
  • 📤 实时上报 + 历史补报 —— 异步批量、磁盘 outbox、断网续传;服务端按事件 ID 去重,重复投递无害
  • 🤖 Agent 可查询 —— token_usage / token_usage_diagnostics 工具,以及供其它插件调用的 ctx.tokenReport 服务
  • 🔒 不采集对话内容 —— 只取 token 数值、模型名、工作目录与轮次等统计字段

快速开始

dsh plugin --profile web add dsh-plugin-token-report@latest
  1. 重启 —— dsh --profile web --no-open,用终端打印的完整地址打开浏览器
  2. 看用量 —— 选择工作区,输入框上方出现用量条。想汇总到部门看板,点面板右上角齿轮「配置」填服务端地址 + appKey,保存即生效,不必重启

不需要改任何 profile 配置:0.9.0 起本插件不再注册 sessionTelemetry 服务(那个名字由官方 OTel 后端占用),改为订阅宿主会话事件流 —— 因此与官方 session-telemetry-otel 可以同时开着,各记各的。桌面端(DSH Desktop)的安装见「在 DSH Desktop(桌面端)上安装」。

环境要求

项要求
DSH 宿主0.1.7-rc.2 或更高、0.3.0 之前(本插件已在 0.1.7-rc.2 与 0.2.0-rc.2 上实测启动)
宿主模块与宿主同代(@deepseek-ai/cordis ~4.0.4)。插件只吃宿主的会话事件流(@deepseek-ai/dsh-session)—— 0.9.0 起不再依赖 @deepseek-ai/dsh-session-telemetry
Node.js≥ 22.15.0
profile需要 web profile(界面面板与 /api 数据通道在那里;headless profile 下只有上报与工具)

上面那个版本窗口是兼容窗口,不是精确版本:宿主启动时逐个 peer 判定,任一不满足就跳过整个 bundle —— 现象是「面板不见了 + 一条也不上报」,启动日志只有一行 skipping profile bundle …, 不是报错。排查看下方「升级与常见问题」的排查表。

实际使用截图

以下截图来自 2026-09-25 本机运行的 DSH Web 与真实会话日志,仅截取插件区域。数值是该机器当时的用量,不是模拟数据,也不代表性能基准。

用量概览与模型明细

点击输入框上方 TOKEN 用量 条里的「详情」,即可查看计费总量、未缓存输入、输出、缓存读、缓存命中率、调用数和会话数。趋势支持切换 Token 总量、调用数与命中率,明细支持模型、服务商、项目和会话分组,点击行可展开,超过 10 行可翻页。

真实 DSH 插件用量概览

自定义日期范围

除了今天、昨天、本周、最近 7 天、本月、近 30 天和今年,还可以通过双月日历选择开始与结束日期。开始与结束日期既可以点日历选,也可以直接敲进输入框(2026-09-21、2026/9/21、2026年9月21日 都认,失焦后统一成 2026-09-21);格式不对、日期不存在或开始晚于结束时,标题右侧会说明原因,「应用范围」只在区间可用时才可点。范围按本地时区计算,包含起止两天,点击「应用范围」后更新统计;生效期间按钮上直接显示所选区间(如 09-12 – 10-06)。

真实 DSH 插件日期选择

安装最新稳定版

前置要求(宿主版本窗口、Node 版本)见上方「环境要求」。安装本身只有一条命令:

dsh plugin --profile web add dsh-plugin-token-report@latest

dsh plugin add 会自动在 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 中登记发布包。检查它只出现一次,且没有 0.9.0 之前的旧仓内源码包名 @ai-token-report/dsh-plugin(那个名字现在只用于清理历史装机);不要再手动 insert 插件。正常列表例如:

{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "dsh-plugin-token-report"
      ]
    }
  }
}

不需要额外改 cordis.patch.yml:0.9.0 起本插件不注册 sessionTelemetry 服务,所以官方 session-telemetry-otel 可以继续开着 —— 它只在你主动提交反馈时上传,本插件只看 token 数值与模型名。

⚠️ 0.8.x 及更早是互斥的:那一版把自己注册成第二个 telemetry 后端(cordis 同名服务只能注册一个),所以必须在同一个 cordis.patch.yml 里合并 - id: session-telemetry-otel 与 disabled: true,否则 DSH 启动会失败并只报一行 service "sessionTelemetry" has been registered at <OpenTelemetrySessionBackend>。 升级到 0.9.0 之后那一行可以删掉(留着也无害 —— 只是官方后端不再工作)。

随后重启 DSH,用终端打印的完整地址打开浏览器:

dsh --profile web --no-open

选择工作区后,输入框上方会出现用量条(0.3.0 起这是默认位置)。想让面板改到会话标题栏右上角、或两个位置都要,见下方「调整面板位置」。安装后无需单独启动本地统计网页。

dsh plugin 内部调用宿主自己的包管理器。上面的安装命令用于 DSH profile;本仓开发、构建与发布使用 Bun。

在 DSH Desktop(桌面端)上安装

桌面端有两条装法,同一套装配结果:

  1. 界面里装(推荐):侧栏「插件」→「+ 添加插件」→ 填包名 → 「安装」→ 「立即启用」→ 重启 Desktop。
  2. 命令行装:用 Desktop 自带的 dsh 与它自己的 pnpm 走一遍 dsh plugin --profile web add …(headless / 排障 / 界面装不上时用,见下方「命令行安装」)。

同一套步骤的交付版在仓库的 docs/桌面端安装交付清单.md(逐条命令 + 核对 + 回滚, 文件不在 npm 包里,所以这里只写路径);本节是它的说明版。

界面安装(侧栏「插件」)

这个页面是 Desktop 随包的官方插件管理器 @deepseek-ai/dsh-client-ui-plugin-manager(本机 Desktop 里实测存在,版本 0.1.7-rc.2; 侧栏那个「插件」入口与「添加插件」按钮的文案都出自它)。别与社区市场 dshmarket 搞混 —— 本机 profile 里两者都装着,但后者的入口叫「插件市场」(另一条侧栏项 + 设置里一张卡片)。 管理器的完整行为以它自己的 README 为准 —— 下面是与装本插件相关的那几条,引号里的文案都是从它客户端产物里逐字核出来的。

步骤说明
① 侧栏「插件」页面顶部「官方」是随 Desktop 提供、默认关闭的组合包;「已安装」是当前 profile 持有的组合包。
② 点「添加插件」对话框标题与按钮同名,输入框标签「包名或地址」,说明是「输入插件的包名、GitHub 仓库地址或本地目录路径」。我们要填的就是包名:dsh-plugin-token-report(要钉版本就写 dsh-plugin-token-report@1.0.0)。旁边「插件安装引导和示例」里的「包名」一栏写得很直白:「插件包名即 npm 包名……社区插件的 README 安装命令中 dsh plugin add 或 pnpm add 之后的部分」,不确定就点「填入示例」。
③ 「安装源」(可跳过)只决定这次去哪个 npm 源问:「默认安装源」/「npm 官方源」/「中国大陆镜像源」/「自定义地址」(私有源要登录就把凭据放在本机 ~/.npmrc)。选择只记在本浏览器的 localStorage 里;dsh plugin 命令走的是 Host 配置的注册表 —— 所以在界面里换过源,不影响命令行。
④ 点「安装」Host 先问清 spec 指向什么(pluginManager.inspect),再跑 pnpm。被拒时输入框下方给一句话,其中与本插件有关的那条是「这个包没有声明组合包,无法作为插件安装」—— 本包自 0.9.0 起带 cordis.patch.yml,不会撞上它(见下方第二个坑)。
⑤ 点「立即启用」装完给一次启用机会:启用新组合包/直接关闭对话框(关闭 = 已安装但关闭,插件不会生效)。装完点它最省事。
⑥ 重启 DSH Desktop管理器自己就写着「更改将在下次启动生效」。重启后面板出现在输入框上方(默认位置)即装好。

升级与回退:界面里没有版本选择器,spec 就是输入框里那一行(@1.0.0 才是钉版本)—— 官方管理器的 README 明写:profile 安装的插件通过卸载后重新安装来升级(安装后暂不支持自动更新)。 所以升级 = 「卸载」(会要求确认)→ 「添加插件」填新版 → 「立即启用」→ 重启;命令行 add 同 version 也换不了版本。 一次只能跑一个安装 spec。

装之前请看一眼对话框里那句安全提示(大意:插件在本机以你的权限运行,来源不明的插件可能损坏 DSH 或读取你的数据)—— 这也是为什么本插件只从 npm 上那个固定包名安装,不推荐从第三方链接装同名包。

命令行安装(headless / 排障)

界面装不了(宿主没有受管 profile、pnpm 被拦、装完没生效要排障)时,用 Desktop 自带的 dsh 与它自己的 pnpm 走一遍。步骤 ①~⑥ 来自仓内实测记录(只在版本号上按本次发布改成 1.0.0):

① 退出 DSH Desktop(要写 profile 与 node_modules)。

② 打开一个 PowerShell,把 home、宿主入口与 Desktop 自己的 pnpm 都指过去:

# 安装目录按实际替换(本机在 D:\Program Files\DSH Desktop)
$desktopRoot = "D:\Program Files\DSH Desktop"
$desktopDsh  = "$desktopRoot\resources\app.asar.unpacked\node_modules\@deepseek-ai\dsh\lib\bin.js"

$env:DSH_HOME = "$env:APPDATA\dsh-desktop\harness"
$env:PATH     = "$env:DSH_HOME\.desktop-bin;$env:PATH"   # ★ 用 Desktop 的 pnpm,别用系统里那个

PATH 这一行不是可有可无:Desktop 的 pnpm.cmd 会在 pnpm 跑动期间临时排除 generation 投影, 换成另一个 pnpm 就绕开了这层保护。

③ 装包(两种 spec 二选一):

# npm 稳定版
node $desktopDsh plugin --profile web add dsh-plugin-token-report@latest

# 本地 tarball:未发布的版本 / 离线分发。
#   ★ 路径用正斜杠,反斜杠会报 ERR_UNSUPPORTED_ESM_URL_SCHEME;下面的版本号按实际 tarball 替换。
node $desktopDsh plugin --profile web add file:D:/path/to/dsh-plugin-token-report-1.0.0.tgz

add 会自动在 profiles/web/package.json 的 dsh.profile.bundles 里登记包名;不要再手动 insert。

④ 官方 OTel 不需要再禁用(0.9.0 起):本插件不注册 sessionTelemetry 服务,两边可以同时开着。0.8.x 才需要在这一步往 profiles/web/cordis.patch.yml 里写 - id: session-telemetry-otel + disabled: true;升级后那两行可以删掉。

⑤ 核对(只读,不起服务):

node $desktopDsh plugin --profile web list              # 期望:dsh-plugin-token-report@1.0.0
node $desktopDsh --profile web --dump-config | Select-String token-report

⑥ 重启 DSH Desktop。 面板出现即装好(默认输入框上方;位置在面板「配置」里改,保存即生效)。

桌面端与命令行版的四个差别

命令行版DSH Desktop
DSH home~/.dsh(或 $DSH_HOME)%APPDATA%\dsh-desktop\harness(macOS:~/Library/Application Support/dsh-desktop/harness)
profile任意只有 web —— Desktop 只暴露这一个,别的名字直接抛错
宿主你自己装的 dshDesktop 自带一整套 harness,在 <安装目录>\resources\app.asar.unpacked\node_modules\ 下(本机实测 @deepseek-ai/dsh = 0.1.7-rc.2、@deepseek-ai/cordis = 4.0.4、随包 Node v24.9.0)
包操作你调 dsh plugin …Desktop 启动时会做 profile maintenance:把非市场来源的插件迁成不可变 generation(.generations/live/<...>,一次 rename 上线),并写 dsh.desktop.generationProjection 与 pnpm.overrides;迁移失败会写 profiles/web/.generations-deferred.json 并冻结该迁移

其余一律相同:数据目录仍是 ~/.ai-token-report,与命令行版共用(身份 / appKey 填一次两边都生效,见上方「多套 DSH 并存」);会话日志按本机全部 DSH 的并集统计。

装好之后

  • 启动日志会打印 UI 用量面板数据通道已挂载 → GET /api/tokenReport.stats(面板位置:…)。

  • 桌面端看不到宿主的终端输出(它写进 %APPDATA%\dsh-desktop\logs\harness.log),本次启动的完整地址(含 ?token=) 就在那行 dsh web: http://127.0.0.1:<端口>/?token=… 里。想确认界面数据通道真的在跑,就用它取一次配置路由 (/api 前面有 Host/Origin 栅栏,所以要带 Origin 与上一步拿到的 cookie):

    $url = "http://127.0.0.1:<端口>/?token=<启动日志里那串>"
    $jar = "$env:TEMP\dsh-cookies.txt"
    curl.exe -s -o NUL -c $jar $url
    curl.exe -s -b $jar -H "Origin: http://127.0.0.1:<端口>" "http://127.0.0.1:<端口>/api/tokenReport.config"
    # 本机实测:200 + {"position":"dock"}
    
  • 首次使用、填服务端与 appKey、看「上报调试」,都与命令行版一致,见「第一次使用」与「开启团队上报」。

四个坑

  1. 🚨 包名必须是发布名:界面与命令行都按包名找包,dsh-plugin-token-report(不是仓内历史名 @ai-token-report/dsh-plugin)。名字写错在界面里表现为「所有注册表都没有这个名字」,在命令行里是 cannot resolve profile bundle …。
  2. 🚨 包必须带组合包 patch(cordis.patch.yml):官方插件管理器安装前会问清 spec 指向什么,没有声明组合包的包当场被拒,原话是「这个包没有声明组合包,无法作为插件安装」。本包自 0.9.0 起带这个文件,发布产物里那份的 name 是发布名。
  3. 🚨 版本窗口:当前 peerDependencies 是 >=0.1.7-rc.2 <0.3.0-0,放宽后的窗口从 0.7.0 起就在 npm 上(0.6.0 及更早那一版钉的是精确 0.1.7-rc.2)。宿主启动时由 dsh-app-boot 的 evaluatePluginCompatibility 逐个 peer 做 semver.satisfies(runtime, range, { includePrerelease: true }),任一不满足就跳过整个 bundle —— 日志只有一行 skipping profile bundle …,表现是「面板不见了 + 一条也不上报」,不是报错。所以:
    • Desktop 自带的 0.1.7-rc.2(以及 0.2.x)都在窗口内,装 @latest 即可,不需要为了拿放宽窗口去手工打 tarball(tarball 只在「装未发布版本」时才用得上)。
    • 反过来,Desktop 升到 0.3.0 及以后会被跳过。那时要么等插件放宽并复验,要么用宿主自己的 allow-version … --accept-risk 强制放行(自担风险,不等于已验证)。
  4. 🚨 升级 = 先卸载再装新版;不要只手改 package.json:界面里没有版本选择器(也不提供自动更新),命令行 add 同 version 也换不了版本。另外一旦 Desktop 的 generation 迁移成功,插件会被搬进不可变的 .generations/live/<...>,那时只有重新 add 才换得了版本(plugin remove 会走 Desktop 的 generation 下线流程)。

不要把仓内源码目录装进 Desktop:发布包是构建产物(index.js / client.js),而仓内目录要先 bun run --filter dsh-plugin-token-report build、且依赖写的是 workspace:*(指向两个从未发布的私有包)。宿主跑在 Node 上、也不会替你构建,所以桌面端只用发布包或 tarball;界面里那个「本地绝对路径 / 压缩包」入口同样指的是已构建好的发布产物。

第一次使用

  1. 打开「详情」,选择需要查看的周期。首次建立索引可能需要十几秒,后续只增量读取变化的日志。
  2. 切换趋势指标或明细分组查看用量来源。图表下方「查看图表数据」提供精确值。
  3. 手动点击刷新即可读取最新数据;页面每 3 秒问一次「有没有新数」(没有就零成本), 有新采集时按宿主缓存节奏(最多 30 秒)自动更新;切回前台标签页会立刻取一次。

仅查看本机统计不需要署名。未署名时,插件不采集上报事件,也不上报。 页面读取的是 DSH 已有的本机会话日志。

多套 DSH 并存(DSH Desktop / 命令行 / 第三方客户端)

一台机器上同时装着多套 DSH 时(命令行版 ~/.dsh、DSH Desktop 的 %APPDATA%\dsh-desktop\harness……), 插件缺省把它们的会话日志一起统计。发现是结构驱动的 —— 候选目录里只有真的有 sessions 子目录 的才算一个根 —— 所以面板上的数字是多套 DSH 的并集,接入新的第三方客户端不需要改配置。

两套 DSH 的会话经常互为镜像(同一份日志被两边各记一份)。镜像按 event_id = <sessionId>:<seq> 去重、 只算一次,所以「并集小于各自相加」是正确结果,不是漏扫。启动日志与 token_usage_diagnostics 会逐一列出这次实际读到的根 —— 「我的数据到底读了哪几处」不会只给一个数字。

署名、连接配置与本地索引库都在同一个「数据目录」里,缺省 ~/.ai-token-report/,它与会话日志根无关, 所以多套 DSH 缺省就共用同一份身份与配置,Desktop 里不会再出现「尚未署名」,不需要任何配置:

<数据目录>/identity.json            ← 署名(token 就是 appKey)
<数据目录>/plugin-connection.json   ← 面板里填的服务端地址 / appKey / 间隔 / 位置 / 会话日志根
<数据目录>/project-aliases.txt      ← 项目名称别名(「自定义配置」;四种离线形态共用这一份,
                                      刻意**不**塞进 plugin-connection.json)
<数据目录>/usage.sqlite             ← 本地增量索引库(日志的派生物,可删可重建)
<数据目录>/outbox/                  ← 磁盘 outbox(崩溃不丢数据)

只有两种情况才需要动配置:想钉住统计范围(只看其中几处)用 dshHomes —— 面板里就能改(齿轮「配置」→「会话日志根」,保存即生效),也可以用部署配置 dshHomes 或环境变量 DSH_TOKEN_REPORT_DSH_HOMES 统一钉死; 想让某套 DSH 单独用一份身份 / 库用 dataDir(对应环境变量 DSH_TOKEN_REPORT_DATA_DIR, 只能在部署配置 / 环境变量里给 —— 换掉它等于连身份 / 库 / outbox 一起换,会让人「突然变成另一个人」)。

🚨 不要用日志根去达到「分开身份」的目的:dshHome / dshHomes 换掉的是日志来源, 那会让面板少算另一套 DSH 的会话,而实时上报照常工作 —— 这个错误不会以「完全没数据」的形式暴露。

别的 AI 客户端也算(缺省就是全算)

面板、token_usage 工具与上报都覆盖本机全部已注册来源:DSH、Codex、Claude Code、 Trae(国际版 trae / 国内版 trae-cn,两个发行版算两个来源)、WorkBuddy。 没有开关要打开 —— 本机装了哪个客户端,它的用量就进面板、也会进部门看板; 没装的那些自然不会出现(诊断里会逐项报出解析到的根)。

代价必须知情:第一次取数与第一轮历史补报要冷扫这些日志(本机实测 Codex 就有 1,500 个文件 / 2.8 GB,十几秒到几分钟),之后按文件字节数增量,只解析变化过的文件。 嫌慢就把那个客户端的日志目录挪走,或者用它自己的环境开关关掉 (例如 DSH_TOKEN_REPORT_CODEX=0)—— ⚠️ 这一项刻意不在面板里: 「我不想统计 Codex」是一件部署策略级的事,不该和「我的服务端地址」放在同一个表单里。

为什么不再做成「白名单」:少统计一个来源没有任何下游信号 —— 面板数字看着完全正常, 而它与「我在那台客户端上本来就没用量」长得一模一样。采集范围不是性能偏好,是一句会被读成结论的口径。

按来源筛选(只看某一个客户端的用量)

全部来源一起算时,一屏数字回答得了「本机用了多少」,回答不了「这些几乎全是 Codex 跑的」。 工具栏上那个**「来源筛选」下拉**(就在「自定义」日期范围右边)就是干这个的:默认**「全部来源」**,选一个客户端之后整块面板只算它。

  • 默认 = 不过滤:选「全部来源」时请求里一个 source 参数都不发 —— 于是它与这个筛选存在之前逐字节相同,老宿主也照常服务。
  • 候选来自宿主的来源注册表(本机认识哪些客户端),不是「库里有哪些用量」。所以选中一个从没跑过的客户端会得到如实的 0,而它旁边仍旧写着「全部来源 = 一起算」;候选缺失(老宿主)时整个下拉不出现,绝不给一个做不到的选择。
  • 它只收窄查询:采集、历史补报、上报都不受影响(筛选是视图状态 —— 和「周期 / 明细分组」同类,不写进 plugin-connection.json,重启后回到「全部来源」)。筛过的这一轮不会改变下一轮「全部来源」看到的东西。
  • 明细 / 趋势 / 项目 / 会话数 / 金额都跟着筛,库与直扫两条取数路径逐位一致(库不可用时降级直扫,筛选不会静默失效)。
  • 写错来源名会明确报错,绝不静默当成「没给」:sources: ['Codex'](大小写打错)在「取交集」的写法下交集为空,而空交集在 openStats 那边等于不过滤 —— 于是它会安静地返回全部来源的数(数字变大、不报错)。所以未注册的 id 从参数层到查询层一路抛错,并列出本机已注册的清单。

等价入口(同一语义,同一实现):

入口写法
面板工具栏「来源筛选」下拉(单选;「全部来源」= 不带参数)
token_usage 工具source 参数,逗号分隔:dsh / codex / claude-code / trae / trae-cn / workbuddy;all 或缺省 = 全部来源
CLIai-token-report --source codex --source dsh(可重复,也可 --source codex,dsh;all = 全部;未注册的 id 当场报错)
ctx.tokenReport 服务queryUsage({ sources: ['codex'] }) —— [] 或全量 = 全部来源

调整面板位置

面板默认出现在输入框上方。想让它出现在会话标题栏右上角、或两个位置都要, 有两种办法 —— 面板内改(推荐,立刻生效),或在 profile 里改部署配置。

办法一(0.6.0 起):面板右上角齿轮「配置」→ 面板位置 → 验证并保存。 保存后面板就地换地方,不必刷新页面、更不必重启 DSH:

取值效果
输入框上方(用量条)只显示输入框上方的用量条(默认)
会话标题栏右上角(胶囊)只显示标题栏右上角的胶囊;点开就是同一个详情面板
两处都显示两处都显示 —— 与 0.2.0 的外观一致

这一项与下面「部署配置」写的是同一个东西,只是存在本机 (<数据目录>/plugin-connection.json,缺省即 ~/.ai-token-report/,见上方「多套 DSH 并存」),并且优先于部署配置。

办法二:在 profile 的 cordis.patch.yml 里给插件加一段 ui —— 适合「IT 统一规定全公司都用某个位置」:

- id: token-report
  config:
    ui:
      position: dock      # dock(默认,输入框上方) | header(右上角) | both(两处都要)

改完刷新页面即可生效。三种取值共用同一个详情面板,数字口径完全一致。 也可以不改 YAML,用环境变量 DSH_TOKEN_REPORT_UI_POSITION 临时覆盖。

写错的值不会让面板消失:只认上面三个值,其它一律回退 dock,并在 DSH 启动日志里告警。 面板内那一栏同样只提供这三个值,选不出非法值。

开启团队上报

在详情面板右上角点击齿轮「配置」,六个字段:

字段说明
服务端地址部门平台根地址(例如 https://portal.example.com,或本机自建的 http://127.0.0.1:8787)。上报地址由它推导(<地址>/api/v1/token-usage),不需要自己拼路径
appKey管理员在平台「appKey 管理」页签发的那一串。已配置时留空 = 只改下面各项偏好,不会重新校验、也不重写身份文件
上报间隔5 秒 / 10 秒(默认)/ 30 秒 / 1 分钟 / 5 分钟。这个数字直接决定部门服务端的请求密度,所以只给档位
面板位置见上一节;保存后就地换地方
会话日志根每行一个 DSH home;留空 = 自动发现。见上方「多套 DSH 并存」
项目名称别名(「自定义配置」)每行一条 项目名 = 路径或仓库名;只改本机看到的名字,不影响上报数据与部门看板。见下方「自定义配置」

点击「验证并保存」后,插件用这个 appKey 向对应服务端的 /api/v1/identity/verify 校验身份, 姓名与分组以服务端返回值为准(面板不再询问姓名 —— 它由 appKey 在服务端绑定的人决定)。

★ 保存后立即生效,不需要重启 DSH。 保存成功后页面会如实回报当前状态 (「已保存并开始上报 → 地址」或「上报仍未启用:原因」),并当场开始补报本机全部历史用量。 已保存的 appKey 不回显。

看「到底上报了什么」(上报调试)

配置页第二个页签 「上报调试」 是排查「部门看板上没有我的数」的地方。 它每 3 秒刷新一次,把下面这些一次说清:

  • 在不在上报:状态 + 地址;没在跑时给原因(未署名 / 未配 appKey / 部署关闭了上报)。
  • 发了多少:已采集、已投递(含重复与拒收)、内存队列、磁盘待投递(批数 / 条数 / 字节)、 请求数与失败数、最近成功时间。
  • 最近上报:每次真实请求的请求体原文(点开可展开)+ 服务端回执 (接收 / 重复 / 拒收)与 HTTP 状态。请求体过大时只显示开头,并明确标注「已截断」。
  • 历史补报进度:扫描文件数 / 服务端确认数 / 上次错误。
  • 两个按钮:「立即上报一次」(真发)与「预览下一批内容」(只显示,不发送、不消耗队列)。

🚨 页面里看不到 appKey。 调试数据由宿主半的 GET /api/tokenReport.reports 提供, 而宿主只保留请求体、不保留请求头 —— appKey 走 Authorization: Bearer,天然不在这里。 不要为了「方便排查」把请求头加进去:那会把一个调试页变成凭证泄漏面。

身份与连接保存在数据目录下(缺省 ~/.ai-token-report/;DSH Desktop 与命令行版缺省就共用同一份、不需要任何配置,见上方「多套 DSH 并存」)。插件与本地 Web 共用身份文件。部署侧固定了身份时,页面会提示配置由管理员管理。

启用上报后,插件会在后台扫描上面那些会话日志根(缺省是本机全部 DSH home)以及本机全部已注册来源(Codex / Claude Code / Trae / WorkBuddy)下的全部历史会话,分批补报用量,直到服务器全部确认收到;不需要逐个打开旧会话。实时新用量同时上报,服务端按事件 ID 去重。断网或退出后,下次启动会继续;更换服务端地址或 appKey 后,会向新连接重新全量补报。

历史补报只发送 token 数值、模型和会话归属等统计字段,不发送对话正文。token_usage_diagnostics 会显示历史扫描进度、服务器确认数、重试错误和最近完成时间。对照本地与部门看板时,请选择相同时间范围并筛选 appKey 对应人员。

DSH 升级会保留旧格式日志作为备份;同一会话存在多个规范格式版本时,统计与补报都只读取最高版本,与 DSH 自身一致,避免事件重编号后重复计费。历史补报不会自动删除服务器上的旧数据;已由旧版本重复上报的记录需先对账、备份,再单独修复。

让 Agent 查询

可以在 DSH 会话里要求:

调用 token_usage,查看我今天的 token 用量,按模型分组。
调用 token_usage,查看最近 7 天的用量,按天显示趋势。
调用 token_usage_diagnostics,检查上报是否成功、是否有待发送数据。

工具注册需要宿主提供对应能力并启用 features.tools。查询工具只读本机日志,本身不产生上报。

功能说明

能力使用方式
界面统计用量条与标题栏入口(挂哪几个由 ui.position 决定,默认只挂输入框上方),共用详情面板
时间分析预设周期、双月日历、自定义范围、趋势切换
明细分析模型 / 服务商 / 项目 / 会话分组,展开与分页
费用(估算)面板顶部一行「费用(估算)」+ 明细表每行的金额列;token_usage 工具也给出同样一段。金额是本机按 pricing.json 快照(没有就退回内置种子价)现算的估算,与部门看板可能不同 —— 所以那行口径说明(单价来源 / 未计价比例 / 「估算 ≠ 财务账单」)永远与金额一起出现
多套 DSH 并集缺省统计本机全部 DSH 的会话日志(互为镜像的会话按 event_id 去重,只算一次)
多客户端来源本机全部已注册来源(Codex / Claude Code / Trae / WorkBuddy)缺省就一起统计、也一起上报;装了哪个就有哪个。要收窄只能用各来源自己的环境开关(如 DSH_TOKEN_REPORT_CODEX=0)
本地增量查询SQLite 增量索引;库不可用时自动回退日志扫描并提示
上报连接面板内填服务端地址 + appKey,验证后立即生效(无需重启)
上报偏好面板内选上报间隔、面板位置与会话日志根;只改偏好时不必重填 appKey,保存后即时生效
实时上报异步批量发送、磁盘 outbox、失败保留、重启重放
历史补报独立后台线程扫描全部历史,服务器确认后保存进度,失败持续重试
上报调试配置页「上报调试」页签:状态与原因、计数、最近请求体原文与回执、补报进度、立即上报 / 不发送预览
Agent 与插件集成token_usage、token_usage_diagnostics、ctx.tokenReport

token 数永远只展示真值;金额(估算)在面板与 token_usage 里也会出现, 但未计价的用量写「未计价」而不是 ¥0.00(「没配上价」与「没花钱」是两件事), 金额一律由宿主算好、格式化好再透传给界面 —— 浏览器半不做任何换算。 趋势图刻意没有金额曲线:多币种绝不跨币种相加,那条判定规则的唯一实现留在部门看板。 只采集用量相关字段(包含模型名、工作目录、轮次等),不采集对话内容。上报失败不会阻塞 DSH 的会话循环;服务端按事件 ID 去重。

采集了什么,不采集什么

内容
采集计费级的四类 token 数(未缓存输入 / 输出 / 缓存读 / 缓存写)、模型名、供应商、工作目录(项目)、会话与轮次标识、事件时间、来源客户端
不采集对话正文、提示词、模型回复、附件与文件内容 —— 内容开关恒为关,一个字节都不读
不署名就不采没有身份文件、没有 appKey,或部署侧关掉上报(features.reporting: false)时,不注册上报后端、一个字节都不发,只在启动日志里提示一次「去哪里填」
为什么不做 unknown 兜底那等于未授权的数据采集。宁可一条不报,也不替使用者做这个决定

上报走 Authorization: Bearer <appKey>,地址由使用者自己填(缺省指向本机自建的 http://127.0.0.1:8787/api/v1/token-usage)。插件不向任何其它地址发送数据。 面板的「上报调试」页签能看到每一次真实请求的请求体原文与回执 —— 采集范围不必只看承诺,可以自己核。

升级与常见问题

从旧版升级时,重新运行上方带 @latest 的安装命令即可安装最新稳定版。若曾源码直挂或手动 insert,先执行下方离线修复,再重启 DSH。

从 0.5.0(或更早)升到 0.6.0 时,数据目录换了位置,需要手动搬一次家 —— 见下方「0.6.0 数据目录位置变更」。 0.5.0 的下一个公开版本就是 0.6.0,中间没有需要单独安装的版本。

各版本都变了什么

版本使用者能感知的变动
1.0.0★ 新增「来源筛选」:面板工具栏(「自定义」右边)多一个下拉,默认**「全部来源」(请求与以前逐字节相同**),选中一个客户端之后整块面板只算它;token_usage 工具多了 source 参数、ctx.tokenReport 的查询多了 sources(同一语义,见「按来源筛选」)。未注册 / 写错的来源名当场报错并列出本机已注册清单,绝不静默变回「全部来源」。★ 同时修掉一个只在库不可用时才出现的老问题:直扫降级路径忽略了来源筛选(筛了一个来源却拿到全部来源的数字,且不报错)。另外补上桌面端界面安装(侧栏「插件」→「添加插件」)
0.9.0★ 不再与官方 OTel 后端互斥:插件不再注册 sessionTelemetry 服务(改订阅宿主会话事件流),装了它不必再停用 session-telemetry-otel;cordis.patch.yml 里那两行可以删掉。包名与发布名统一为 dsh-plugin-token-report(旧仓内名 @ai-token-report/dsh-plugin 仍会被修复工具清理)。peer 去掉 @deepseek-ai/dsh-session-telemetry
0.8.0★ 统计与上报范围都改成「本机全部已注册来源」(Codex / Claude Code / Trae / WorkBuddy 缺省就一起算、也一起报);面板里可改「会话日志根」(保存即生效);README 补齐 DSH Desktop 的安装步骤。旧的 extraSources 配置项已废弃(写了会在启动日志里告警)
0.7.0peerDependencies 从精确 0.1.7-rc.2 放宽成兼容窗口 >=0.1.7-rc.2 <0.3.0-0 —— 宿主小版本升级不必再等插件跟进
0.6.0★ 数据目录搬到 ~/.ai-token-report(升级要手动搬一次家,见下一节);面板里可改面板位置;保存即生效,不必重启;面板开始展示费用(估算)
0.4.0随包提供离线修复工具 repair-profile.mjs(清理 duplicate loader entry id;开发期写作 0.3.1,npm 上没有这个版本)
0.3.0面板默认位置改成输入框上方的用量条(ui.position: dock)—— 要保留 0.2.0 的外观就配 both

🚨 0.6.0 数据目录位置变更:升级必须搬一次家

这是升级到 0.6.0 唯一需要动手的地方。 数据目录(署名 / 连接配置 / 本地索引库 / outbox / 补报水位) 的缺省位置从 <DSH home>/token-report(通常是 ~/.dsh/token-report)改为 ~/.ai-token-report, 旧目录不会被自动迁移,也不会报错:

现象原因
面板回到「尚未署名」、要求重新填 appKey署名与连接配置还留在旧目录里
面板数字变了(历史少了一块)新目录里没有索引库;日志仍在,重扫即可恢复
磁盘 outbox 里没发完的批留在了旧目录待投递数据不会自己搬过来

先把该 profile 的 DSH 停掉,再搬(只做移动,不删除任何东西):

# Windows PowerShell —— 换过 DSH_HOME 的话,按实际路径替换 $old
$old = "$env:USERPROFILE\.dsh\token-report"
$new = "$env:USERPROFILE\.ai-token-report"
New-Item -ItemType Directory -Force $new | Out-Null
Get-ChildItem -Force $old | Move-Item -Destination $new -Force
# macOS / Linux
old=~/.dsh/token-report; new=~/.ai-token-report
mkdir -p "$new" && mv "$old"/* "$new"/

若新目录里已经有同名条目(例如升级后已经跑过一次 DSH、新的空 outbox/ 已建), Move-Item -Force 对已存在的目录仍会报错 —— 那种情况先把新目录里的空 outbox 删掉, 或者只搬 identity.json、plugin-connection.json、state.json、backfill 这几项 (usage.sqlite 可留可删,它是日志的派生物)。

搬完核对:新目录里应当能看到 identity.json 与 plugin-connection.json;重启 DSH 后面板不再要求重新署名。 想确认生效位置,打开「配置」→「上报调试」——诊断文本会同时打印会话日志根与数据目录。 CLI(ai-token-report)与本插件共用同一个数据目录,所以搬一次两边都恢复。

0.3.0 启动报 duplicate loader entry id: token-report

插件树里重复挂载了相同 ID,Loader 在插件代码执行前就会失败。仅升级 JS 文件不能清理旧 profile。离线修复工具从 0.4.0 起随包提供(开发期写作 0.3.1,但 npm 上并没有这个版本 —— 别去装它);先停止该 profile 的 DSH,再在 Windows PowerShell 运行:

node "$env:USERPROFILE/.dsh/profiles/web/node_modules/dsh-plugin-token-report/repair-profile.mjs" --profile web
node "$env:USERPROFILE/.dsh/profiles/web/node_modules/dsh-plugin-token-report/repair-profile.mjs" --profile web --apply

设置过 DSH_HOME 时,把上面的 USERPROFILE/.dsh 路径替换成实际 DSH_HOME。第一条只检查,需修复时退出码为 1;第二条先备份,再修改。工具去掉重复 bundle 和旧源码包依赖,把插件 insert 转成 ID 配置覆盖;保留其它插件、原配置与 !!js 表达式。YAML 注释在原文备份中保留。

终端会打印备份目录(<dataDir>/plugin-backups/repair-*,缺省 ~/.ai-token-report/plugin-backups/repair-*)。遇到配置冲突、其它插件占用 ID,或全局 $DSH_HOME/cordis.patch.yml 仍重复插入时拒绝写入,需人工合并。不要删除整个 profile、身份文件、数据库或 outbox。修复后重新运行 dsh web。

尚未升级时,也可单独复制仓库构建出的 repair-profile.mjs 到故障电脑,用同样参数运行,无需启动 DSH。

0.2.0 → 0.3.0 的行为变更(唯一一处):用量面板的默认位置改成 ui.position: dock —— 默认只出现输入框上方那条用量条, 会话标题栏右侧的胶囊不再默认出现。要保留 0.2.0 的外观(两处都有), 在插件 config 里加 ui: { position: both }。 位置配错(写了别的值)不会让面板消失:一律回退 dock 并在启动日志里告警。

现象处理
启动报 service "sessionTelemetry" has been registered at <…>只可能出自 0.8.x 及更早的版本(它要独占那个服务名,与官方 OTel 互斥)
面板不见了,且一条也不上报(启动日志只有 skipping profile bundle …)宿主版本落在兼容窗口之外 —— 见上方「环境要求」。这一行不是报错,是整个 bundle 被跳过了
没有用量入口确认安装在 web profile、bundle 数组包含发布包名,并已重启;features.ui 不能关闭
桌面端(DSH Desktop)装不上界面里「插件」→「添加插件」→ 填 dsh-plugin-token-report(包名必须是发布名、且包要带组合包 patch);装完点「立即启用」再重启。排障与 headless 走「在 DSH Desktop(桌面端)上安装」里的命令行步骤
401 / 未通过宿主鉴权使用本次 DSH 启动时打印的完整地址重新打开
首次统计较慢等待首次索引完成;如显示降级,检查 SQLite 权限与宿主 Node 版本
团队看板没有数据先看配置页「上报调试」页签:它会直接说「未上报及原因」并列出最近请求与回执;再用 token_usage_diagnostics 看补报进度
改完配置没生效0.6.0 起保存即生效(页面会回报状态)。若显示「需重启 DSH」,说明宿主没提供热生效入口(旧版本宿主),重启即可
升级后面板要求重新署名 / 数字少了一块数据目录换了位置,旧目录要搬一次家 —— 见上方「0.6.0 数据目录位置变更」

源码、开发文档与构建方式见 GitHub 仓库 (0.9.0 起仓内包名与 npm 发布名是同一个:dsh-plugin-token-report —— 插件市场的 npm 映射就是按仓内 package.json 的 name 去找包的)。


开发与部署参考(仓内包)

以下章节针对源码直挂与二次开发,示例里的 dsh-plugin-token-report 既是仓内包名也是发布名(0.9.0 之前仓内叫 @ai-token-report/dsh-plugin)。通过 npm 安装时使用上方的安装步骤。

装在 DSH 里,无人值守地把本机产生的计费级 token 用量实时上报到部门服务端, 同时给同事一个「问一句就能看到自己用量」的工具,以及一块在 DSH 界面里 一直看得见的用量面板。

① 实时上报   订阅宿主 session/event → 账本记录 → 入队    ← 会话进行中,秒级
② 统计工具   token_usage / token_usage_diagnostics   ← Agent 可调用
③ 统计服务   ctx.tokenReport                        ← 其它插件可调用
④ 界面面板   输入框上方的用量条 + 标题栏徽章          ← 人直接看

四种形态与 CLI ai-token、本地页面走同一套聚合与同一套口径, 所以「工具报的数」「面板上的数」「终端里的数」必然一致。


1. 一份全局配置

团队铺开时,每个人机器上的差异应当只有「身份」一项。其余全部来自同一份下发配置:

# ~/.dsh/profiles/web/cordis.patch.yml
- id: token-report
  # 按正式插件 bundle 已提供的 ID 覆盖配置;不重复 insert,也不覆盖包名。
  config:
    # ── 全局配置 ────────────────────────────────────────────────
    name: ai-token-report                      # 插件实例名,同时上报为 client.name
    appKey: !!js `process.env.ATR_APP_KEY`      # ★ 上报凭证,走 Authorization: Bearer
    endpoint: https://portal.example.com/api/v1/token-usage   # 计费上报地址

    batch:
      maxRecords: 50                # 单批最大条数
      flushIntervalMillis: 10000    # 定时冲刷间隔(turn 结束会额外触发一次)
      timeoutMillis: 15000          # 单次请求超时

    outbox:
      enabled: true                 # 磁盘 outbox(崩溃不丢)。默认开
      dir: ~/.ai-token-report/outbox
      maxBytes: 33554432            # 超限丢**最旧**的批并告警

    features:
      reporting: true               # 关掉即完全不上报
      tools: true