dsh-balance-usage
DeepSeek Harness 插件:在侧边栏左下角用绿色显示 DeepSeek 账户余额与当前峰谷时段 (空闲 / 忙碌),点击展开账户余额、峰谷切换倒计时、忙碌时段时间表与近期用量统计; 并在输入框下方与每一轮的用量旁边,按官方高峰 / 空闲价估算每个会话与每一轮的花费。
只依赖 DSH 官方账号服务与本地会话日志,不接入任何第三方服务。
English overviewA DeepSeek Harness (DSH) plugin that adds cost awareness to the Web UI. It shows the DeepSeek account balance and the current peak / off-peak phase in the sidebar, and estimates per-session and per-turn cost using the official peak and off-peak rates. The balance comes from DSH's own account service and the cost is estimated from local session logs — it talks to no third-party service.
- Sidebar footer (bottom-left) — the account balance in green when healthy, plus a coloured dot and a label stating whether it is currently off-peak or peak (peak = twice the off-peak price). Clicking opens a panel with the balance, the current pricing window with a live countdown to the next switch, the peak-hours timetable, and recent usage (today / last 7 days / last N days, a 14-day chart, and a per-model table).
- Composer dock — an inline
本会话消耗 ≈¥xpill next to DSH's own session usage line. Clicking expands an anchored panel upwards (aposition: fixedportal, so the row never grows). - Per-turn actions row — an inline
本轮花费 ≈¥xtrigger rendered in the same row as DSH's built-in本轮用量trigger, using flexorderto sit to its right.
Pricing follows the official rules: peak is Mon–Fri 09:00–12:00 and 14:00–18:00 Beijing time; weekends and Chinese public holidays are off-peak all day. Un-cached input, cache reads, cache writes and output are priced separately, using the token fields DSH records per request.
Everything is local: the client talks to the host half over one read-only loopback route
(/dsh-balance-usage/summary and /dsh-balance-usage/session). Credentials are never touched by
the plugin — the balance comes from DSH's account service, or from a DEEPSEEK_API_KEY only when
the official account is not signed in.
npm test runs test/verify.mjs, a dependency-free offline end-to-end verifier (176 assertions)
that drives the real host route handler with fake services and renders the client half through a
minimal React compatibility layer.
The rest of this document is in Chinese.
功能
1. 左下角余额条目(绿色)
- 侧边栏展开时:钱包图标 +
余额+ 金额胶囊 + 时段圆点 + 时段文字。 - 侧边栏收起(56px 轨道)时:紧凑显示
¥12.34,圆点颜色依旧表示峰谷。 - 金额按余额状态着色:
- 充足 → 绿色(
--dsw-alias-state-success-primary) - 偏低 → 琥珀色(低于
lowBalanceThreshold,默认 10) - 为 0 → 红色
- 充足 → 绿色(
- 时段圆点:绿色 = 空闲时段,琥珀色 = 忙碌时段。
- 悬停提示会同时给出余额、当前时段与「距切换还有多久」。
2. 点击展开的浮层面板
| 区块 | 内容 |
|---|---|
| 账户余额 | 总额(大字、按状态着色,点击直接跳官方账号页)、充值 / 赠送拆分、余额来源;「官方账号」与「充值」分别打开官方用量页 / 充值页 |
| 当前计价时段 | 空闲 / 忙碌胶囊、当前北京时间、距下一次切换的实时倒计时与切换时刻 |
| 忙碌时段(高峰计价) | 明确列出 周一至周五 09:00–12:00、14:00–18:00(北京时间),并说明高峰价 = 空闲价 × 2、周末与法定节假日全天按空闲计价 |
| 近期用量 | 今日 / 近 7 天 / 近 N 天的 Token、估算费用、请求次数;近 14 天每日 Token 柱状图;按模型拆分表 |
- 竖条悬停看详情:鼠标移到每日柱状图上会弹出明细浮层 —— 日期、合计 Token、输入 / 输出、 缓存命中、请求次数、高峰 / 空闲拆分,以及当天的花费。
- Token 一律用 K / M 表示(不换算成 B),大小量级之间可以直接比较。
- 面板 560px 宽,高度贴合可用窗口(最多占满视口高度减去 32px),内容超出时面板内部滚动。
- 面板通过
react-dom的 portal 渲染到document.body,用position: fixed贴住左下角条目向上展开, 因此不受侧边栏overflow与过渡动画的裁剪影响。 - 点击面板外部或按
Esc关闭;点「刷新」、右键余额条目、或窗口重新获得焦点都会立即刷新。 - 自动刷新间隔可调:面板底部有下拉框(关闭 / 15 秒 / 1 分钟 / 2 分钟 / 5 分钟 / 15 分钟),
改动立即生效并存进
localStorage(键名dsh-balance-usage.prefs)。默认值来自 Host 的config.refreshSeconds(出厂 60 秒)。 - 外观可切换:面板底部的「外观」分三档 —— 默认(跟随应用主题)/ 深色 / 浅色,
选择也存进
dsh-balance-usage.prefs,两个浮层(左下角面板、底部与每轮的花费浮层)一起生效。 深色 / 浅色不是另画一套配色,而是在浮层根元素上覆盖官方主题 token (--dsw-alias-bg-overlay、--dsw-alias-label-*、--dsw-alias-border-*等), 浮层里所有既有规则整体跟随,不会出现「不深不浅」的半吊子配色。 - 不会让你白等一个刷新周期:刚打开 Harness 时 Host 路由可能刚注册、当前会话也可能还没有用量, 这时每 3 秒重试一次直到拿到数据(拿到后交回常规刷新间隔);页面重新可见或窗口重新获得焦点时 也会立刻刷新。Host 侧也不再缓存失败结果,避免客户端在 TTL 内一直拿不到数据。
3. 对话下方:本会话花费
注册进 conversation.composer.dock(输入框下方那一栏):
🎛 本会话消耗 ≈ ¥5.67 ← 胶囊是 inline-flex,和官方那行用量统计并排同一行
点一下向上弹出明细浮层:
┌───────────────────────────────┐
│ 🎛 本会话金额 ≈ ¥5.67 │
│ ───────────────────────────── │
│ 高峰花费 ≈ ¥1.20 │
│ 空闲花费 ≈ ¥4.47 │
│ 请求次数 128 次 │
│ 缓存命中率 99.4% │
│ 模型 deepseek-flash │
│ │
│ 本地按官方价表估算,非官方账单。│
└───────────────────────────────┘
关键是展开不会改变那一栏的高度(早期版本就是在这一点上做错了):
- 胶囊外层
display:inline-flex,与官方那行用量统计并排,不另起一行; - 展开的浮层是 portal 到
document.body的position: fixed元素,用官方基元primitives.useAnchoredPosition({ side: "top", gap: 6, margin: 8 })锚在胶囊上沿并向上展开 —— 浮层完全脱离文档流,展开 / 收起时那一栏高度一个像素都不变; - 关闭走官方基元
primitives.useDismissOnOutsidePointer,另加Esc; - 两个官方基元缺失时都有降级实现(自己按
bottom定位 + 自己监听外部点击),功能不受影响; - 官方界面已经显示 Token 用量,所以这里只谈钱:胶囊显示金额,浮层显示花费拆分与请求数,
不再重复
未缓存输入 / 缓存读取 / 输出那些数字; - 没有可统计用量时整条不渲染(不留占位、不留空行)。
数据来自 Host 的 /dsh-balance-usage/session?sessionId=…(按会话统计整个对话,不按时间窗口截断,
带 15 秒缓存),随自动刷新间隔一起更新。
4. 每一轮:本轮花费
挂在 conversation.chat.assistant-actions —— 也就是官方「本轮用量」所在的那一行。
DSH 内部把该槽位的内容当作 extraActions 传给 MessageIconActions,而官方的
「本轮用量」正是同一个组件的 usageAction:
<MessageIconActions
extraActions={renderSlot("conversation.chat.assistant-actions", { messageId })}
usageAction={<TurnUsagePanel usage={data.tokenUsage} />} // 官方「本轮用量 11.14M tok」
/>
extraActions 在 usageAction 之前渲染,而 .actions 是 flex 容器,
所以本插件用 order: 1 把自己排到所有默认 order: 0 的兄弟之后,落到行尾:
… ⧉ ⑂ | 本轮用量 11.14M tok 12:34 | 🎛 本轮花费 ≈¥0.46
↑ 本插件(order:1 排到最后)
- 触发器尺寸对齐官方
TurnUsagePanel的触发器:inline-flex、高calc(28px + var(--dsh-content-font-delta,0px))、border-radius:var(--dsw-radius-sm), hover / 展开时用--dsw-alias-interactive-bg-hover—— 和官方那个触发器是同一套控件; - 图标用官方的
IconGaugeOutlineRegular(仪表盘 = 计量 / 消耗)。 官方「本轮用量」用的是IconDatabaseOutlineRegular,所以花费这边刻意不复用它; - 点击用和官方完全相同的交互向上弹出明细浮层(portal 到 body +
position: fixed+useAnchoredPosition({ side: "top" })):标题「本轮金额」+ 高峰花费 / 空闲花费 / 请求次数 / 模型; - 该槽位只提供
messageId,所以 Host 会把每一轮包含哪些 assistant 消息 id 一起返回, 客户端按messageId → 轮次 → 花费反查; - 没有该轮数据时返回
null(该 slot 的契约明确允许「无内容的条目返回 null」), 不会留空位、不会多出空白。
5. 空闲 / 忙碌时段的官方口径
依据 DeepSeek 官方定价文档:
Off-peak rates are half of the peak rates. Peak hours are 01:00 - 04:00 and 06:00 - 10:00 UTC, Monday through Friday, excluding Chinese public holidays. All other hours are off-peak, including weekends and Chinese public holidays in full.
换算为北京时间(UTC+8)即:
| 时间 | |
|---|---|
| 忙碌(高峰计价) | 周一至周五 09:00–12:00、14:00–18:00 |
| 空闲(低谷计价) | 工作日其余时间、周六周日全天、中国法定节假日全天 |
内置了 2026 年法定节假日表(依据国务院办公厅 2025-11-04 通知),
可用 holidays 配置项整体覆盖。
峰谷归属按请求发起时刻(step/start 事件时间)判定,取不到时回退到消息完成时刻。
官方未公开跨时段请求按发起还是按完成计费,这是合理近似;实测请求耗时在秒级,边界影响极小。
6. 价格表(空闲时段价,元 / 百万 tokens)
| 模型 | 输入(缓存未命中) | 输入(缓存命中) | 输出 |
|---|---|---|---|
deepseek-flash |
1 | 0.02 | 4 |
deepseek-v4-pro |
4.5 | 0.15 | 13.5 |
deepseek-chat(旧) |
2 | 0.5 | 3 |
deepseek-reasoner(旧) |
4 | 1 | 16 |
高峰时段按 ×2 计。模型名做了归一化:含 pro → v4-pro,含 flash → flash,
因此 deepseek-flash 与旧名 deepseek-v4-flash 走同一套价。
计费口径:DSH 的
TokenUsage.inputTokens是未缓存输入,不含缓存命中的部分 —— 实测inputTokens + cacheReadTokens + cacheWriteTokens + outputTokens === totalTokens, 正好和官方价表的「INPUT (CACHE MISS)」/「INPUT (CACHE HIT)」两栏一一对应。 所以未缓存输入按 input 价、缓存读取按 cacheRead 价分别计价(早期版本曾把缓存读取从输入里 再减一次,导致未缓存输入被算成 0、费用偏低,已修正)。
安装
本插件是一个标准的 DSH bundle 包(package.json 里带 dsh.bundle.patch 与 dsh.client),
安装后由 profile 的 bundle 栈自动挂载,无需手改 cordis.patch.yml。
已验证的安装方式(本地 link)
cd $env:DSH_PROFILE_DIR # 通常是 C:\Users\<你>\.dsh\profiles\desktop
pnpm add "link:C:\Users\<你>\Documents\deepseek-harness\default-workspace\dsh-balance-usage"
然后把 dsh-balance-usage 加进该 profile package.json 的 dsh.profile.bundles 数组:
{
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-balance-usage"
]
}
}
}
link: 会建立指向源码目录的符号链接,改完源码刷新页面即可看到客户端改动
(服务端改动需要重启 DSH Desktop)。
配置
所有字段都是可选的,写在注册项的 config 块里(cordis.patch.yml):
- id: balance-usage
name: dsh-balance-usage
config:
utcOffsetHours: 8
peakWindows:
- { start: 9, end: 12 }
- { start: 14, end: 18 }
peakWeekdays: [1, 2, 3, 4, 5] # 0=周日 … 6=周六
peakMultiplier: 2
holidays: ["2026-10-01"] # 覆盖内置节假日表
scanDays: 30 # 用量回溯天数
maxSessions: 150 # 单次统计最多扫描的会话数
cacheTtlMs: 120000 # 服务端统计缓存
refreshSeconds: 60 # 客户端自动刷新间隔(秒),0 = 不自动刷新
apiKey: "" # 未登录官方账号时的回退 API Key
lowBalanceThreshold: 10 # 低于该值金额转琥珀色
currency: "" # 强制币种,如 CNY / USD
price: # 逐项覆盖价格(空闲时段价)
deepseek-flash: { input: 1, cacheRead: 0.02, cacheWrite: 0, output: 4 }
refreshSeconds 是 Host 侧配置,改动后需要重启 DSH Desktop;
客户端下拉框里的选择只覆盖它,可随时改、无需重启。
apiKey 留空时依次尝试环境变量 DEEPSEEK_API_KEY。已登录 DSH 官方账号时不会用到它。
权限与数据
- 余额优先通过 DSH 内置账号服务读取,插件不接触、不保存任何登录凭据。
- 仅在未登录官方账号且配置了 API Key 时,才会向
api.deepseek.com/user/balance发一次余额查询。 - 用量统计完全基于本机会话日志(
ctx.sessionQuery读取assistant/message的 token usage)计算, 不向任何外部服务发送会话内容。 - 客户端与服务端只通过一个本机只读路由
/dsh-balance-usage/summary通信。
已知限制
- 显示的费用是本地估算,不是 DeepSeek 官方账单;官方调价后需更新价表。
- 用量统计默认只扫描最新的 150 个会话(
maxSessions)、最近 30 天(scanDays), 面板上会显示「已扫描 N 个会话」。 - 本地 token 数与官方计费口径可能不同(系统提示词、工具定义的内部算法未公开)。
- 模型靠事件里的
request/header推断,同名不同版本无法区分。 - 法定节假日表内置到 2026 年,之后需通过
holidays配置补充。
可以直接用的官方 UI 基元
客户端 bundle 里 require("@deepseek-ai/dsh-client-ui-primitives") 就能拿到官方 UI 库
(由 harness 在运行时提供,不需要写进 dsh.client.inject)。下面是官方自己正在使用的那批导出:
| 类别 | 可用项 |
|---|---|
| 图标 | 约 200 个 Icon*,每个都有 OutlineRegular(线框)/ Medium / Artwork 三档,例如 IconGaugeOutlineRegular、IconDatabaseOutlineRegular、IconClockOutlineRegular、IconDataOutlineRegular、IconSparkleRegular、IconRefreshOutlineRegular、IconWarningOutlineRegular、IconInfoOutlineRegular、IconCopyOutlineRegular、IconChevronDownOutlineRegular、IconRightUpOutlineRegular、IconLinkOutlineRegular |
| 基础控件 | Button、Tooltip、Modal、Menu / MenuItemButton / MenuSurface / MenuGroup、HoverCard、Toast、Tag、Pill、StateDot、Switch、Checkbox、Input、ShortcutKeys、SegmentedTabs / SegmentedControl、DisclosureRow、TextShimmer |
| 内容展示 | MarkdownText、CodeBlock、JsonTree / JsonBlock、DiffBlock、ReadBlock、SearchBlock、WebBlock、TerminalBlock、PathLabel、FileTypeIcon、ImageLightbox |
| 设置页 | SettingsForm / SettingsFormModel / SettingsValueField / SettingsSecretField、settingsNumberField、settingsTextField |
| Hook | useAnchoredPosition、useAnchoredMaxHeight、useDismissOnOutsidePointer、useModalLayer、observeComposition、pointerModality |
| 工具函数 | writeClipboard、relativeTime、focusWithoutRing、closeTopModal、isBehindModal、classifyFileType、fileSizeText、languageForPath |
本插件用到的是 IconGaugeOutlineRegular(花费)、useAnchoredPosition(side: "top" 向上锚定)、
useDismissOnOutsidePointer(点击外部关闭)。三者缺失时都有降级实现,功能不受影响。
开发与验证
test/verify.mjs 是离线端到端验证脚本,不需要 DSH、不需要网络、没有任何依赖:
用假的 ctx / sessionQuery / deepseekAccount 驱动 Host 半体并直接调用它注册的 HTTP
路由处理器,再用一层极简 React 兼容层渲染客户端半体(含 ref 提交、依赖数组、document
事件与 portal 模拟)。
176 项断言,覆盖:
- 余额拆分(充值 / 赠送)、峰谷判定、中国法定节假日、周末;
- 计费口径:未缓存输入 / 缓存读取 / 缓存写入 / 输出分别计价,高峰 ×2;
- 逐日、逐模型、逐轮(turn)聚合,以及逐轮之和 == 会话总额;
- 左下角条目、面板、底部花费胶囊、每轮花费的渲染输出;
- 浮层必须是 portal 到 body 的
position: fixed元素且side: "top"(展开不撑大布局); - 点击开关、点击外部关闭、
Esc、以及各种缺参数时的降级。
npm test # 等价于 node test/verify.mjs
改动何时生效
| 改动的文件 | 生效方式 |
|---|---|
lib/client.js |
直接改即生效(link: 安装下 DSH 的 client HMR 会发布新版本);刷新页面即可看到 |
lib/index.js、package.json、cordis.patch.yml |
属于 Host 侧,需重启 DSH Desktop |
License
MIT
No comments yet. Be the first to write one.