wingsky-1/dsh-plugin-hub

packages/dsh-provider-usage

多 provider 用量统计框架(v2 适配器契约):常驻胶囊 + 详情面板;内置 DeepSeek 官方(区间记账法推算每日用量 + 峰谷倒计时徽标,官方无用量接口也能算)与 OpenCode Go;其他数据源只需放入一个 mjs 适配器文件即可接入,设置页热插拔,密钥只留在宿主端。

用量与计费

@wingsky-1/dsh-provider-usage

DSH(DeepSeek Harness)Web GUI 插件:多 provider 通用用量统计框架(v2 适配器契约)。

聊天界面右上角常驻悬浮胶囊,展示当前模型 provider 的用量信息;点击展开详情面板。 内置两套适配器开箱即用——DeepSeek 官方(余额 + 峰谷倒计时徽标 + 每日用量推算)与 OpenCode Go(官方 /v1/usage 三窗口用量);其他任意数据源只需按 v2 契约写一个 mjs 适配器文件即可接入(设置页检测/添加/切换热插拔,见「适配器开发指南」)。 渲染在宿主端完成——适配器返回 HTML、客户端只做注入,密钥不进浏览器。

一键安装

dsh plugin --profile web add @wingsky-1/dsh-provider-usage
安装 / 卸载 / 更新后都需重启一次 dsh web(bundle 层只在启动时组合)生效。

核心优势

  • 通用框架 + 开箱即用:一套 v2 适配器契约承载任意 provider;DeepSeek 官方与

OpenCode Go 两套适配器内置,装完即显示用量

  • 接入任意数据源只需一个 mjs 文件:fetchData / formatCapsule / formatPanel

三个导出即完成接入;设置页检测/添加/切换热插拔,改文件自动热更新

  • 官方没有用量接口也能算:DeepSeek 内置适配器以区间记账法推算每日消耗

(纯消费区间 = 余额降幅,可与平台账单对账;充值独立列示不混算;异常区间不计), 余额折线充值时刻自动断轴平移,附峰谷倒计时徽标与 15 日用量柱形面板

  • 密钥不出宿主:取数在宿主端执行,密钥只在宿主端持有与使用、不下发浏览器

(推荐凭据链 / env 注入;显式 apiKey 配置会随宿主配置落盘并以 0600 保护); 渲染输出双层净化(esc() 转义义务 + 结构化净化兜底),XSS 双重防线

  • 历史可回溯、性能有兜底:按天分片 JSONL 落盘(0600)、超龄超量自动清理;

面板渲染进程内缓存 + stats 缓存 + 后台预热,数据未变时重复请求零重算

安装

前提:已安装 DeepSeek Harness 且 dsh web 可正常启动(未全局安装 dsh 见下方「未全局安装 dsh」)。

安装插件(add)

dsh plugin --profile web add @wingsky-1/dsh-provider-usage

卸载插件(remove)

dsh plugin --profile web remove @wingsky-1/dsh-provider-usage

更新插件(update)

dsh plugin --profile web update @wingsky-1/dsh-provider-usage
安装 / 卸载 / 更新后都需重启一次 dsh web(bundle 层只在启动时组合)生效。

未全局安装 dsh

若本机没有全局 dsh 命令,用 npx 临时拉起:

npx @deepseek-ai/dsh plugin --profile web add @wingsky-1/dsh-provider-usage

工作原理(v2)

