dsh-peak-balance
v0.6.1
Published
Peak/off-peak billing clock with a provider-neutral account readout (DeepSeek balance, subscription plan windows, relay credits), per-turn spend measured from the provider's own counter, and a /hist token-history scene split per provider for dsh-tui
Maintainers
Readme
dsh-peak-balance
中文 · English
DeepSeek 峰谷计费时钟 · 实时余额 · 每轮花费 · /hist 历史用量方格图,显示在 dsh-TUI 里。
⚡ 峰时 09:00-12:00 · 距谷时 1h23m · 本轮 ¥0.0234 · 余额 ¥42.10(顺序是 时段 → 倒计时 → 本轮 → 余额:终端一窄,最先被截断的是余额,而每轮花费是运行中一直在变的那一项。模型正在作答时,「本轮」显示的是实时花费,写成 本轮·计费中 ¥…,随每次用量上报刷新;一轮结束后定格为 本轮 ¥…。)
开启峰时警告模式后,处于高峰时段时这一行会变成一个按所选颜色脉动闪烁的边框:
╭──────────────────────────────────────────────────────────╮
│ ⚡ 峰时 · 距谷时 1h23m · 本轮 ¥0.0234 · 余额 ¥42.10 ▂▃▄▅▆▇ │
╰──────────────────────────────────────────────────────────╯输入 /hist(或 /tokenhistory;/th 也行但要带个空格,见下文)打开历史看板——一个占满终端的场景,形状像 GitHub 的贡献图:
╭─ 🐋 Token 历史 总 token 26w 子代理 计入 ────────────────────────────── ✕ ─╮
│ 更新于 12:04:11 · 341 会话 · 6,706 事件 · 9 活跃天数 │
│ ───────────────────────────────────────────────────────────────────────────── │
│ 6月 7月 8月 9月 │
│ Mon ▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢ │
│ Wed ▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢ │
│ Fri ▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢▢ │
│ ▲ │
│ 总 token 少 ▢▢▢▢▢ 多 · 峰值 251,442,584 │
╭─ 2026-09-11 周五 ───────────────────────────────────────────────────────╮
│ token 未命中输入 2,065,340 · 缓存读 224.3M · 输出 1.5M │
│ 花费 ¥18.8494 │
│ 缓存命中 99.1% · 子代理占比 12.4% │
│ 模型 deepseek-flash 227.9M · deepseek-v4-pro 3.2M │
╰───────────────────────────────────────────────────────────────────────────╯
│ 总计 1,079,834,040 · 总花费(估) ¥64.6867 · 缓存命中 99.0% │
│ 子代理 36/341 会话 · 945 事件 · 峰值日 2026-09-11 │
│ ───────────────────────────────────────────────────────────────────────────── │
│ 模型 总 token 花费(估) 命中率 费率来源 │
│ ───────────────────────────────────────────────────────────────────────────── │
│ deepseek-flash 587.3M ¥33.94 99.3% 内置价目 │
│ deepseek-v4-flash-vision-exp 412.6M ¥24.53 98.8% 内置价目 │
│ deepseek-v4.1-flash-expires… 68.2M — 98.5% 未知 │
│ deepseek-v4-pro 8.0M ¥4.28 95.4% 内置价目 │
│ deepseek-v4-flash 457.2k ¥0.0975 90.1% 内置价目 │
│ ←/→ 前后周 · ↑/↓ 前后天 · t 今天 · m 指标 · w 跨度 · s 子代理 · r 刷新 · q/Esc │
╰───────────────────────────────────────────────────────────────────────────────╯功能
| 功能 | 说明 |
| --- | --- |
| 峰谷时钟 | 显示当前计价时段与切换到下一时段的倒计时(北京时间周一至周五 09:00-12:00、14:00-18:00 为高峰,周末全天谷时)。 |
| 账户读数(通用) | 按当前对话实际使用的 provider 显示余额或套餐额度:官方余额、订阅套餐的 5 小时/周/月窗口、中转站的额度与限额。provider 由会话日志的 request/header.config.provider 判定,base URL 与密钥引用经宿主接缝解析;开机恢复的对话在发出第一个请求前不会报告自己的路由(宿主重放历史时不再发事件),此时 auto 跟随宿主持久化的 /model 路由——也就是下一个请求将要走的那条。没有可查接口的 provider 直接不显示,绝不猜数字。 |
| 每轮花费 | 刚结束那一轮对话的花费:provider 有消费计数器时显示实测扣减(如 Command Code 的 credits、OpenRouter 的 key 用量、中转站的已用额度),否则按价目估算,都没有就显示「费率未知」。数字跟着你当前聚焦的对话走。 |
| 额度诊断 /quota | 一条命令看清算的是什么、谁在应答、上次为什么失败,以及本进程能路由到的全部 provider(/quota check <provider> 现场探测)。 |
| 峰时警告 | 可选。高峰时段生效时,状态行变成圆角边框,边框、时段标签与右侧波形按设定颜色脉动。 |
| 历史方格图 /hist | 全屏场景:一天一格、按当天用量深浅着色,鼠标悬停出当日明细;键盘按方格挪动(←/→ 前后一周、↑/↓ 前后一天、t 回到今天),另有 m 指标、w 跨度、s 子代理、r 刷新、q/Esc 返回。 |
| 总计与模型维度 | 总计:总 token、总花费(估)、缓存命中率、活跃天数、会话数、子代理占比、峰值日;模型表:每个模型的总用量、总花费、总缓存命中率与费率来源。总计行统计全部历史(不随方格显示的跨度变化),行内标注「全部历史」以说明这一点。 |
| 自定义费率 /hist price | 内置价目表没收录的模型由你自己补单价;未设置前只显示 token 并标注「费率未知」,绝不猜金额。 |
| 跟随界面语言 | 与 dsh-tui 的 /lang 即时联动:状态行、历史场景、命令回执都跟着切(宿主把选择写进 dsh-tui 设置命名空间,插件监听 settings/updated 事件;没挂该命名空间的宿主由 1 秒轮询 ~/.dsh-tui/lang.json 兜底)。设置卡片与命令补全描述本来就中英双语。 |
| 设置子页 | /settings 的 Peak & Balance(峰谷与余额) 卡片新增 Token history(历史用量) 子页,共 10 个选项(含场景版式与色阶)。 |
安装
# 从 npm 安装
dsh plugin --profile dsh-tui add dsh-peak-balance
# 或直接从本仓库的本地目录安装(pnpm 会把目录打包复制进 profile)
dsh plugin --profile dsh-tui add file:/到本仓库的绝对路径/dsh-peak-balance该命令会把 bundle 行追加进 profile 的 dsh.profile.bundles。之后在 dsh-tui 内执行 /restart 让 profile 载入新行;重启后 /settings 即可看到设置卡片。
不建议用符号链接(
link:或目录联接)方式安装:Node 会按插件的真实路径解析依赖,@deepseek-ai/*必须能从该路径向上找到。file:与 npm 安装都会在 profile 内留下真实目录,这正是宿主期望的布局。更新本地
file:安装:pnpm 会缓存本地目录依赖,只跑add/update不会拾取改过的源码。请先升版本号,再卸载重装以刷新 profile 内的副本:dsh plugin --profile dsh-tui remove dsh-peak-balance dsh plugin --profile dsh-tui add file:/到本仓库的绝对路径/dsh-peak-balance
设置项(主卡片四项 + 历史用量十项 + provider 额度七项)
主卡片:
| 字段 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| Show balance / 显示余额 | boolean | true | 状态行是否显示账户那一段(余额或套餐额度)。 |
| Show per-turn cost / 显示每轮花费 | boolean | true | 是否显示当前这轮的花费:作答进行中显示实时估算,轮次结束后定格(provider 有计数器时显示实测扣减)。 |
| Peak-hour warning / 峰时警告模式 | boolean | false | 高峰时段是否把状态行变成闪烁边框。 |
| Warning color / 警告色系 | select | red | 边框颜色:红 red、橙 orange、黄 yellow、绿 green、青 cyan、蓝 blue、紫 purple。 |
Provider quota(第三方 provider 额度) 子页:
| 字段 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| Quota provider / 额度来源 | text | auto | auto 跟随聚焦对话的 provider;恢复的对话还没发出第一个请求时,先跟随宿主持久化的 /model 路由;填 provider id(如 commandcode、openrouter)固定其中一个;off 隐藏账户那一段。 |
| Quota metric / 额度口径 | select | auto | auto(最紧的窗口)、balance、window5h、windowWeekly、windowDaily、windowMonthly、planRemaining、keyLimit、periodSpend、lifetimeSpend(累计已用)、rotate(轮换,每 8 秒换一个口径)。 |
| Turn spend / 每轮花费口径 | select | auto | auto(有计数器就实测)、measured、estimate。 |
| Unofficial endpoints / 允许非公开端点 | boolean | false | 是否允许读取厂商未公开文档的额度端点(多为逆向控制台接口)。 |
| Provider spec file / provider 声明文件 | text | 空 | 描述本包未内置适配器的 provider 的 JSON 文件;留空用 ~/.dsh-tui/dsh-peak-balance-providers.json。 |
| Billing mode / 计费方式 | select | auto | 账户读数的形态:auto 按 provider 的声明(订阅=百分比、按量=金额),也可强制 money(金额)或 plan(百分比)。 |
| Percentage base / 百分比基数 | select | meter | 套餐百分比的分母:meter 用状态行当前显示的那个窗口,monthly 用整个月度额度。 |
Token history(历史用量) 子页:
| 字段 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| Grid metric / 方格图口径 | select | tokens | 方格深浅表示什么:tokens 总 token、cost 花费、output 输出 token、cacheMiss 缓存未命中输入。 |
| Time span / 时间跨度 | select | 26 | 方格图显示多少周:13 / 26 / 53。 |
| Count subagents / 计入子代理 | boolean | true | 子代理会话也真实消耗 token;关掉后只统计你自己跑的对话,总计行会写明排除了多少。 |
| Week starts on / 每周起始日 | select | mon | 方格图第一行是 mon 周一还是 sun 周日。 |
| Grid palette / 方格色阶 | select | github | github GitHub 绿、blue 蓝色系、theme 跟随当前主题强调色。 |
| Scene layout / 场景版式 | select | card | card 圆角边框卡片 + 分区辅助线;plain 无边框(省 2 行 4 列)。终端太小时 card 自动退化为 plain。 |
| Hover: tokens / 悬停:token 分项 | boolean | true | 当日明细是否显示 未命中输入 / 缓存读 / 输出 分项。 |
| Hover: cost / 悬停:花费 | boolean | true | 是否显示当天估算花费。 |
| Hover: cache hit rate / 悬停:缓存命中率 | boolean | true | 是否显示当天缓存命中率。 |
| Hover: models / 悬停:模型明细 | boolean | true | 是否显示当天用了哪些模型。 |
设置写入 dsh-peak-balance 命名空间,改完立即生效,无需重启。场景内的 m / w / s 就是改设置(会写回 dsh/settings.yaml 并立即生效,重开 TUI 仍然有效):按下去先本地翻转,写入失败时只保留本次会话内的改动并记一条日志。
/hist 历史用量与花费
/hist # 打开方格图(推荐,不会与内置命令冲突)
/tokenhistory # 同上(长别名)
alt+h # 同上(不用打字;聊天状态下生效)
/th # 也行,但要打「/th 」带一个空格,见下
/hist price # 列出已自定义的费率 + 费率未知的模型
/hist price set <model> <hit> <miss> <out> [peakHit peakMiss peakOut]
/hist price rm <model>
/hist price clear
/quota # 额度诊断:来源、适配器、口径与上次失败原因
/quota check <provider> # 现场探测一个 provider(provider 省略则用当前目标)
/quota meter windowWeekly # 切换状态行口径(等于改设置)为什么 /th 单独回车会去切主题
这是宿主行为,插件改不了,如实说明:dsh-tui 的输入框在斜杠补全菜单打开时,回车执行的是当前高亮的那条建议,不是你打的那行字(PromptInput.js 的 handleEnter);而补全列表是「内置命令在前、插件命令追加在后」,且打字时选中项固定回到第 0 条。打 /th 时匹配到 theme、thinking 和我们的 th,高亮落在 theme → 回车就切了配色。
可用的四条路:
| 入口 | 说明 |
| --- | --- |
| /hist | 推荐。hist 不是任何内置命令的前缀,打 /hist 时菜单里只有它自己,回车直接执行。 |
| /tokenhistory | 同样不冲突。 |
| alt+h | 聊天状态下直接打开场景,不用打字。 |
| /th + 回车 | /th 后面跟一个空格:菜单会因为「已经是子命令位置」而关闭,回车就走 th 命令。 |
注意:如果将来装了名字以
hist开头的技能(skill),它也会以同样的方式抢/hist的回车——那是同一套宿主补全逻辑,届时换个名字即可(代码里是一个常量)。
键盘
| 按键 | 作用 |
| --- | --- |
| ← / → | 挪到左边/右边那一格(同一星期几,前后一周) |
| ↑ / ↓ | 挪到上边/下边那一格(同一列,前后一天) |
| t | 回到今天 |
| m / w / s | 切换 指标 / 跨度 / 子代理(会闪烁对应徽章并写回设置) |
| r | 重新扫描 |
| q / Esc | 返回对话 |
到方格图边界或「未来」的格子会停住(不绕回)。上下左右都是纯几何挪动,不再按「天/周」两个维度跳。选中格会被提亮,并在方格图下方用 ▲ 指出所在列。
子代理开关的反馈:场景标题栏有常驻徽章(子代理 计入 / 子代理 不计入,计入时是绿色),按下 s 会反色闪烁 1.2 秒;关掉时总计行还会写明「已排除 N 会话 / M 次上报」,而不是只让数字变小。
数据来源是本机会话日志($DSH_HOME/sessions/)。日志里每一个 assistant/message 事件都带着 DeepSeek 为该次请求返回的用量(inputTokens / cacheReadTokens / outputTokens / cacheWriteTokens),插件按事件自身的时间戳落到北京时间的自然日与高峰/空闲档,再按模型归档。因此:
- token 是官方上报的真实数字,不是本地估算;
- 金额是估算(官方 API 只返回 token,不返回钱),用内置价目或你自定义的单价换算,并明确标注;
- 只覆盖本机 dsh 的用量,其他客户端或网页版的调用不在其中;
- 覆盖范围随本机日志保留策略而定(本机现有日志从 2026-09-04 起)。
子代理默认计入(它们花的是真钱),表里可分辨,也可以关掉。
未收录模型(内置价目表没有的)只显示 token,金额显示「—」,等你在 /hist price set 里给定单价后才参与计价。自定义费率立即写入 ~/.dsh-tui/dsh-peak-balance-rates.json,并同时影响历史看板与状态行的每轮花费。<hit> <miss> <out> 是空闲时段的三个单价(元/百万 tokens),高峰时段默认按官方规则取两倍;要给高峰单独定价时再补三个数字。模型名可以写成 provider:model(如 commandcode:deepseek/deepseek-v4.1-flash),这样同一个模型在不同账户下可以有不同单价;不带前缀的写法始终有效。
准确性的两个实现细节(都能在真实日志上复现):
- 会话日志是多帧 zstd(每次追加一个独立帧)。
zlib.zstdDecompressSync只解第一帧,而"扫魔数切帧"会在压缩块内部偶然命中魔数——此时截断解码仍会「成功」并返回残缺内容,静默丢事件。本插件改为解析 zstd 帧头与块头精确计算帧长;在本机 341 份日志上,旧写法丢了 282 个事件,新写法全部取回。 - fork / rewind 出来的会话日志会物理携带父会话的事件前缀。插件按日志头的
seedLength截断(首个session/end-seed事件即落在此处),否则会把父会话的用量重复计一遍——本机 9 份 seeded 日志里是 292 条用量事件(约 4.5%)。
缓存:插件激活时就读取增量缓存并直接画出方格图,所以打开场景是瞬时的;随后的扫描只刷新变化的部分。缓存分三层增量:按 (路径, 大小, mtime) 跳过没变的日志;日志只被追加时,从缓存记下的「已折到第几帧」(done,附前缀尾部 16 字节签名校验)继续,只解压新增的帧;记录结构升级时由迁移函数就地改写旧缓存,而不再整份重扫。本机实测(437 份日志 / 112 MB / 19 万帧):最大的那份日志追加后,重扫从 314 ms 降到 17 ms;插件升级触发的首次扫描从整份重建降到 81 ms。内容已是最新时整轮约 20~30 ms。缓存写在 ~/.dsh-tui/dsh-peak-balance-history.json(可安全删除,删了会自动重建);真正慢的只有本机第一次全量重建(约 5~6 秒,其中约 4.3 秒是 19 万个小 zstd 帧的逐帧解码,这是 Node 端的硬开销),它会边扫边落盘,并且边扫边出图——最新的日志先扫,打开场景不到一秒就能看到最近数周的真实数据,中途被打断也能续扫而不是重来。场景打开期间每 60 秒在数字背后静默刷新一次(状态行显示「刷新中」),关掉即停。
数字是怎么来的
时段:空闲时段价格为高峰时段的一半;高峰为北京时间(UTC+8)周一至周五 09:00-12:00、14:00-18:00,其余时间(含周六日全天)为空闲时段。
花费:DeepSeek API 只返回 token 用量、不返回金额,因此每轮花费是估算:
费用 = 未命中输入 token × 未命中单价 + 缓存命中 token × 命中单价 + 输出 token × 输出单价三项输入侧数字是并列不重叠的:inputTokens 是未命中缓存的提示词,cacheReadTokens 是命中缓存的部分,服务商返回的 totalTokens 正是二者相加再加输出。若把命中量当成输入的子集去相减(旧写法),会把带缓存的轮次价格算低数倍 —— 2026-09-10 用真实余额扣减核对:实际扣 ¥0.20 的一轮,旧写法估 ¥0.03,上式估 ¥0.23。
每条用量按其请求发生的时刻落入高峰或空闲桶,跨时段的一轮不会被整体按当前时段计价。内置价目(元/百万 tokens)核对日期 2026-09-10,来源为官方模型 & 价格页:deepseek-flash 空闲 0.02 / 1 / 4,高峰 0.04 / 2 / 8;deepseek-v4-pro 空闲 0.15 / 4.5 / 13.5,高峰 0.30 / 9 / 27。旧模型名 deepseek-v4-flash、deepseek-v4-flash-vision-exp 按 Flash 价计费;deepseek-v4-pro 自北京时间 2026-09-14 12:00 起路由到 V4.1-Flash,按 Flash 价计费。价目表未收录的模型显示「费率未知」,只显示 token 而不给错误金额。
余额:GET https://api.deepseek.com/user/balance(与 dsh-tui 内置 /balance 同一只读接口)。密钥经 credentials 接缝读取 DEEPSEEK_API_KEY(可回退环境变量),仅放入请求头,不写日志、不落盘。
第三方 provider 额度
0.4.0 起,账户那一段不再写死 DeepSeek。插件按下面的顺序决定「问谁、怎么问」,任何一步拿不到就静默降级:
- provider —— 当前聚焦对话最近一次
request/header.config.provider(auto模式;也可用设置固定或关掉)。开机恢复的对话还没上报过任何 header,此时auto改用宿主持久化的/model路由(~/.dsh-tui/model.json,即选择器在重启后重新应用的那个文件);内置的deepseek-official默认只作最后兜底(宿主完全没保存过选择时)。 - 端点与密钥 —— provider 自己的设置段(
ctx.llm.listConfigurableProviders()给出的命名空间指针)→ 声明文件 → 内置目录快照。密钥经credentials接缝解析,失败回落同名环境变量。 - 适配器 —— 按下表选择;都不匹配就显示「无接口」(只有你显式指定了该 provider 时才显示这一行)。
内置适配器
| 适配器 | 覆盖的 provider | 口径 | 是否经真实账户核对 |
| --- | --- | --- | --- |
| deepseek-balance | deepseek-official / deepseek | 余额(按接口回报的币种) | ✅ 是 |
| commandcode-plan | commandcode | 5 小时 / 周窗口、月度套餐余量、本期消费 | ✅ 是(GOAT 套餐实测) |
| openrouter | openrouter | 余额(credits − usage)、key 限额、日/周/月用量 | ⚠️ 未核对(按官方文档实现,夹具测试) |
| moonshot-balance | moonshotai / moonshotai-cn | 余额(.cn 为 CNY,国际站为 USD) | ⚠️ 未核对 |
| siliconflow-balance | siliconflow 路由 | 余额(接口未标注币种,按原值显示) | ⚠️ 未核对 |
| openai-billing | 组合里声明的网关/自建路由(One API、New API 等) | soft/hard_limit_usd(两字段同值,是总额度)减去 total_usage(美分);单位跟随网关自己的显示设置 | ⚠️ 未核对 |
| declared | 声明文件里的任意 provider | 由声明决定 | — |
OpenRouter 的 /credits 需要管理密钥(普通 key 会 403);插件会同时请求 /key,因此只有普通 key 时仍然显示 key 限额与用量。
套餐按百分比显示
账户那一段的形态跟着 provider 的计费方式走:按量计费(有货币余额、按 token 扣钱)显示金额;订阅套餐(带上限的滚动窗口 + 月度池)显示百分比 —— 状态行给出「还剩多少」,以及「本轮用掉了这个窗口的百分之几」。判定顺序是:设置项 Billing mode 的强制值 → provider 自己的声明(适配器或声明文件里的 billing)→ 按数据推断(有上限窗口且无货币余额即视为套餐)。推断刻意保守:形状不认识的按金额显示,而不是凭空造一个分母。
百分比的分母由 Percentage base 决定:默认是状态行当前显示的那个窗口(最紧的限额,重置倒计时也在那一行),也可以改成整个月度额度;两边互相兜底,所以只报其中一种的 provider 也有可用的分母。没有上限就没有百分比,此时如实退回绝对数值。
🌊 谷时·半价 · 周末 · 距峰时 18h26m · 本轮 1.79%(0.2500) · 套餐 GOAT · 5h 剩 92.9%(1.00/14.00) · 距重置 1h00m窄终端只显示百分比:每个片段都带一个紧凑写法(本轮 1.79%),整行放不下时从右侧开始逐个换成紧凑写法(宿主正是先截尾)。宽度优先取宿主的终端尺寸钩子,没有就用 process.stdout.columns;两者都没有时按完整写法渲染,与旧版本一致。
百分比精度随量级变化:小于 10% 给两位小数(订阅的一轮常常不到 1%,一位小数会看起来不动),10% 及以上一位小数,更小的显示 <0.01%,真正的零显示 0%。
声明文件(未内置的 provider)
~/.dsh-tui/dsh-peak-balance-providers.json(可安全删除;坏了就当作空配置):
{
"version": 1,
"apiBases": { "my-relay": "https://relay.example.com" },
"allowUnofficial": false,
"providers": {
"my-relay": {
"adapter": "declared",
"auth": { "kind": "bearer", "apiKeyEnv": "MY_RELAY_KEY" },
"requests": [
{
"path": "/api/user/self",
"headers": { "New-Api-User": "1" },
"meters": [
{ "id": "balance", "kind": "money", "currency": "USD", "value": "data.quota", "scale": 0.000002 },
{ "id": "periodSpend", "kind": "money", "currency": "USD", "used": "data.used_quota", "scale": 0.000002 }
]
}
],
"spendCounter": "data.used_quota",
"spendUnit": { "kind": "money", "currency": "USD" }
}
}
}点路径支持数组下标(data.0.results.0.amount);scale 用来做单位换算;resetAt 支持 ms / s / iso / remainingMs / remainingS。New API / One API 的账户接口用的是网页 access token,而不是 sk- 中转 key,所以要在凭据库里另存一份(上例的 MY_RELAY_KEY),并给 allowUnofficial 或全局的「允许非公开端点」开关打开——那是控制台侧接口。
其他常见写法(同样用声明文件即可,不必改代码):
| 目标 | 关键字段 |
| --- | --- |
| Anthropic 组织级成本(需 admin key) | GET /v1/organizations/cost_report,金额路径 data.0.results.0.amount,单位是分(scale: 0.01),币种 USD |
| MiniMax 编程套餐 | GET https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains,model_remains.0.current_interval_usage_count / ..._total_count,重置 remains_time 用 resetUnit: "remainingMs" |
| 智谱余额 | GET https://open.bigmodel.cn/api/biz/account/query-customer-account-report,data.availableBalance(CNY) |
兼容性
| 项 | 值 |
| --- | --- |
| 宿主 | @deepseek-harness-tui/dsh-tui 0.10.x(ctx.tuiStatus.registerView、ctx.tuiSettingsSections.register、ctx.tuiScenes.register/open、ctx.commands.register、ctx.tuiCommandTrees.register、ctx.tuiShortcuts.register) |
| Harness | @deepseek-ai/dsh 0.1.2-rc.1+(session/event、settings、credentials) |
| 运行时 | Node ^22.19 \|\| >=24,纯 ESM,无原生依赖(多帧 zstd 用内建 node:zlib) |
| Manifest | manifestVersion 0.15 · id com.dsh-tui-ecosystem.dsh-peak-balance · 契约 tui.dsh/v1alpha1#DecisionEvents(optional)+ commands.dsh/v1alpha1#Command(required)· 四条命令贡献(/hist、/th、/tokenhistory、/quota) |
| 平台 | dsh-tui 能跑的平台(Windows / macOS / Linux) |
所有宿主接缝都是软探测(ctx.get(name, false)):缺少 TUI 扩展服务、缺少 credentials 服务或没有网络时,插件保持静默而不是让宿主失败;注册会在 profile 组合期间重试 30 秒,所有定时器都由 activation 的 effect 清理。
命令注册走中介面优先:ctx.tuiPluginHost.registerCommand(带已校验的组件身份与调用检查点)→ 拒绝时回退到 ctx.commands.register(生态文档里的 C-070 边界)。以普通 profile 行加载的第三方插件没有 verified identity,所以实际生效的是回退路径;两条路径都失败时只记一条日志,插件其余部分照常工作。
alt+h 走 ctx.tuiShortcuts.register(ctrl/alt 必需、保留组合会被宿主拒绝并返回 no-op):宿主只接受非保留组合,alt+h 未被 alt+v/alt+up 等内置键占用。
语言:插件按宿主正在显示的语言渲染,解析顺序与 dsh-tui 一致 —— DSH_TUI_LANG → 运行时 dsh-tui.lang 设置 → ~/.dsh-tui/lang.json → 系统 locale(locale 缺失时沿用历史默认 zh;存在但不支持的语言一律回落到 en,与 dsh-tui 相同)。/lang 切换通过设置服务的 settings/updated(ns, next, prev, source) 事件即时重绘状态行与已打开的历史场景;宿主没有提供该命名空间时,退化为每秒轮询持久化文件(用 mtime/size 指纹判断,没变就只做一次 stat)。DSH_PEAK_BALANCE_LANG_FILE 可覆盖该文件路径,供测试与诊断使用。
已知限制
- 输入框边框本身无法由插件改色:dsh-tui 0.10 的输入框边框由其内部
EffortInputBorder组件独占渲染,没有对插件开放的接缝。因此警告边框是渲染在对话栏正上方的状态贡献——这是不改宿主源码能做到的最接近效果。 - 花费为基于 token 用量的估算,实际扣费以 DeepSeek 平台账单为准。
- 价目表内置在包内,官方调价需要插件更新;未收录的模型需要你自己用
/hist price set补单价。 - 账户那一段依赖 provider 自己的接口:没有公开额度接口的 provider 不会显示任何数字(OpenAI、Gemini、Anthropic 预付费余额、GLM/Kimi 编程套餐、Claude 订阅等),而不是显示一个猜出来的值。可以用声明文件接入自家部署或未内置的接口。
- 恢复的对话在发出第一个请求之前,账户数字来自宿主的
/model路由,而不是这个对话本身:dsh-tui 重放已恢复会话的历史时不发会话事件,所以还没有任何东西报出这个对话自己的 provider。若该对话被单独固定到与/model选择不同的 provider,它的第一个请求会把状态行纠正过来。 - 全新环境、还没发出任何请求时会落到
deepseek-official路由:既没有~/.dsh-tui/model.json偏好、也还没有任何会话事件时,宿主只报得出这一条路由。所以你若打算用别家 provider、又没配 DeepSeek 密钥,账户那一段会先显示「未配置密钥」,直到你的第一个请求报出真正的 provider——不会出错,那个请求之后状态行自己会纠正。 - 只有 DeepSeek 官方与 Command Code 两个适配器经过真实账户核对(见上文表格);其余具名适配器按各自官方文档实现,并用文档/源码里的夹具 payload 测试,未经真实账户核对。
- 非公开端点默认关闭:spec 里声明
"allowUnofficial": true的 provider,只有在全局的allowUnofficialQuota也打开时才会被访问(如逆向的控制台接口)。Command Code 的/alpha/*不受该开关限制——那是官方 CLI 自己走的端点。 - 不内置 Command Code 的价目表:它的模型目录不带价格、价格页是前端渲染的,编一份出来就是猜。订阅套餐里的模型在
/hist只显示 token,直到你用/hist price set commandcode:<model> …给出单价;状态行的「本轮」仍然是实测扣减。 /hist的金额一律是「元」:成本由价目表算出(内置价目或/th price自定义费率,单位都是元/百万 token),所以合计行、按 provider 分列与模型表都按¥显示。credits是状态行上账户自己的额度口径,不参与成本列。- 历史缓存结构升级会被就地迁移,首次扫描仍然要几秒:记录结构变化时插件用迁移函数改写旧缓存(不重扫日志),无迁移路径的旧版本才整份丢弃——0.5.1 的那次丢弃就是「升级后第一次
/hist要等十几秒」的来源,现在不会再有。真正慢的只有本机第一次全量扫描(本机 437 份 / 112 MB 约 5~6 秒;其中约 4.3 秒是 19 万个小 zstd 帧逐帧解码的固有开销),期间边扫边落盘、边扫边出图:最新的日志先扫,不到一秒就能看到最近数周的真实数据。 - 新 fork 的会话日志在只有 2 帧时无法做帧级续读:这类日志的第 1 帧是会话头、第 2 帧才是
session/end-seed截断标记,只看到第 1 帧时无从确定截断点,插件会选择整份重读(代价以百字节计)。 /quota的 provider 列表来自ctx.llm.listConfigurableProviders()、内置目录快照与声明文件;组合里声明但宿主当前不可路由的 provider 可能不在列表里。- 余额按接口回报的币种显示:接口在
currency里给出币种(CNY/USD…),状态行使用对应符号(¥/$);未知币种回退成 ISO 代码(如12.34 CHF),不会把美元当人民币显示。 - 富状态视图与其他插件共享 6 行预算,本插件占用 3 行,且仅在警告边框显示时占用。
- 状态行显示的是宿主当前聚焦的那个对话:切换对话后它跟着切。已结算的轮次按对话记录(不是按会话对象),所以切走再切回来仍然能看到那个对话自己的上一轮花费;只有该对话在本进程内确实还没有已结算的轮次时才显示
本轮 —(新开的/new会话就是这种情况),而不是拿别的对话的数字顶上。 - 子代理的花费计入「本轮」:子代理会话(
origin: subagent/delegationDepth > 0)的用量会按会话头里的parentSession折叠回发起它的那个对话正在进行的轮次;父轮次结束后才回来的后台子代理不再计入(那一轮已经结算)。宿主没有给出父子关系时按旧行为忽略。注意/hist的总量与状态行口径不同:历史默认把子代理单独统计(可用「计入子代理」开关切换),状态行则是把它并进父轮次。 - 空 marker 是稳定状态,不是反复的
/new:宿主在/new以及退出一个不可恢复的会话时都会把~/.dsh-tui/resume.txt写成空串,所以插件只在标记内容或 mtime 变化时才采用它。早期版本每秒重放一次该读数,会让已经认领状态行的对话被永久静音(显示停在本轮 —)。 - 轮次结束时的瞬时判定:用量报告按其请求开始时刻(
step/start)归入峰/谷档;一轮的费率与模型按该轮第一次上报时的快照计价,因此轮内换模型或跨 2026-09-14 的 Pro→Flash 换挡不会把整轮改价。结算发生在轮末,只影响没记录到任何上报的极端情况。 - 历史只覆盖本机 dsh 的用量:日志里没有的调用(网页版、其他客户端、其他机器)不会出现在方格图里。历史起点取决于本机日志文件,现有日志从 2026-09-04 开始。
/th单独回车会被宿主的补全菜单抢去切主题(见上文「命令名与回车行为」):这是宿主逻辑,插件无法把命令排到补全列表前面;用/hist、/tokenhistory、alt+h或/th带空格都可以。同理,若日后某个技能名以hist开头,也会以同样方式抢走/hist的回车。- 本机第一次打开
/hist(还没有缓存)需要几秒:全量扫描会话日志(本机 437 份 / 112 MB 约 5~6 秒),期间显示扫描进度,并边扫边出图(最新日志先扫,最近数周不到一秒即可见);扫描会边扫边落盘,中途被打断也能续扫。有缓存之后,打开场景直接由缓存出图。 - 鼠标悬停依赖全屏(alternate screen):profile 的
fullscreen: true下可用;inline 模式下请用键盘(←/→/↑/↓)选中日期,明细卡同样会显示。 - 方格图的深浅按当前可见区间内非零日期的四分位分级,因此数据增长后同一数值的颜色档位可能变化(与 GitHub 一致);图例里始终标着当前区间的最大值。
- 终端不够宽时裁周而不是缩格:宽度放不下完整跨度时只显示最近的若干周,标题栏徽章会写「显示 27/53 周」;
←/→挪到边缘时窗口跟着滚一格,选中格不会被藏掉。高度不足时按此优先级逐级降级:模型表 → 明细卡的字段(先砍模型行,再砍命中率/花费/token)→ 总计行;<12行或<40列时改用兜底视图(最近若干天一行一条 + 一行总计),保证不溢出。
开发与验证
pnpm install --frozen-lockfile
pnpm check:encoding # 无 UTF-8 BOM / 编码损坏(dsh 崩溃的经典元凶)
pnpm validate:manifest # 准入形状 + 版本一致性
pnpm test # node:test 单元测试 + 宿主桩集成测试
pnpm pack:verify # 入口引用到的模块是否都在 files 内
pnpm verify # 以上四项依次执行宿主集成探测(无头启动一个一次性 profile,验证宿主是否接受注册——单测看不见这一层):
node ../../tools/probe-plugin.mjs . --wait 15探测 profile 现在会一并挂载 scenes / plugin-host / command-trees / extensions(含 tuiShortcuts)四行,因此场景、四条命令与 alt+h 快捷键的注册都会被验证;期望输出里能看到 history scene registered、四行 command registered、command tree registered roots=4 与 shortcut registered alt+h,退出码 0。
校验历史数字(真值校验)
聚合逻辑是纯函数、可单测(test/history.test.js),但数据本身要看真实日志。想在同机复核:
- 删除缓存
~/.dsh-tui/dsh-peak-balance-history.json,让下一次/th全量重扫; - 用一个独立实现(不 import 本包的模块)按同一份字节折叠一遍,比较逐日/逐模型数字;
- 对比时注意会话日志是活文件:一边跑一边写会让总数持续增长,两次快照不可能相等,只有工具与插件对同一批字节的一致才有意义。
本仓库开发时用这种方式核对过:341 份日志、116 个 (模型, 日期, 峰/谷) 桶,两份实现 0 处不一致;同时验证了 0 个重复 seq、0 个乱序 seq、0 行非法 JSON。
预览峰时警告效果
警告边框只在真实高峰时段(周一至周五 09:00-12:00 / 14:00-18:00 北京时间)出现。想在任意时间预览:
DSH_PEAK_BALANCE_FORCE_PEAK=1 dsh --profile dsh-tui # PowerShell: $env:DSH_PEAK_BALANCE_FORCE_PEAK=1该开关只改变呈现方式(倒计时仍按真实时钟显示),不设置该变量时完全不生效。
诊断日志
插件在 ~/.dsh-tui/dsh-peak-balance.log 保留一份有上限的生命周期日志:模块被导入一行、apply() 开始一行(含 pid 与实际加载的文件路径)、解析出的配置、可挂载的宿主接缝探测结果、每次注册的结果,以及卸载。有了它就能区分「宿主压根没加载这个文件」和「某个接缝拒绝了注册」,不必给运行中的 TUI 挂调试器。文件超过 128 KiB 时自动裁剪保留最新一半;DSH_TUI_DEBUG=1 会追加每次刷新的细节。跑测试时不会写入该文件。
聚焦对话来自两条独立信号,按优先级:
- 宿主托管的
tui/session-switched决策事件通知——宿主挂载了 plugin-interop 行时可用(日志里host=1)。manifest 把该契约声明为 optional 并写清兜底方案,缺少它的宿主只会降级,不会被拒绝准入。 - 宿主每次切换都会重写的启动器标记
~/.dsh-tui/resume.txt,每秒读一次。宿主开始新对话(/new)时会把这个标记清空——这是「有信息」而不是「读不到」:状态行会放掉原来那个数字,改由第一个"不是刚离开的那个对话"来接手。只有压根读不到标记时,才回退到「最后一次会话事件」。
DSH_PEAK_BALANCE_FOCUS_FILE 可覆盖该标记路径,供测试与诊断使用——测试运行因此永远不会去读真实标记。
发布与版本
仓库 VviLliAm-qwq/dsh-peak-balance,MIT 许可。版本遵循 SemVer;npm 的 version 与 manifest 的 version 始终保持一致,打一个与该版本同号的 v* tag 即触发发布工作流;该工作流经 npm **可信发布(OIDC)**上传,仓库内不存放令牌。
收录
本插件收录在 dsh-tui 插件市场。市场只做链接罗列,不做代码审查、不代表背书。
给插件作者的坑(本插件踩过的)
- Cordis 插件入口只能导出
name、Config、apply三个符号。 若在同一个模块里额外导出工具函数,宿主对 activation 的包装方式会改变,随后所有tuiStatus/tuiSettingsSections注册都会被拒(requires a live Cordis activation context)。这个故障是「半死」的:设置命名空间仍会注册成功,于是插件看起来活着,但设置卡片和状态行永远不出现。请把实现放在同目录的另一个模块,入口只做再导出。 - 可选宿主服务要「严格优先」获取(
ctx.get(name));非严格的ctx.get(name, false)可能返回影子占位实例,宿主会拒绝其方法调用。非严格形式只作兜底,并且要持续重试 —— 第一拍时接缝行可能仍在激活中。 - 中介式命令注册需要「已校验的组件身份」,普通 profile 行拿不到。
ctx.tuiPluginHost.registerCommand会先requireComponentIdentity,以file:装进 profile 的第三方插件不是通过宿主的 admission 通道加载的,于是它会抛the calling activation has no verified dsh-plugin.json Component identity。manifest 里照样要如实声明commands.dsh/v1alpha1#Command与该命令的 contribution id(将来被 admission 接管时才有意义),但代码必须准备好回退到ctx.commands.register,否则命令会静默消失。 - 状态行与全屏场景是两种不同的接缝。
tuiStatus.registerView的富视图上限 3 行且拿不到滚轮/键盘;要画多行界面(例如方格图)必须用ctx.tuiScenes.register+open,它给的是完整ui套件(Box/Text/useInput/useTerminalSize/useTheme)和close()。 - 场景里 hooks 必须无条件、按固定顺序调用。 看起来无害的「
ui.useTheme不存在就跳过」会改变 hook 顺序,真实 React 会当场抛 invalid-hook-call。本插件的写法是先取出useInput/useTerminalSize/useTheme(缺失时给一个返回默认值的桩),再无条件调用。 - 命令名不要是内置命令的前缀。 宿主的斜杠补全菜单在打开时接管回车,执行的是高亮项,且内置命令排在插件命令之前 —— 名字撞前缀(
/thvstheme/thinking)就意味着单独回车永远轮不到你。要么换个不冲突的名字(/hist),要么另给一个快捷键(ctx.tuiShortcuts,需要 ctrl/alt 且避开保留组合)。 - 全屏场景要自己预算行高。 alternate screen 里溢出不是「被裁剪」而是把整帧推走;每个区块都该先问「还剩几行」,再决定画不画。宽度同理:先算能放几列,再去裁内容,而不是指望宿主截断。
本插件把排查过程写进 ~/.dsh-tui/dsh-peak-balance.log,上面几条就是这样查出来的(见上文「诊断日志」)。
许可
MIT — 见 LICENSE。
