dsh-llm-balance
已验证dsh-llm-balance · v0.7.1 · MIT · Web 界面
DeepSeek Harness plugin for querying LLM service provider usage: reads each configured provider's remaining balance or subscription quota and shows it in an always-visible card in the web GUI.
安装
dsh plugin add dsh-llm-balance 用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。
源码
标签
作者
说明文档
dsh-llm-balance
DSH(DeepSeek Harness)插件:在 Web GUI 右下角常驻一张简洁卡片,显示当前接入的 LLM 账号还剩多少——
充值型账号(第一行提供商名,第二行金额)只显示余额;订阅型账号每个窗口一行:频限(47%) 3d0h0m重置。
- 余额(充值 / 按量):DeepSeek
/user/balance、Moonshot / Kimi 开放平台/v1/users/me/balance;只显示金额,不臆造百分比、不显示充值/赠送拆分 - 订阅窗口:Kimi For Coding
/coding/v1/usages(滚动窗 + 周池)、OpenCode Go/zen/go/v1/usage(频限 / 周 / 月);每行频限(已用%) XdXhXm重置,配色仍按剩余 % - 已配置的提供商都会出现:自动模式以
ctx.llm.listProviders()的路由为准;读了不出来的路由会显示「无额度接口」占位而不是消失(也可用sources显式增删) - 圆点含义:正在被当前模型使用 → 绿色;未在用且有告警(剩余 <20% 红、<40% 黄)→ 告警色;其余 → 灰色
- 头部图标:模式循环「跟随当前模型 → 整体汇总 → 所有来源」· 更新间隔选择 · 半透明背景开关 · 刷新
- 密钥只存在于宿主进程:浏览器只拿到金额、币种、窗口时长与错误码,接口只读且
no-store
安装
dsh plugin --profile web add /path/to/dsh-llm-balance
随后在插件市场里启用(可热挂载,无需重启 dsh web),或重启 dsh web 后生效:
curl -X POST http://127.0.0.1:3080/dsh-market/toggle \
-H 'Origin: http://127.0.0.1:3080' -H 'Content-Type: application/json' \
-d '{"name":"dsh-llm-balance","enabled":true}'
刷新浏览器页面即可看到右下角卡片。
显示模式
| 模式 | 图标 | 内容 |
|---|---|---|
| 跟随当前模型 | ◉ | 当前会话在用模型所属提供商的额度(读 modelSelection 投影;无会话时用默认模型路由) |
| 整体汇总 | ≣ | 余额按币种合计一行 + 每个订阅只显示最紧张的窗口 |
| 所有来源 | ☰ | 逐个来源全量明细(默认) |
模式、折叠、半透明、间隔与卡片位置都保存在浏览器 localStorage。头部图标依次是:模式切换、间隔芯片(60s;点击在卡片上方弹出
60s / 5m / 30m / 1h 列表,选中立即按新间隔轮询;宿主的 60s 缓存 TTL 不变,间隔更长时每次轮询都会触发重新抓取)、
半透明背景(▦,只让底色变淡,文字始终清晰)、刷新。整行标题就是拖动手柄:在标题上按住拖动即可把卡片放到任意位置,
落在左/右边缘 48px 内会磁吸吸附到该边,双击标题复位到右下角。单击标题折叠/展开(拖动结束时不会误触发折叠),
点击卡片其他地方立即刷新。
磁吸后的收缩气泡
卡片磁吸在左/右边缘时,会在卡片的角上出现一个收缩控件(左吸 ‹ / 右吸 ›)。角的位置按卡片位置决定:
横向着跟随磁吸的那一边,竖向则朝向屏幕中心的开放方向——卡片在下半屏时控件落在上方角(向上外浮),
在上半屏时落在下方角(向下外浮)(例:吸在右侧且位于下半屏 → 右上角)。控件悬浮在卡片角外(不再压住标题行);
视口那一侧没有空间时自动退回卡片内侧,保证不被裁掉。自由摆放的卡片不显示该控件。
点击后卡片收缩成一个小气泡:只显示当前提供商的大写首字母(按档位配色,余额/额度告急时依然是红/黄),
字母下面只显示一个数——充值型是余额金额,订阅型是 频限(滚动窗)的已用百分比(该来源没有频限窗时
退化为最紧张的窗口)。气泡仍贴在原磁吸边、保持原来的纵向位置;点击气泡任意位置(或聚焦后回车/空格)即可恢复完整卡片。
气泡本身不可拖动,需要移动请先恢复卡片(此时标题拖动与「双击标题复位」重新可用)。
配置(全部可选)
cordis.patch.yml 里的行刻意不带 config:——只有纯 insert 才能被热挂载。默认都在 src/config.ts,
需要覆盖时改这一行(重启生效):
- insert:
- id: llm-balance
name: 'dsh-llm-balance'
config:
defaultMode: all # current | summary | all
percentMode: used # used(已用) | left(剩余);两档都只影响订阅窗口的显示,配色恒按剩余
thresholds: { warn: 40, error: 20 } # 订阅窗口的剩余% 分档
balanceThresholds: # 余额的绝对金额分档(默认不配 = 余额不变色)
'deepseek-balance:CNY': { warnBelow: 40, errorBelow: 20 }
'moonshot-balance:*': { warnBelow: 5 }
colors: { normal: '#4d6bfe', active: '#22c55e' } # 可只覆盖其中一档(active = 在用绿点)
sources: auto # auto = 只读已注册 LLM 路由对应的来源;或显式数组 [{ id, label, apiKeyEnv, baseURL, region }]
refreshMs: 60000
timeoutMs: 8000
deepseek: { apiKeyEnv: DEEPSEEK_API_KEY } # baseURL 可覆盖,默认跟随 llm-deepseek 设置
moonshot: { region: cn } # cn=CNY(.cn 主机) | intl=USD(.ai 主机)
kimiCode:
apiKeyEnv: KIMI_API_KEY
tokenFile: ~/.kimi-code/credentials/kimi-code.json
persistRefreshedToken: true
opencode: # 仅当要覆盖 opencode* 族的默认端点/凭证时才需要
baseURL: https://opencode.ai/zen/go
apiKeyEnv: OPENCODE_GO_API_KEY
只在「已接入 harness」的来源上显示
自动模式(sources: auto)以 ctx.llm.listProviders() 里已注册的路由为准:
未接入的账号即使本机有凭证也不读。每个已注册路由都会出现在卡片上——已实现探针的显示真实数据,
其余显示一行「无额度接口」占位(不发起任何请求),所以配置了 3 个提供商就会看到 3 行,不会莫名其妙少一个。
想监控一个尚未接入 harness 的账号,就显式列出来(会绕过路由门,此时它永远不算「当前使用中」):
sources:
- { id: kimi-code, label: Kimi For Coding }
反过来,一旦在 DSH 里配好 kimi-coding 路由(例如通过 llm-pi-ai),Kimi 会自动出现,无需改配置。
余额的颜色
余额没有可信的分母,因此不显示百分比:只报金额,并按你在 balanceThresholds 里给的
绝对金额分档(键为 "<sourceId>:<CURRENCY>",支持 "<sourceId>:*" 通配币种)。
默认不配 → 余额恒为正常色;余额为负(欠费)一律按红处理。
订阅窗口的颜色与格式
每个窗口一行:频限(47%) 3d0h0m重置 = 窗口标签 +(已用百分比)+ 倒计时 + 重置。
窗口标签按语义区分:频限(滚动限流窗)/ 周 / 月 / 日——提供商标称几个窗口就显示几行
(OpenCode Go 报 3 个:频限 / 周 / 月)。倒计时用紧凑单位,不足一天给 5h0m,超过一天给 3d0h0m。
显示的是已用 %(percentMode: left 可切回剩余 %),但颜色始终由剩余 % 决定:
剩余 <20% 红、<40% 黄、其余蓝。也就是说「已用 92%」会是红的——因为只剩 8%。
原始计数与提供商标称的窗口长度(如 5小时)放在该行的悬浮提示里,不占正文。
凭证与端点跟随路由
来源读取的是该 harness 实际路由到的账号:若 kimi-coding / opencode-go 等路由在 llm-pi-ai 里声明了
apiKeyEnv(例如 KIMI_CODING_API_KEY、OPENCODE_GO_API_KEY),插件优先用它;DeepSeek 同理跟随
llm-deepseek 设置节。端点也随路由:opencode-go → https://opencode.ai/zen/go/v1/usage,
opencode / opencode-zen → https://opencode.ai/zen/v1/usage(可用 opencode.baseURL 覆盖)。
凭证按候选链依次尝试,而不是只猜一个名字:路由声明的引用 → 插件自己的配置 → 由路由 id 推导的名字
(opencode-go → OPENCODE_GO_API_KEY)→ 目录默认名(OPENCODE_API_KEY)。设置节也每次读取而非挂载时快照,
所以运行中新增提供商、或插件与 llm-pi-ai 的挂载顺序不确定,都不会让某个来源误判为「没有凭证」。
若某来源最终仍读不到,卡片显示「无额度接口」,把鼠标悬停在该行可看到具体原因(例如它尝试过哪些凭证名)。
Kimi Code 的凭证
优先该路由声明的 apiKeyEnv,其次 KIMI_API_KEY;都没有时读 Kimi Code CLI 的 OAuth 文件(access_token 只有约 15 分钟寿命)。
过期时插件用 refresh_token 换取新令牌,并且只在凭证文件与读取时逐字节一致时才原子回写
(CAS),因此永远不会覆盖 CLI 刚写入的新令牌。设 persistRefreshedToken: false 则纯只读、只在内存中
使用新令牌。
开发
pnpm install
pnpm run check # typecheck + build + test + preflight
pnpm run verify-live # 只读真机探测:打印归一化后的余额/窗口与档位(不回显任何密钥)
- 宿主半(
src/*.ts)零运行时依赖:只通过结构化接口消费 DSH 服务,产物里没有需要 profile 解析的裸包 import。 - 浏览器半(
src/client/*)以window.__ModuleLoader__.load({ id, factory })闭包工厂产出, 仅require平台模块表里的react/react/jsx-runtime。 - 浏览器 bundle 的样式全部走主题 CSS 变量(
--dsw-alias-*),明暗主题自动适配。
扩展新的提供商
一个文件 + 一行即可(src/providers/):
- 新建
src/providers/<name>.ts,导出Probe:id、label、match(routeId)、probe(host, signal)。 无凭证时throw new SkipSource('NO_CREDENTIAL', …),该来源就会自动隐藏。 - 返回
{ kind: 'balance', entries }或{ kind: 'quota', windows }(见src/protocol.ts)。 余额只给金额({ currency, total }),百分比只属于订阅窗口。 - 在
src/providers/index.ts的PROBES里加一条。
注意 match(routeId) 要与 DSH 的路由 id 对得上,否则自动模式永远不会读它——除非在 sources 里显式列出。
已知限制
- Moonshot 余额按官方规范实现;本机未配置该 Key,未经真机验证。
- 余额不做百分比与趋势预测:没有可信分母,低余额告警需要你配
balanceThresholds。 - 未在 harness 注册路由的账号默认不显示(这是刻意的;用
sources可强制显示)。 - 订阅套餐名/会员等级(如
ADVANCED)不展示:卡片只保留「还剩多少」这一个问题。 - OpenCode 的
/v1/usage未写入官方文档(由社区发现),字段可能变动;若某套餐没有该接口,卡片显示「无额度接口」。 - 热挂载要求行不带
config:;带配置的行需要重启dsh web。
License
MIT