dsh-quota-pill
在 DSH Web 界面的 composer 下方常驻显示 DeepSeek 账号余额与「本次会话」额度消耗,位置与 DSH 自带的统计药丸(耗时 / token)并排。
仓库:https://github.com/jiangluoyiyi/dsh-quota-pill
这是一个本地 bundle:安装进当前 profile 后对所有会话生效,逻辑只有客户端半边,不注册任何 Host 服务、不下发工具、不进入模型上下文。
谷 余额¥8.75 │ 消耗¥0.42
药丸固定为两个信息格(余额 / 消耗),前面带一个峰谷标识,中间竖线分隔:
峰 = 高峰时段(价格翻倍,警示色);谷 = 空闲时段(半价,弱化灰)
- 两个数值都用等宽数字(
tabular-nums),位数变化时不会左右抖动
- 消耗尚未结算时显示
—(如 余额¥8.75 │ 消耗—),不会让药丸变窄或跳宽
- 版本号不占用药丸,只在展开的卡片里
点击药丸展开明细卡片(只放必要信息:余额分项、本次会话消耗、当前时段与它的三项单价;规则说明留在本文档,不占卡片):
余额
充值钱包 ¥8.75
赠送额度 ¥0.00
本次会话 ¥0.00
高峰 1h06m
缓存命中 ¥0.04
缓存未命中 ¥2.00
输出 ¥8.00
单位:元 / 百万 tokens
v0.1.9 刷新
排版约定:所有数值等宽数字(tabular-nums)右对齐;时段行与三行单价左端对齐、无缩进;单价一律两位小数(¥2.00 而非 ¥2),使三行数字宽度稳定;单位用一行灰色小字说明。
功能
| 显示项 | 含义 | 数据来源 |
|---|
| 余额 | 账号可用余额合计(充值钱包 + 赠送额度,按币种分别相加) | ctx.remote.account.getBalance() |
| 消耗 | 该会话的余额差值:进入该会话时记录基线余额,之后与当前余额相减 | 本地计算 |
| 峰 / 谷 | 当前处于高峰还是空闲时段 | 本地按北京时间判定(见下) |
| 充值钱包 / 赠送额度 | 两类钱包分项,保留平台返回的币种(¥ / $) | 同 getBalance |
| 时段与单价 | 当前时段、该时段剩余倒计时(如 1h06m)、三项单价 | 内置价格表 |
| 版本号 | 当前页面实际加载的 bundle 版本(在卡片操作行左侧) | 构建时常量 |
峰谷与价格
官方计价规则(模型 & 价格):高峰时段为北京时间周一至周五 9:00–12:00、14:00–18:00,其余时段(含周末)均为空闲;空闲价为高峰价的一半。扣费优先扣减赠送余额。
卡片按当前时段列出该时段的单价(元 / 百万 tokens):
| 模型 | 时段 | 缓存命中 | 缓存未命中 | 输出 |
|---|
deepseek-flash | 空闲 / 高峰 | 0.02 / 0.04 | 1 / 2 | 4 / 8 |
deepseek-v4-pro | 空闲 / 高峰 | 0.15 / 0.30 | 4.5 / 9 | 13.5 / 27 |
关于这份数据的四点如实说明:
- 这是官方标价,不是你的实际扣费。你的真实扣费以平台账单为准;卡片里的单价只用于判断"现在贵不贵、贵一倍"。
- 模型名默认按
deepseek-flash(当前 profile 的默认模型)。若你在用 deepseek-v4-pro,可设 globalThis.__DSH_QUOTA_PILL_MODEL__ = 'deepseek-v4-pro' 覆盖;如需跟随会话实际选择的模型,请提需求。卡片上不显示模型名(为保持简洁),换模型后单价会随之变化。
- 法定节假日未计入:规则把中国法定节假日排除在高峰之外,而时钟判定不出来,所以节假日会显示为"峰"。这一点不在卡片里,记在这里。
- 价格可能变动:页面更新后需要同步
client.js 里的 PRICE_TABLE;验证脚本会校验每一项的"空闲价 = 高峰价 ÷ 2"以及"一律两位小数",防止手抄出错。
余额为 0、未登录、读取失败都会给出各自的可读状态,不会把失败伪装成 0 余额。
安装
本机开发目录(本仓库已链接进 profile):
plugin_manager { action: "install_bundle", target: "<本目录绝对路径>" }
从 GitHub 安装(install_bundle 支持 git URL,会在 profile 内克隆):
plugin_manager { action: "install_bundle", target: "https://github.com/jiangluoyiyi/dsh-quota-pill" }
安装后刷新一次页面(Ctrl+Shift+R 或菜单重载)才会看到药丸。之后的代码改动由宿主侧的 bundle 轮询(默认 500ms 比较 mtime/ctime/size)自动发布给已打开的页面。
判断页面是否加载到最新代码:点开卡片,底部灰字 vX.Y.Z 就是凭据(药丸本身不显示版本,以免与金额混淆)。看不到期望的版本号即为页面仍在跑旧 bundle。
数据通路
余额取自 Host 的 account Remote 命名空间——与「设置 → 账户」页面用的是同一个后端调用,底层是 GET https://platform.deepseek.com/api/v0/users/get_user_summary,鉴权票据由 Host 保管、从不进入浏览器。
两个必须注意的契约细节(写错就会静默失败):
- 入参:
getBalance 需要 AccountClientMetadata,即 { version, locale, timezoneOffsetSeconds },且为严格编解码——多字段会被拒绝。
- 返回值是信封:Remote 调用返回
{ ok: true, value } 或 { ok: false, error },业务数据在 value 里。直接读返回值上的 status 会永远判失败(信封没有 status 字段),这是本插件开发过程中真实踩过的坑,verify-quota-pill.mjs 现在专门覆盖了这两种情况。
刷新策略
| 场景 | 行为 |
|---|
| 首次挂载 | 立即读一次 |
| 空闲 | 每 45s 一次 |
会话运行中(useSession().running) | 每 20s 一次 |
| 一轮结束 | 4s 后补读一次(此时扣费已落账) |
| 读取失败 | 按 20s → 60s → 120s 退避,避免把平台故障变成请求风暴 |
| 会话切换导致组件重挂载 | 5s 内刚读过的结果直接复用,不重复请求 |
| 展开卡片 | 额外调一次 getState() 做通路自检(卡片里的 RPC: 行仅在失败时出现) |
同一页面只会存在一个读取循环;切换会话不会重启它。
显示状态
| 状态 | 表现 |
|---|
| 正常 | 谷 余额¥8.75 │ 消耗¥0.42 |
| 高峰时段 | 峰 余额¥8.75 │ 消耗¥0.42(峰 用警示色) |
| 消耗未结算 | 余额¥8.75 │ 消耗—(首次读数无可比基线) |
| 未登录账号 | 余额格显示账号状态,消耗格 — |
| 未接通账户服务 | 余额未知 + 卡片内 诊断: no-remote / no-account / no-method |
| 调用失败 | 保留上一次成功读数并标为陈旧(黄色),卡片内显示真实错误码 |
| 读取中 | 余额格显示 读取余额…,消耗格 — |
文件
| 文件 | 作用 |
|---|
client.js | 全部逻辑:Slot 注册、余额读取循环、会话基线、卡片渲染、样式 |
index.js | Host 半边,空实现(本插件不需要 Host 服务) |
package.json | dsh.bundle.patch + dsh.client 声明 |
cordis.patch.yml | 插入一行 loader entry |
verify/verify-quota-pill.mjs | 离线验证脚本(见下节) |
挂载座位是 conversation.composer.dock,用独立 id quota(order: 10)追加在自带 stats 药丸之后,不替换任何现成 UI。样式只使用 --dsw-alias-* 主题 token,不 import 任何 Harness Client 包。
本地验证
verify/verify-quota-pill.mjs 用真实账号返回的 payload 形状驱动真实 bundle,覆盖 62 项断言:bundle 形状、注入服务、Slot 注册、metadata 字段、钱包解析与格式化、首次读数不虚报 0、差值语义、未登录 / 信封失败 / 传输失败三态、失败后保留上次读数、药丸只有两个信息格且不含版本号、灰色页脚内容、峰谷时段边界与倒计时、两项价格表的半价关系、卡片对当前时段的报价。
node verify/verify-quota-pill.mjs
它自带一个尊重依赖数组与 effect 清理的 React hook 运行时,因此能测出"状态该不该跨渲染保留"这类问题。注意它的边界:它模拟的是 React 与 Remote 的契约,不能替代浏览器实测——渲染结果仍需在页面上确认。
已知限制
- 「本次会话」是本地测量,不是平台账单:平台不提供会话级计费。该数字 = 进入会话时的余额 − 当前余额,因此这段时间内在其他窗口、其他设备或直接调用 API 产生的扣费会计入差额。余额是账号级的,无法归属到单个会话。
- 会话基线随页面存活:刷新页面后基线重新记录,差值从刷新那一刻重新累计。
- 两个会话同时运行时,余额差值无法归属到具体某一个会话。
- 余额读取依赖账号登录态:使用 API key 而非账号登录时,
getBalance 返回 null,药丸显示「未登录账号」。
- 未验证项:会话切换后的现场隔离效果、长时间运行的平台限流表现、浅色主题下的观感。
- 赠送额度为 0 时仍显示该行,用于确认钱包结构;如需隐藏可改
client.js 中对应条件。