宿主端(Node)                                客户端(浏览器)
─────────────────────────────                ─────────────────────
预热定时器(5min) ─┐
                  ├→ getStats()               60s 轮询 /stats(取数前先复检
客户端轮询 ───────┘   │ Mutex 互斥锁              会话当前 provider,#71 自愈)
                      │ 60s 缓存              胶囊框架 ← capsuleHtml
                      │ 5s 取数超时           面板框架 ← panelHtml(/history,90s 兜底缓存)
                      ↓
                adapter.fetchData(ctx)   ← 用户 mjs(apiEndpoint/staticPath/apiKey 注入)
                      ↓
                按天分片 JSONL 历史落盘 ──→ 面板渲染缓存全清(主失效)
                      ↓
                adapter.formatCapsule/Panel() → 净化 → HTML 下发

内置适配器 opencode-go-builtin(OpenCode Go 官方 /v1/usage 三窗口用量)与 deepseek-official-builtin(DeepSeek 官方余额 + 峰谷倒计时徽标,见下节)开箱即用。

provider 跟随语义(0.1.2 投影面,#383):切换会话即时刷新;会话内切模型经宿主
modelSelection 投影帧(control frame type:projection)实时推送、客户端切完即重拉;
信号缺失最坏场景由每次取数前的复检兜底自愈(#71)——最长一个轮询周期内收敛。
图解文档:完整的流程图 / 时序图(启动装配、/stats 取数全链路、/history 面板、自定义适配器注入三条路径与热更新、客户端交互、密钥解析链)见 docs/architecture.md。

配置

键 · 默认值 · 含义 enabled · true · 插件开关 adapter · 无 · 用户适配器 mjs 路径(缺省用内置 opencode-go) provider · opencode-go · 关联的模型 provider 名 staticPath · 无 · API 路径(注入 fetchData 入参,与 apiEndpoint 拼接) apiEndpoint · 无 · API 基础地址(可选;显式配置优先于凭据链) apiKey · 无 · 显式密钥(可选;缺省走凭据解析链,不进设置面板回显) historyDir · /dsh-provider-usage/ · 历史存储根目录 warmupIntervalMs · 300000 · 后台预热间隔(无客户端访问时保持历史连续) cacheDurationMs · 30000 · 缓存新鲜度(毫秒,下限 5000;#198 由 60000 下调——峰谷徽标跨时段边界端到端翻转延迟 ≤95s = 宿主缓存 30s + 客户端轮询 60s + 渲染余量) fetchTimeoutMs · 5000 · fetchData 强制超时(固定值,不可配置;#208 起 2s→5s,用户配置不生效) autoReload · true · 热更新开关(编辑适配器文件后自动加载;默认开启,可显式 false 关闭) maxAgeDays · 30 · 历史保留天数 maxSizeMB · 20 · 历史大小上限(MB,超限从最旧日文件删) trendRetentionDays · 180 · 会话用量趋势聚合保留天数(#503;日切压实后按天留存,正整数上界 3650)

报告配置(historyDir/reports/config.json,设置页「报告」tab 承载)

键 · 默认值 · 含义 daily / weekly / monthly · 全关 08:00/09:00/09:00 · 三周期独立开关与触发时刻(weekly 另有 weekStartsOn 周一起点、monthly 另有 dayOfMonth 触发日) provider / model · ""(跟随默认) · 报告生成所用模型路由(空串 = dsh 注册序首个) reasoningEffort · 未设置 · 当前 exact model 的 DSH reasoning effort opaque ID;未设置时请求省略该字段并沿用 DSH 默认。设置值必须存在于该模型的能力响应中,否则在发起模型请求前 fail closed;档位顺序、名称与默认值不复制、不按 low/off 或数组末档推断 prompts · 三周期年报模板 · 三周期独立提示词({stats} 占位;#633 起默认模板含目录观察——目录名 basename、占比分母 totals.total、只报数字不解读目录内容;#662 起默认模板再含时段观察——钟点/时段档只可原样引用 byHour/byPeriod/peakHour 字段、禁行为脑补、禁与目录交叉关联;存量旧默认模板读时自动升级) push.enabled · false · 生成完成后经 dsh-notifier 推送摘要(摘要仅周期/窗口/总量/调用数数值,不含项目路径) directories · [](全部) · #633 目录范围:非空数组(目录 basename 列表,"all" 显式全选语义,至多 32 项)= 报告统计只呈现所选目录的目录分布;空数组 = 全部目录。影响报告生成统计口径(byDirectory 维度按所选目录过滤)

报告生成、推理等级与有界重试

  • reasoning effort:客户端只展示并原样保存当前 exact model 的 DSH 能力响应;未选择时省略 reasoningEffort,显式 ID 在生成前按同一 exact model 校验。能力暂不可用时保留已有值并显示提示,不静默清空,也不回退到 low/off。
  • 预算与退避:初次报告调用之外最多自动 retry 5 次,attempts=0..5;第 1 至第 5 次自动重试分别退避 1、2、4、8、16 分钟。只有可分类的 transient 与空输出(reasoning-only / 双空)进入该预算;认证、额度、非法请求、content-filter、abort、未知终态与 unsupported block 均 fail closed,不自动重试。
  • attempt 口径:初次调用 + 自动 retry 最多形成 6 个报告级 outer attempt,每个 outer attempt 至多 dispatch 一次报告 stream。插件直接消费 ctx.llm.stream,不经过 DSH agent request retry waterfall;因此这只是报告级预算,不等于也不保证底层 HTTP 请求恰好 6 次。
  • 状态与恢复:逻辑身份是 period + report key。读取会在内存中按 period 规范化:所有非终态 entry 保留在 records,每个 period 只保留按 key 排序后的最后一条 terminal entry;写盘序列化时再次裁剪,并把被裁剪的 terminal key 写入全量 terminalKeys(period → key → 终态 code,按 key 排序且不按数量淘汰)。墓碑只保留终态 code,不携带 attempts、observations 或 usage;所有已知 terminal key 都保留墓碑,避免历史 key 被自动重开。flatten 以及 list/listDue/get 只投影 records,不会把墓碑展开成完整 entry。没有 entry 且没有墓碑的 key,自动 beginAttempt 才会创建 initial entry;已有 entry 时必须带匹配的 cycleId,waiting 还必须已到 nextRetryAt,in-flight 或 terminal 返回 null;若 records 没有该 key 但 terminalKeys 有墓碑,也返回 null。对这个只剩墓碑的 key,手动「重新生成」(force) 才会清除墓碑并创建新 cycle、重置计数、重新读取当前配置;force 失败仍不推进 lastRun。

例如 daily 中 2026-09-21 和 2026-09-23 都 terminal 时,records.daily 只保留 2026-09-23 的完整 entry,terminalKeys.daily 记录 2026-09-21: "auth-failed";对被裁剪的 2026-09-21 调用 beginAttempt 返回 null,只有 beginForce 才会移除墓碑并开始新 cycle。

  • 累计成本:每个实际报告 attempt 分别记录 input/output/reasoning/total/cache-read/cache-write token 与 duration;ReportTokenUsage.reasoningTokens 和 retry usage 均为 number | null。周期累计按字段独立聚合,任一 attempt 缺失某字段时该字段保持 null,不把未知伪装成 0。设置页状态区显示当前 outer attempt、累计成本、下次重试或 terminal 原因;旧状态响应没有 retry 时保持原 UI。
  • 耐久性边界:retry ledger 自身使用 0600 临时文件、完整写入、文件 fsync、原子 rename 与支持的平台上的目录 fsync;损坏文件先 no-clobber 隔离取证,再 fail closed,不把损坏当空账本。现有 report/index/lastRun 全链尚未全部 fsync,因此这里只承诺 ledger 自身耐久与进程崩溃/重启恢复,不宣称掉电下全链原子 durability。

启用选择状态恢复

historyDir/adapter-state.json 保存 provider → 启用适配器的映射。合法状态通过独占临时文件、 文件 fsync 与原子 rename 写入;支持目录 fsync 的平台还会同步父目录。rename 前的读取或写入 I/O 错误会取消本次持久化,避免覆盖无法读取的旧状态。rename 已成功而父目录 fsync 失败时, 新状态已提交并继续生效;health / 日志会单独提示「崩溃后的耐久性未完全确认」,不会误报成 写入失败。

坏 JSON 或非法顶层形态会以 no-clobber 方式移出主路径,保存为 adapter-state.json.bak-[-n],随后本次启动按默认启用关系继续。该 .bak 仅供取证、 不会自动恢复或合并;后续合法选择会从当前默认/运行时状态重新落盘。为避免外部反复损坏 造成无限累积,正常轮转最多保留 5 份取证备份,并始终保护本次新隔离的现场。因此 fail-closed 仅适用于旧状态无法读取或隔离失败的路径,不适用于已成功隔离的坏 JSON/非法形态。

胶囊位置配置

用量胶囊(会话右上角悬浮球)与面板的位置支持自定义:打开设置 → 插件 →「用量统计」→「胶囊位置」区,选择锚点(右上 / 左上 / 右下 / 左下)与偏移(水平 / 垂直 / 面板间距 / 层级基准)后点「保存」——立即生效且跨设备同步(宿主落盘 ui.json 并经 SSE 广播,无需重启)。默认值:右上 / 0 / 48 / 10 / 40——胶囊采用固定定位,不随会话滚动内容滑动位移(无避让抖动,滚动时位置稳定);水平偏移 0 使胶囊右缘贴近容器右缘(右侧对齐);垂直偏移 48 让胶囊默认位于 MCP 管理器浮窗(同为右上角、距顶 8px)正下方,两胶囊默认互不重叠;面板间距 10px;层级基准 40 对应 CSS 默认 z-index: 40,胶囊与点击后弹出的主面板同取该配置值(#128 重开维护者要求,不再派生 +30)。胶囊与 MCP 浮窗互不探测、互不避让,各自位置只由本插件配置决定。

键 · 值域 · 默认 placement · top-right / top-left / bottom-right / bottom-left · top-right offsetX / offsetY / panelOffsetY · 非负整数,clamp 到 0–2000(单位 px) · 0 / 48 / 10 zIndexBase · 整数,clamp 到 1–9000(胶囊与点击后弹出的主面板同取该配置值) · 40

移动端 / 平板端适配(issue #128):断点判定基准是会话容器(conversationHost) 的视口宽度而非窗口媒体查询——窄屏(≤480px,手机竖屏 / 极窄分栏)下面板近全屏宽、 卡片重排、按钮触控目标加大到 ≈44px;平板档(≤834px)过渡;桌面维持现状。 胶囊最终坐标经 JS 视口 clamp(safe-area 语义:宿主无 viewport-fit=cover, env(safe-area-inset-*) 恒 0 时自然退化为普通 clamp);软键盘弹出经 visualViewport resize 跟随,横竖屏切换后下一帧重算。

跨包避让契约(源自 issue #116,不可回退):本插件默认 offsetY: 48 依赖 dsh-mcp-manager 浮窗的默认位置(top-right、距顶 8px、高约 26px)在其正下方 让位;修改该默认值前须同步评估 mcp-manager 默认锚点 / 偏移,回退属跨包行为 契约变更,两包须联动调整。

DeepSeek 官方内置适配器(deepseek-official-builtin)

认领 provider deepseek-official,对接 DeepSeek 官方「查询余额」接口 GET https://api.deepseek.com/user/balance(来源: api-docs.deepseek.com/api/get-user-balance)。

数据口径

  • 仅保留 CNY 币种:官方 balance_infos[] 含多币种条目时只取 currency === "CNY" 一条,

其余(如 USD)全量忽略;金额自官方字符串字段严格解析(非法/缺失 → null,杜绝 NaN 落盘)。

  • 无 CNY 条目(仅 USD 或空数组)时产出 balance/toppedUp/grantedBalance = null 的正常帧

(不抛错),胶囊显示「DeepSeek 余额 --」占位。

  • is_available=false 表示账号不可用:该帧仍记录与展示余额,但不参与每日消耗的区间记账

——相邻区间的推算跳过并在面板标注「含不可用区间不计」,避免把封禁/清零误计为消耗。

每日用量推算(区间记账法)

官方 API 无任何用量接口,每日消耗由相邻采样点逐区间记账推算。字段语义前提(实测确认): topped_up_balance 是充值账户当前剩余(恒等式 total = toppedUp + granted 成立, 消费时 toppedUp 与 total 同步下降),故不做任何代数相消,直接按区间性质分类:

相邻采样区间 · 判定 · 处理 toppedUp 无增加且 granted 不变 · 纯消费区间 · 消耗 = 余额降幅,可与平台账单对账 toppedUp 上涨 / granted 变动 · 扰动混合区间 · 消费漏计;提取「充值 +¥X」事件独立列示 跨度 > 27h(采样中断)/ 任一端不可用 · 跳过区间 · 不计柱不计入汇总,面板注明

  • 区间归属:计入结束端所在日——跨午夜隔夜消费不丢失;当日有 ≥1 个完整区间即出数

(冷启动自然成立,无跨日基线依赖)。

  • 展示分层:日柱 = 当日落账区间降幅之和;充值合计在汇总行独立展示为绿色

「另有充值 +¥X」,绝不与消耗混算;卡1 徽章同口径(近 24h 纯消费区间求和)。

  • 折线断轴(B2):充值时刻做断轴平移——充值后各点按累计充值额整体下移抹平台阶,

断轴处画虚线连接两侧真实水位并注明金额,跳变显式可见可回溯。

双卡面板与峰谷徽标

  • 卡1:CNY 余额大头 + 消费徽章(区间记账口径,充值不误报为 ▲)+ 近 24h 波动折线

(统一时间锚、降采样 ≤300 点、充值时刻断轴平移)。

  • 卡2:近 15 个自然日每日消耗柱形图(区间记账口径;消耗蓝柱向上、净增绿柱向下、

异常仅标注;充值额在柱 title 与汇总行独立列示)。

  • 胶囊常驻峰谷倒计时徽标(纯本地时间计算,不依赖远端数据——取数失败时同样显示):
  • 时段定义为 UTC 工作日固定窗口 01:00–04:00 / 06:00–10:00(半开区间),

来源 api-docs.deepseek.com/quick_start/pricing, 核实日期 2026-08-26;硬编码常量无配置项,周末全天低谷。

  • 谷态显示距下次开峰倒计时(如 ⚡谷 · 距峰 02:41),峰态显示距切谷倒计时;

tooltip 注明 UTC 时段定义与服务器时区对照。

密钥解析顺序