跳过主要内容
D

dsh-usage-display

deluo/dsh-usage-display

多厂商余额/用量徽标(内置 DeepSeek 余额与智谱 GLM Coding Plan 配额):host 侧按轮次事件取数,经 SSE 同步到浏览器展示。

安装

dsh plugin --profile web add github:deluo/dsh-usage-display

README

dsh-usage-display

npm version license dsh-plugin

在 dsh Web 界面显示模型厂商余额 / 套餐用量的会话头部徽标插件。host 进程从各家 厂商官方接口取数并缓存,浏览器侧只读本地路由渲染;API key 始终留在 host, 不会下发到浏览器。

内置三家厂商,按多厂商适配器架构组织,新增厂商只需实现一个 adapter:

厂商providerId展示内容
DeepSeekdeepseek多币种账户余额(granted / topped-up 拆分)
MiniMaxminimaxToken Plan 配额(5 小时窗口与周窗口)
智谱 GLMzhipuCoding Plan 配额(5h、周限额、工具用量三个百分比)

效果预览

设置页(Settings → Plugins → “用量与余额”)可按通用显示 / DeepSeek / MiniMax / 智谱 GLM 分区调整展示偏好与告警阈值,保存后立即生效:

设置页“用量与余额”

特性

  • 会话头部用量徽标:展示当前模型对应厂商的主指标,点击展开明细面板并支持手动刷新。
  • 可配置告警阈值:余额低于阈值、配额用量超过阈值时,徽标状态点与进度条按 warn(黄)/ critical(红)两级染色。
  • 可配置进度可视化:percent 类指标在徽标与面板中支持文本、环形图、条形图三种 形态(display.panelStyle),徽标可用 auto 按指标类型自动选择。
  • 设置页热更新:Settings → Plugins → “用量与余额”页可即时调整展示偏好与告警 阈值,保存后 host 重建运行时并重新取数,无需重启;接入类字段仍走 cordis 配置。
  • 真正产生模型调用后自动取数:turn/startturn/end 各全量刷新一次,切换模型时 只定向刷新新选中的厂商。
  • host 刷新落定后通过 SSE 通知浏览器重读本地缓存;浏览器绝不直连厂商 API。
  • 每家厂商独立缓存与故障隔离:一家失败不影响其他家,失败时保留上次成功值并标注更新时间。
  • 凭证只以引用名出现在配置中,每次取数由 dsh 凭证服务实时解析,轮换后下次请求即生效。

前置要求

  • Node.js(与运行中的 dsh 相同的版本即可)
  • pnpm
  • dsh CLI ≥ 0.1.0-rc.6

安装

从源码安装(开发)

# 1. 安装依赖并构建(产出 lib/index.js 与 lib/client.js)
pnpm install
pnpm run build

# 2. 安装进 web profile(在插件目录内执行 `add .` 等价;-w 按 workspace 链接)
dsh plugin --profile web add -w D:/Code/dsh-usage-display

# 3. 验证组合层,然后启动
dsh web --dump-config
dsh web

Windows 下路径务必用正斜杠,反斜杠会被解析成非法包名(如 Codedsh-usage-display)。 -w 需要保留:profile 自带 pnpm-workspace.yaml

卸载:dsh plugin --profile web remove dsh-usage-display

分发形式

  • npmpnpm add dsh-usage-display(或 npm i dsh-usage-display)后执行 dsh plugin --profile web add dsh-usage-displayprepare 脚本会在安装时构建。
  • tarballpnpm pack 后执行 dsh plugin --profile web add ./dsh-usage-display-<version>.tgz, 分发的是预构建产物,用户侧无需构建授权。
  • Git 安装dsh plugin --profile web add github:deluo/dsh-usage-display。源码包由 prepare 脚本构建;pnpm ≥ 10 默认拒绝,需按提示在该 profile 的 pnpm-workspace.yaml 中给 allowBuilds 授权后重试。

配置

默认配置见 cordis.patch.yml,用户可在 profile 或 home 级的 cordis.patch.yml 中覆盖(后应用层整行替换):

