@zoytown/dsh-billing
English | 中文

@zoytown/dsh-billing 是一个用于查看 DeepSeek API 账户余额的 DeepSeek Harness(dsh)插件。它读取平台的 GET /user/balance 端点,并以三种方式呈现结果:侧边栏底部的余额胶囊、设置里的「余额」页、以及 /balance 命令。它不注册任何面向模型的工具,也不追加任何 session 事件,因此挂载它对会话零成本。
| 侧边栏 | 设置 → 余额 |
|---|---|
|  |  |
平台实际提供了什么
只有当前余额。没有用量或消费明细端点——/usage 与 /dashboard/billing/usage 均返回 404——所以本包报告的是还剩多少,而非花了多少。任何「本次会话花费」都只能是基于 token 数的本地估算,那是另一件事,刻意不在本包范围内。
{
"is_available": true,
"balance_infos": [
{ "currency": "CNY", "total_balance": "25.00", "granted_balance": "0.00", "topped_up_balance": "25.00" }
]
}balance_infos 是数组——一个账户可能同时持有 CNY 和 USD——本包所有消费者都完整渲染,而不是只取第一项。
安装
dsh plugin --profile web add @zoytown/dsh-billing从 npm 安装拿到的是预构建产物,无需任何构建授权。从 git 安装(github:zoyluoblue/deepseek-harness-billing)只会拉到源码而不会触发构建,暂不支持,见[已知限制](#已知限制)。
bundle 插入三行配置——服务(同时也是浏览器行)、/balance 命令、以及 UI 的数据路由。三者互不依赖;在自己 profile 的 cordis.patch.yml 里按 id 禁用任意一行即可。
配置
| 键 | 默认值 | 含义 |
|---|---|---|
apiKey | 省略 | 字面量密钥。优先用 apiKeyEnv,避免密钥进入配置文件;非空字面量优先级更高。带 role('secret'),因此永不随 describe() 响应外泄。 |
apiKeyEnv | DEEPSEEK_API_KEY | 凭据引用,每次读取时经 ctx.credentials 解析;该接缝不存在时回退到启动环境。复用 LLM 适配器的同一个密钥——本包不新增密钥。 |
baseURL | https://api.deepseek.com | 余额端点基址,会追加 /user/balance。回退到 $DEEPSEEK_BILLING_BASE_URL。 |
cacheTtlMs | 60000 | 一次成功快照的保鲜时长。 |
timeoutMs | 10000 | 单次请求的中止上限。 |
lowBalanceThreshold | 10 | 低于此值告警。0 关闭该下限,仅保留平台自身的 is_available 判定。 |
为什么不复用 $DEEPSEEK_BASE_URL
那个变量用于引导 chat-completions 适配器,而用户完全有正当理由把它指向网关或自建端点。/user/balance 仅存在于官方平台,复用它会让一套正常工作的代理配置在侧边栏里变成永久 404。因此该端点使用独立变量——这与 dsh-web-search-deepseek 为搜索单独设变量的处理一致。
若 baseURL 不提供该路径,失败会归类为 ENDPOINT_UNAVAILABLE,并在文案中点明这个原因,而不是抛出一个泛化的 HTTP 错误。
缓存
策略集中在一处,因为三个界面可能同时发问,而这是一个账户端点、官方未文档化其限流:
- 一次成功快照在
cacheTtlMs内直接复用; - 并发请求共享同一个在途请求,且任一调用方的取消不会取消其他人共同等待的那次读取;
- 失败永不进缓存——下次询问会重试,同时保留上一次成功的快照,供界面在错误旁一并展示;
- 本引用的
credentials/updated提交后立即失效缓存。
不做后台轮询。新鲜度由消费者的询问驱动。
错误
BillingError.code 是界面的分支依据。失败绝不能渲染成余额为零:「没钱了」和「查不到」是两件不同的事。
| Code | 触发原因 |
|---|---|
CREDENTIAL_MISSING | 无任何来源提供该引用;不会发出请求。 |
UNAUTHORIZED | HTTP 401/403。 |
ENDPOINT_UNAVAILABLE | HTTP 404——几乎总是 baseURL 指向了网关。 |
RATE_LIMITED | HTTP 429。 |
HTTP_ERROR | 其它非 2xx。 |
MALFORMED_RESPONSE | HTTP 200 但响应体不是余额文档。 |
NETWORK_ERROR | 传输失败、超时、基址无法解析,或重定向被拒。 |
ABORTED | 调用方取消。 |
两个朴素客户端会踩、而本包已处理的细节:密钥无效时端点返回 JSON error.message,但完全没有 Authorization 头时返回的是纯文本,因此响应体不会被无条件按 JSON 解析;以及使用 redirect: 'error' 在联系 Location 目标之前拒绝重定向,因为跟随重定向会把 bearer token 带到另一个主机。
命令
| 命令 | 效果 |
|---|---|
/balance | 渲染余额,使用缓存。 |
/balance refresh | 同上,但忽略未过期的缓存。 |
Web UI
两个浏览器界面,共用同一个控制器:在胶囊已经在读取时打开设置页,会加入那次读取而不是再发一个请求。
| 界面 | 挂载点 | 展示内容 |
|---|---|---|
| 侧边栏胶囊 | sidebar.footer.action | 设置按钮旁的金额;56px 折叠轨下退为 32px 图标 + 状态角标 |
| 设置 → 余额 | settings.section | 全部币种、充值/赠金拆分、当前阈值 |
胶囊区分五种状态,整个设计的支点是:失败渲染破折号,绝不渲染数字——「没钱了」和「查不到」不能长得一样。unconfigured 用虚线轮廓且完全不出数字;low 是唯一允许抢注意力的状态,且琥珀色始终与警告三角同时出现,颜色永不是唯一信号。折叠轨的角标只在 low 和 error 出现:余额健康时没有理由在余光里闪。
样式只使用 --dsw-alias-* 语义 token——本插件不定义主题、不写任何明暗选择器,两套主题都由 ui-theme 继承而来。
数据通道
浏览器半边读取 billing-route 行提供的 GET /billing/balance。之所以用普通的 webserver 路由而非 Typert Remote:Remote 需要主仓代码生成的调用描述符,仓外的包无法产出。
该路由返回的是账户数据,因此自带浏览器信任围栏,防御本地 HTTP API 会打开的两条「代理人混淆」路径——DNS rebinding(页面把自己的域名解析到 127.0.0.1,套接字打到本服务而 Host 写的是攻击者域名)与普通的跨站读取。Host 必须是 loopback 或列在 trustedHosts 中,且浏览器附带的 Fetch-Metadata 必须表明同源。它比主仓自己的 /api 围栏更严格:不推导任何 LAN IP 授权,loopback 之外的一切都必须显式声明。
- id: billing-route
config:
trustedHosts: [] # 仅当部署到本机之外时,填入 "host" 或 "host:port"这不是身份认证。它阻止浏览器被当作通往 loopback 的代理,但不识别调用方。
常见问题
DeepSeek 余额怎么查?
调用 GET https://api.deepseek.com/user/balance,带 Authorization: Bearer <DEEPSEEK_API_KEY> 请求头。返回 is_available 和一个 balance_infos 数组,每个币种一条。本插件把这个端点接进 DeepSeek Harness,让余额出现在侧边栏、设置页和 /balance 命令里。
dsh 插件怎么安装?
dsh plugin --profile <名称> add <包名>。本插件:
dsh plugin --profile web add @zoytown/dsh-billing该命令会把包装进 profile,并把它的 bundle 追加到 profile 的 dsh.profile.bundles 列表。卸载用 dsh plugin --profile web remove @zoytown/dsh-billing。
为什么余额显示的是「—」而不是数字?
因为这次读取失败了——插件绝不会显示一个它并不掌握的数字。破折号表示「查不到」,这与「余额为零」是刻意区分开的两种状态。打开 设置 → 余额 可以看到分类后的具体原因(密钥无效、端点不存在、被限流、网络错误)。
这个插件能看花了多少钱吗?
不能。DeepSeek 平台没有提供用量或消费明细端点——/usage 与 /dashboard/billing/usage 均返回 404——所以本插件只报告剩余余额。任何「本次会话花费」都只能是基于 token 数的本地估算,本包刻意不做。
能配合网关或自建 DeepSeek 端点用吗?
聊天补全可以,余额查询不行。/user/balance 只存在于官方平台,因此本插件使用独立的 baseURL(回退到 $DEEPSEEK_BILLING_BASE_URL),绝不复用 $DEEPSEEK_BASE_URL。若 baseURL 不提供该路径,会失败为 ENDPOINT_UNAVAILABLE 并在文案里点明这个原因。
需要单独再配一个 API key 吗?
不需要。它通过 ctx.credentials 解析的是 LLM 适配器用的同一个 DEEPSEEK_API_KEY 凭据引用。在「模型」页轮换密钥后,下一次余额查询即刻生效,无需重启。
会消耗 token 吗?
不会。它不注册面向模型的工具、不贡献 system prompt 段落、不追加 session 事件。命令结果由 UI 适配器直接渲染,永不进入模型历史。
Model Experience
无。本包不注册工具、不贡献 system prompt 段落、不追加 session 事件。命令结果由 UI 适配器直接渲染,永不进入模型历史。
#### Token effect
零。注册与调用都不会到达任何模型请求。
#### KV Cache effect
无;本包不向请求前缀写入任何内容。
已知限制
- 告警下限是一个裸数字,按每币种、以该币种自身单位比较。 在 CNY/USD 混合账户上,一个按 CNY 设定的
10也会把$8.40标记为告警。正确解法是 per-currency 映射,推迟到真有多币种账户需要时再做;在此之前可用lowBalanceThreshold: 0关闭下限。 - 不提供消费/用量报告。 平台未暴露此类端点,见[上文](#平台实际提供了什么)。
- 不支持 git 安装。 本包未提供
prepare脚本,dsh plugin add github:…只会装到未构建的源码。请从 npm 安装,或使用pnpm pack产出的 tarball——两者都是预构建产物,无需构建授权。 - 胶囊点击是刷新,不是跳转。 点它会重新读取余额;从它直接打开「余额」设置页需要设置外壳暴露一个「打开设置」服务,而目前没有。
- 胶囊只显示一个币种。 侧边栏胶囊放不下多个,因此显示平台列出的第一个币种,完整列表在设置页。它绝不跨币种求和——把 CNY 加到 USD 上是个凭空捏造的数字。
- 浏览器半边假定同源服务。 它请求相对路径,Web 应用满足这一点;以
file://加载并通过 IPC 桥接 fetch 的 Electron 外壳需要另外的传输方式。 - 余额新鲜度由拉取驱动。 没有轮询,两次询问之间发生的余额下降不会被察觉,直到下一次有人询问。
- 金额不做二次格式化。 平台返回的十进制字符串原样透传到展示层,因此平台若以非预期形态报告某币种,界面就照该形态呈现。
许可
MIT