Skip to content
dsh.fish
Bundle

dsh-usage-display

在 dsh(DeepSeek Harness)会话头部展示模型厂商余额/用量徽标的插件:内置 DeepSeek 余额、MiniMax Token Plan 与智谱 GLM Coding Plan 配额,适配器架构支持接入更多厂商;host 侧按轮次取数,经 SSE 同步到浏览器。

Source
deluo
stars
1 stars
License
MIT
Updated
Updated 4 days ago

Readme

# dsh-usage-display

[![npm version](https://img.shields.io/npm/v/dsh-usage-display)](https://www.npmjs.com/package/dsh-usage-display)
[![license](https://img.shields.io/npm/l/dsh-usage-display)](LICENSE)
[![dsh-plugin](https://img.shields.io/badge/topic-dsh--plugin-blue)](https://github.com/topics/dsh-plugin)

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

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

| 厂商     | `providerId` | 展示内容                                           |
| -------- | ------------ | -------------------------------------------------- |
| DeepSeek | `deepseek`   | 多币种账户余额(granted / topped-up 拆分)         |
| MiniMax  | `minimax`    | Token Plan 配额(5 小时窗口与周窗口)              |
| 智谱 GLM | `zhipu`      | Coding Plan 配额(5h、周限额、工具用量三个百分比) |

## 效果预览

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

![设置页“用量与余额”](https://github.com/deluo/dsh-usage-display/raw/main/docs/settings.png)

## 特性

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

## 前置要求

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

## 安装

### 从源码安装(开发)

```powershell
# 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`。

### 分发形式

- **npm**:`pnpm add dsh-usage-display`(或 `npm i dsh-usage-display`)后执行
  `dsh plugin --profile web add dsh-usage-display`;`prepare` 脚本会在安装时构建。
- **tarball**:`pnpm 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](cordis.patch.yml),用户可在 profile 或 home 级的
`cordis.patch.yml` 中覆盖(后应用层整行替换):

```yaml
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' 关闭
```

公共字段:

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

展示偏好(插件级 `display`):

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

DeepSeek 专属:

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

智谱专属:

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

MiniMax Token Plan 专属:

| 字段                   | 类型                    | 默认                                                                                 | 说明                                                                  |
| ---------------------- | ----------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| `routeIds`             | `string[]`              | `['minimax', 'minimax-cn', 'minimaxi', 'minimax-coding-plan', 'minimax-token-plan']` | dsh provider 路由 id → 本插件 providerId                              |
| `apiKeyEnv`            | `string`                | `MINIMAX_API_KEY`                                                                    | 首选凭证引用名                                                        |
| `apiKeyAliases`        | `string[]`              | 多个 Token Plan / Coding Plan / CN 别名                                              | 首选未配置时按顺序回退                                                |
| `baseURL`              | `string`                | `https://api.minimaxi.com`                                                           | 区域识别基准地址;含 `minimaxi.com` 走国内站,否则走 `api.minimax.io` |
| `badgeMetric`          | `'5h' \| 'weekly'`      | `'5h'`                                                                               | 徽标主指标                                                            |
| `resetTimeStyle`       | `'countdown' \| 'time'` | `'countdown'`                                                                        | 重置时间展示形态                                                      |
| `warnAbovePercent`     | `number \| 'off'`       | `80`                                                                                 | 用量超过此百分比徽标变黄                                              |
| `criticalAbovePercent` | `number \| '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 接口

调试与二次集成用:

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

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

```json
{
  "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`)。

## 项目结构

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

最小骨架:

```ts
// 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' 的快照
  }
}
```

## 开发

```powershell
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` 对空轮次(输入被拒等)也会发出,多一次无害的余额查询。
- 模型切换没有“点击即触发”的持久事件:高亮点击即生效,数据在下一轮次消息时才刷新。

## 参考

- [DeepSeek 余额接口文档](https://api-docs.deepseek.com/api/get-user-balance/)
- [GLM Coding Plan 配额接口的字段实测参考(cc-switch)](https://github.com/farion1231/cc-switch)

## License

MIT

Install

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

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
Source