dsh-usage-display:
  display:
    badgeStyle: 'auto' # auto | text | ring | bar;徽标上的进度形态
    panelStyle: 'ring' # text | ring | bar;面板里 percent 指标的形态
    showResetCountdown: true # 徽标配额文案是否带“距重置”倒计时
  providers:
    deepseek:
      enabled: true # 关闭后显示“已停用”,不再取数
      routeIds: ['deepseek-official'] # dsh provider 路由 id → 本插件 providerId(providerId 自动补入)
      apiKeyEnv: 'DEEPSEEK_API_KEY' # 凭证引用名,不是 key
      baseURL: 'https://api.deepseek.com'
      badgeCurrency: 'CNY' # 徽标主币种;账户无此币种时保持接口返回顺序
      warnBelow: 10 # 余额低于此值 → warn;'off' 关闭
      criticalBelow: 5 # 余额低于此值 → critical;'off' 关闭
    minimax:
      enabled: true
      routeIds: ['minimax', 'minimax-cn', 'minimaxi', 'minimax-coding-plan', 'minimax-token-plan']
      apiKeyEnv: 'MINIMAX_API_KEY' # 只存引用名,key 由 host 运行时解析
      apiKeyAliases:
        [
          'MINIMAX_TOKEN_PLAN_API_KEY',
          'MINIMAX_CODING_PLAN_API_KEY',
          'MINIMAX_CODING_API_KEY',
          'MINIMAX_CN_API_KEY',
          'MINIMAX_API_KEY',
          'MINIMAX_API_TOKEN',
        ]
      baseURL: 'https://api.minimaxi.com' # 含 minimaxi.com 走国内站,否则走 api.minimax.io
      badgeMetric: '5h' # 徽标主指标:5h | weekly
      resetTimeStyle: 'countdown' # 重置时间:countdown 倒计时 | time 本地时间点
      warnAbovePercent: 80 # 用量超过此百分比 → warn;'off' 关闭
      criticalAbovePercent: 90 # 用量超过此百分比 → critical;'off' 关闭
    zhipu:
      enabled: true
      routeIds: ['zai-coding-cn', 'zai-coding', 'zai', 'glm', 'zhipu', 'bigmodel', 'zhipuai']
      apiKeyEnv: 'ZHIPU_API_KEY'
      apiKeyAliases:
        [
          'ZAI_CODING_CN_API_KEY',
          'ZAI_CODING_API_KEY',
          'GLM_API_KEY',
          'ZAI_API_KEY',
          'BIGMODEL_API_KEY',
        ]
      baseURL: 'https://open.bigmodel.cn'
      authStyle: 'raw' # 'raw' | 'bearer'
      badgeMetric: '5h' # 徽标主指标:5h | weekly | tools
      resetTimeStyle: 'countdown' # 重置时间:countdown 倒计时 | time 本地时间点
      warnAbovePercent: 80 # 用量超过此百分比 → warn;'off' 关闭
      criticalAbovePercent: 90 # 用量超过此百分比 → critical;'off' 关闭

公共字段:

字段类型默认说明
enabledbooleantrue关闭后徽标显示“用量已停用”,不再取数
routeIdsstring[][]dsh provider 路由 id → 本插件 providerId,用于模型切换联动;providerId 本身总会自动加入映射

展示偏好(插件级 display):

字段类型默认说明
badgeStyle'auto' | 'text' | 'ring' | 'bar''auto'徽标进度形态;auto 对 percent 指标用迷你条形图、金额保持纯文本
panelStyle'text' | 'ring' | 'bar''ring'面板中 percent 指标的形态
showResetCountdownbooleantrue徽标配额文案是否带“距重置”倒计时

DeepSeek 专属:

字段类型默认说明
routeIdsstring[]['deepseek-official']DeepSeek 官方 harness 适配器上报的 provider 路由 id;deepseek 作为 providerId 仍会自动加入
apiKeyEnvstringDEEPSEEK_API_KEY凭证引用名,请求 GET {baseURL}/user/balance 时使用
baseURLstringhttps://api.deepseek.com余额接口前缀
badgeCurrencystringCNY徽标主币种(按接口返回的 currency 匹配,大小写不敏感);无此币种时保持接口顺序
warnBelownumber | 'off'10余额低于此值徽标变黄
criticalBelownumber | 'off'5余额低于此值徽标变红

