Chuyển đến nội dung chính

dsh-llm-balance

Đã xác minh

dsh-llm-balance · v0.7.1 · MIT · Giao diện 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.

Cài đặt

dsh plugin add dsh-llm-balance

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Thẻ

Tác giả

Readme

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/):

  1. 新建 src/providers/<name>.ts,导出 Probe:id、label、match(routeId)、probe(host, signal)。 无凭证时 throw new SkipSource('NO_CREDENTIAL', …),该来源就会自动隐藏。
  2. 返回 { kind: 'balance', entries } 或 { kind: 'quota', windows }(见 src/protocol.ts)。 余额只给金额({ currency, total }),百分比只属于订阅窗口。
  3. 在 src/providers/index.ts 的 PROBES 里加一条。

注意 match(routeId) 要与 DSH 的路由 id 对得上,否则自动模式永远不会读它——除非在 sources 里显式列出。

已知限制

  • Moonshot 余额按官方规范实现;本机未配置该 Key,未经真机验证。
  • 余额不做百分比与趋势预测:没有可信分母,低余额告警需要你配 balanceThresholds。
  • 未在 harness 注册路由的账号默认不显示(这是刻意的;用 sources 可强制显示)。
  • 订阅套餐名/会员等级(如 ADVANCED)不展示:卡片只保留「还剩多少」这一个问题。
  • OpenCode 的 /v1/usage 未写入官方文档(由社区发现),字段可能变动;若某套餐没有该接口,卡片显示「无额度接口」。
  • 热挂载要求行不带 config:;带配置的行需要重启 dsh web。

License

MIT