智谱专属:

字段类型默认说明
apiKeyEnvstringZHIPU_API_KEY首选凭证引用名
apiKeyAliasesstring[]见上首选未配置时按顺序回退;指向模型适配器已在用的引用名即可直接复用其 key
baseURLstringhttps://open.bigmodel.cn实际请求 host 按域名路由:含 bigmodel.cnopen.bigmodel.cn,否则 api.z.ai
authStyle'raw' | 'bearer''raw'首选鉴权头风格;401/403 自动换另一种重试,非法值在配置层直接报错
badgeMetric'5h' | 'weekly' | 'tools''5h'徽标主指标
resetTimeStyle'countdown' | 'time''countdown'重置时间展示:countdown 显示紧凑倒计时(如 2h13m2d3h);time 显示本地时间点(如 15:30明天 08:30
warnAbovePercentnumber | 'off'80用量超过此百分比徽标变黄
criticalAbovePercentnumber | 'off'90用量超过此百分比徽标变红

MiniMax Token Plan 专属:

字段类型默认说明
routeIdsstring[]['minimax', 'minimax-cn', 'minimaxi', 'minimax-coding-plan', 'minimax-token-plan']dsh provider 路由 id → 本插件 providerId
apiKeyEnvstringMINIMAX_API_KEY首选凭证引用名
apiKeyAliasesstring[]多个 Token Plan / Coding Plan / CN 别名首选未配置时按顺序回退
baseURLstringhttps://api.minimaxi.com区域识别基准地址;含 minimaxi.com 走国内站,否则走 api.minimax.io
badgeMetric'5h' | 'weekly''5h'徽标主指标
resetTimeStyle'countdown' | 'time''countdown'重置时间展示形态
warnAbovePercentnumber | 'off'80用量超过此百分比徽标变黄
criticalAbovePercentnumber | 'off'90用量超过此百分比徽标变红

warn* / critical* 配反(如 warnBelow 小于 criticalBelow)时按更严格的方向归一; 全部设为 'off' 关闭该厂商的告警染色。

配置里从不出现 key 本身。apiKeyEnv / apiKeyAliases 都是凭证引用名,host 每次取数时 经 ctx.credentials.resolve() 解析:进程环境优先,其次 $DSH_HOME/.credentials.yaml (Models 页 / dsh credentials set 写入),再以项目与用户的 .env 回退。模型侧已配置的 智谱 key 可以直接复用——把 apiKeyEnv 指向模型适配器所用的引用名(dsh web --dump-config 可查到);托管存储里的 key 变更下次取数即生效,进程 env 的快照在启动时冻结。

使用

  • 打开会话后,头部操作区出现用量徽标:DeepSeek 显示余额金额,MiniMax 与智谱显示 配额窗口的已用百分比与重置时间;命中告警阈值时状态点与进度条变黄/红。
  • 徽标左侧的状态点只在纯文本/金额模式与异常状态时出现;有迷你条形图或环形图时 颜色信息已由图形表达,状态点自动隐藏。
  • 点击徽标展开当前模型对应厂商的明细面板,含状态、指标明细(金额行 / 配额表)、 更新时间与“刷新”按钮;配额指标按 display.panelStyle 渲染为环形图、条形图或 纯数字表格。手动刷新会等待真实取数落定。
  • MiniMax 与 GLM 配额重置时间都支持两种形态:countdown 显示 2h13m / 2d3h 等 倒计时,time 显示 15:30明天 08:30周三 08:30 等本地时间点。
  • 在 Settings → Plugins → “用量与余额”页按“通用显示 / DeepSeek / MiniMax / 智谱 GLM”分区, 保存后立即生效。该页写入的是用户设置文档;enabled / routeIds / apiKeyEnv / baseURL / authStyle 等接入类字段不在此暴露,仍在 cordis.patch.yml 中维护。
  • 切换模型时徽标高亮立即跟随(读模型选择目录 store);取数仍由轮次事件驱动, 切换后尚未发消息时展示的是缓存快照。
  • 当前模型的厂商未接入(routeIds 未覆盖)时徽标进入中性态显示“其他厂商”,面板列出 全部已接入厂商。
  • 每家厂商有五态:loading / ok / unconfigured / disabled / error;失败时展示 上次成功值并标注原更新时间。

本地 HTTP 接口

调试与二次集成用:

GET /plugins/dsh-usage-display/balance[?refresh=1]   # 聚合快照;refresh=1 强制真实取数
GET /plugins/dsh-usage-display/events                # SSE:balance-updated 事件 + 15s 心跳

/balance 只接受 GET,其余方法返回 405。响应示例:

{
  "providers": [
    {
      "providerId": "deepseek",
      "displayName": "DeepSeek",
      "kind": "balance",
      "status": "ok",
      "isAvailable": true,
      "metrics": [
        {
          "key": "cny",
          "label": "CNY 余额",
          "kind": "amount",
          "remaining": 110.0,
          "unit": "CNY",
          "detail": { "granted": "10.00", "toppedUp": "100.00" },
          "severity": "ok"
        }
      ],
      "fetchedAt": "2026-08-18T10:00:00.000Z",
      "cached": false,
      "severity": "ok"
    },
    {
      "providerId": "minimax",
      "displayName": "MiniMax Token Plan",
      "kind": "quota",
      "status": "ok",
      "isAvailable": true,
      "plan": "Max",
      "metrics": [
        {
          "key": "5h",
          "label": "5h限额",
          "kind": "percent",
          "used": 28,
          "resetsAt": "2026-08-18T13:00:00.000Z"
        },
        { "key": "weekly", "label": "周限额", "kind": "percent", "used": 45 }
      ],
      "fetchedAt": "2026-08-18T10:00:00.000Z",
      "cached": false
    },
    {
      "providerId": "zhipu",
      "displayName": "智谱 GLM",
      "kind": "quota",
      "status": "ok",
      "isAvailable": true,
      "plan": "pro",
      "metrics": [
        {
          "key": "5h",
          "label": "5h限额",
          "kind": "percent",
          "used": 28,
          "resetsAt": "2026-08-18T13:00:00.000Z"
        },
        { "key": "weekly", "label": "周限额", "kind": "percent", "used": 12 },
        { "key": "tools", "label": "工具用量", "kind": "percent", "used": 8 }
      ],
      "fetchedAt": "2026-08-18T10:00:00.000Z",
      "cached": false
    }
  ],
  "activeBySession": { "<会话id>": { "providerId": "zhipu", "model": "glm-4.7" } },
  "routes": {
    "deepseek-official": "deepseek",
    "deepseek": "deepseek",
    "minimax": "minimax",
    "minimax-cn": "minimax",
    "zai": "zhipu",
    "glm": "zhipu"
  },
  "display": { "badgeStyle": "auto", "panelStyle": "ring", "showResetCountdown": true },
  "providerDisplay": {
    "minimax": { "resetTimeStyle": "countdown" },
    "zhipu": { "resetTimeStyle": "countdown" }
  }
}

普通读只返回本地缓存(cached: true),不会触发厂商 API;?refresh=1 等待取数并返回 新鲜快照(cached: false)。

项目结构

dsh-usage-display/
├── package.json          # 双面插件声明(dsh.bundle + dsh.client)
├── cordis.patch.yml      # host loader 行与默认配置
├── tsconfig.json         # 类型检查配置
├── tsdown.config.ts      # host / client 双 bundle 构建
├── scripts/
│   ├── smoke-client.mjs  # 客户端 bundle 冒烟测试
│   └── smoke-host.mjs    # host 编排冒烟测试
└── src/
    ├── index.ts          # host 入口:HTTP 路由 + SSE + 会话事件编排
    ├── types.ts          # 两侧共用的快照 / 指标契约
    ├── settings.ts       # 设置页可调子集目录(各厂商 tunable 类型聚合)
    ├── core/
    │   ├── registry.ts       # 厂商注册表:schema / tunable 抽取合并 / 展示偏好聚合
    │   └── usage-service.ts  # 缓存 / in-flight 去重 / 故障隔离
    ├── providers/
    │   ├── types.ts          # ProviderAdapter SPI
    │   ├── deepseek.ts       # DeepSeek 余额适配器
    │   ├── minimax.ts        # MiniMax Token Plan 配额适配器
    │   └── zhipu.ts          # 智谱 GLM 配额适配器
    └── client/
        ├── index.ts      # 浏览器入口:读取快照 + 注册徽标
        ├── routes.ts     # 本地路由常量(与 host 侧对齐)
        ├── usage-store.ts
        ├── UsageBadge.tsx
        └── SettingsTab.tsx # 设置页(Plugins 分区 tab)

接入新厂商

  1. src/providers/<id>.ts 实现 ProviderModulefetch() 负责取数并把 wire 格式 归一化为 ProviderSnapshot(异常在内部吞掉并返回 error 快照,正常路径不 throw); 指标 kind 支持 amount / window / percent。如需设置页热更新,模块里声明 tunable(schema + extract + merge)与可选的 display 偏好,字段默认值只在模块内 维护一份。
  2. src/index.tscreateRegistry([deepseek, minimax, zhipu]) 中登记模块,并在 SETTINGS_SCHEMA 里补一行该厂商的 tunable.schema
  3. src/settings.ts 补该厂商的可调类型,客户端 SettingsTab.tsx 按需要增加分区。
  4. cordis.patch.yml 补该厂商的默认配置(只写需要覆盖的字段,其余交给 schema 默认值)。
  5. pnpm run build 后按开发流程验证。

最小骨架:

// src/providers/acme.ts
// (z 来自 @deepseek-ai/schemastery;withCommonConfig / ProviderModule / ProviderAdapter
//   来自 providers/types.ts,此处省略 import 语句)
export const acme: ProviderModule<AcmeConfig, AcmeTunable, 'acme'> = {
  id: 'acme',
  displayName: 'Acme',
  kind: 'balance', // 或 'quota'
  config: withCommonConfig({
    apiKeyEnv: z.string().default('ACME_API_KEY'),
    baseURL: z.string().default('https://api.acme.example'),
    // 如需设置页热更新:把可调字段与 tunable schema 共用一份定义
  }),
  // tunable / display 可选,缺省即没有设置页热更新与专属展示偏好
  create: (ctx, config) => new AcmeAdapter(ctx, config),
}

class AcmeAdapter implements ProviderAdapter {
  readonly id = 'acme'
  readonly displayName = 'Acme'
  readonly kind = 'balance' as const
  async fetch(): Promise<ProviderSnapshot> {
    // 取数 + 归一化;错误时返回 status: 'error' 的快照
  }
}

开发

pnpm install
pnpm run build       # 产出 lib/index.js(host ESM)与 lib/client.js(浏览器 loader 闭包)
pnpm run typecheck   # tsc --noEmit(基于 npm 发布的 @deepseek-ai 类型)
pnpm run format      # prettier 统一格式化(提交前执行;format:check 仅检查)
pnpm run smoke       # 客户端 bundle 冒烟:mock 模块加载器 + mock ctx,无需浏览器
pnpm run smoke:host  # host 冒烟:多厂商编排、模型切换联动、鉴权重试,无需 key

迭代循环:改 host 半 → pnpm run build → 重启 dsh web;改 client 半 → pnpm run build → 刷新页面。本地 node_modules 里的 @deepseek-ai/* 来自 npm 发布版本,若运行中的 dsh 版本接口有变化,请同步 peerDependencies 后重新 pnpm install

已知限制

  • 余额为异步结算,徽标展示的是近实时快照,面板标注原更新时间。
  • 本地路由与 SSE 端点不带鉴权(dsh webServer 的设计如此);dsh web 默认绑定 127.0.0.1,若绑定 0.0.0.0 会把这些只读端点暴露到网络。
  • turn/end 对空轮次(输入被拒等)也会发出,多一次无害的余额查询。
  • 模型切换没有“点击即触发”的持久事件:高亮点击即生效,数据在下一轮次消息时才刷新。

参考

License

MIT

相关插件