dsh-subusage 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -196
- package/lib/client.js +18 -6
- package/lib/index.js +58 -15
- package/lib/volcengine.js +55 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,276 +1,330 @@
|
|
|
1
|
-
# dsh-subusage
|
|
1
|
+
# dsh-subusage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**DeepSeek Harness 订阅用量显示插件。** 在模型选择器旁显示当前模型商的订阅余量药丸,点开可看各周期用量、重置时间与套餐;设置页集中管理 30 条提供商 / 区域路由的检测开关与凭据;并以只读额度 API 供其他插件与 Agent 查询。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> **English.** dsh-subusage shows subscription quota and balance for the AI providers you already use, as a pill next to the model selector in DeepSeek Harness. Provider switches and credentials live in one settings page, and other plugins or agents can read the same snapshot through a read-only quota API.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-subusage)
|
|
10
|
+
[](#license--security--许可与安全)
|
|
11
|
+
[](#compatibility--兼容性)
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|---|---|
|
|
13
|
-
| npm | `dsh-subusage`;支持 npm 源的插件管理器可使用该包名 |
|
|
14
|
-
| GitHub | `https://github.com/KouzakiUmi/dsh-subusage` |
|
|
15
|
-
| 安装包 | [最新 GitHub Release 安装包](https://github.com/KouzakiUmi/dsh-subusage/releases/latest/download/dsh-subusage.tgz) |
|
|
16
|
-
|
|
17
|
-
只需取得 npm 最新压缩包时可运行 `npm pack dsh-subusage`;指定版本前可用 `npm view dsh-subusage version` 核对发布结果。普通 `npm install` 或下载压缩包并不等于已经在 DSH 中启用插件;具体安装参数以目标部署的帮助信息为准。
|
|
13
|
+
## Overview / 功能
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
| 能力 | 解决什么问题 | 入口 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| 余量药丸 | 不用切到各家控制台,选中模型就知道这家还剩多少 | 模型选择器左侧,槽位 `conversation.input.right` |
|
|
18
|
+
| 设置页 | 开关与凭据集中管理;缺凭据、Cookie 过期时把修复入口直接送到卡片上 | 设置 → 订阅用量 → 提供商管理 |
|
|
19
|
+
| 只读额度 API | 其他插件与 Agent 复用同一份额度数据,不必各自对接各家接口 | Agent 工具 `subusage_quota`、Host 服务 `subUsage.quota()` |
|
|
20
|
+
|
|
21
|
+
药丸示例:`✓ 余 87%`、`⚠ 7d 余 12%`、`✕ 7d 已达限额`。多窗口取最差一窗,点击查看完整明细;成功结果按提供商缓存 **60 秒**,手动刷新可跳过普通 TTL(仍遵守上游限流退避与 `Retry-After`)。
|
|
22
|
+
|
|
23
|
+
本插件**不注册模型路由**,药丸按 provider id 匹配;因此使用哪个插件提供该模型路由不影响匹配,id 对不上时才不显示。
|
|
24
|
+
|
|
25
|
+
## 支持的数据 / Supported data
|
|
26
|
+
|
|
27
|
+
共 **30 条 provider id**:新安装**默认开启 11 条、默认关闭 19 条**(数字与清单取自 [`lib/index.js`](lib/index.js) 顶部的 `PROVIDERS`,展示顺序与短名取自 [`lib/client.js`](lib/client.js) 顶部的 `PROVIDER_META` / `PROVIDER_ORDER`)。
|
|
28
|
+
|
|
29
|
+
| 提供商 | provider id | 额度类型 | 鉴权 | 默认 |
|
|
30
|
+
|---|---|---|---|---|
|
|
31
|
+
| DeepSeek | `deepseek` | 余额(账户) | Bearer,与推理同一把 Key | 开 |
|
|
32
|
+
| Z.ai(中国) | `zai-coding-cn` | 订阅窗口 | Bearer(团队档另加组织 / 项目请求头) | 开 |
|
|
33
|
+
| Kimi Coding | `kimi-coding` | 订阅窗口 | Bearer | 开 |
|
|
34
|
+
| Xiaomi MiMo | `xiaomi-token-plan-cn` | 周期额度池 | 官方平台 Cookie(含登录流程) | 开 |
|
|
35
|
+
| OpenCode Go | `opencode-go` | 订阅窗口 | Bearer | 开 |
|
|
36
|
+
| Command Code | `commandcode` | 5 小时 / 周 / 月额度池 | 提供方插件的凭据链 | 开 |
|
|
37
|
+
| SuperGrok | `xai-oauth` | 周期池 | 只读 Grok CLI 的 OAuth 登录文件 | 开 |
|
|
38
|
+
| Codex (ChatGPT) | `openai-codex` | ChatGPT 订阅窗口 | 只读 Codex CLI 的 OAuth 登录文件 | 开 |
|
|
39
|
+
| MiniMax(中国) | `minimax-cn` | 短周期 / 周 | Bearer | 开 |
|
|
40
|
+
| ARK Agent Plan (arkcli) | `arkcli-agent-plan` | 订阅窗口(AFP) | IAM AK/SK 签名 | 开 |
|
|
41
|
+
| ARK Coding Plan (arkcli) | `arkcli-coding-plan` | 订阅窗口 | IAM AK/SK 签名 | 开 |
|
|
42
|
+
| Z.ai(国际) | `zai-coding` | 订阅窗口 | 裸 `authorization`,区域凭据独立 | 关 |
|
|
43
|
+
| Synthetic | `synthetic` | 滚动订阅池 | Bearer | 关 |
|
|
44
|
+
| NanoGPT | `nanogpt` | 日 / 周配额 + 余额 | Bearer(余额端点用 `x-api-key`) | 关 |
|
|
45
|
+
| MiniMax(国际) | `minimax` | 短周期 / 周 | Bearer | 关 |
|
|
46
|
+
| ARK Agent Plan Team (arkcli) | `arkcli-agent-plan-team` | 席位额度(AFP) | IAM AK/SK 签名 | 关 |
|
|
47
|
+
| ARK Coding Plan Team (arkcli) | `arkcli-coding-plan-team` | 席位额度 | IAM AK/SK 签名 | 关 |
|
|
48
|
+
| Ark Coding Plan(中国,旧插件路由) | `ark-coding-plan-cn` | 订阅窗口 | IAM AK/SK 签名 | 关 |
|
|
49
|
+
| Ark Agent Plan(中国,旧插件路由) | `ark-agent-plan-cn` | 订阅窗口(AFP) | IAM AK/SK 签名 | 关 |
|
|
50
|
+
| Ark Coding Plan(BytePlus,旧插件路由) | `ark-coding-plan-byteplus` | 订阅窗口 | IAM AK/SK 签名 | 关 |
|
|
51
|
+
| SiliconFlow | `siliconflow` | 余额 | Bearer,与推理同一把 Key | 关 |
|
|
52
|
+
| OpenRouter | `openrouter` | 限额窗口 + 余额 | 推理 Key(余额需 management / provisioning key) | 关 |
|
|
53
|
+
| Novita AI | `novita` | 余额 | Bearer | 关 |
|
|
54
|
+
| Hyperbolic | `hyperbolic` | 余额 | Bearer | 关 |
|
|
55
|
+
| DeepInfra | `deepinfra` | 余额 | Bearer | 关 |
|
|
56
|
+
| Chutes | `chutes` | 通用限额窗口 | Bearer | 关 |
|
|
57
|
+
| Ollama Cloud | `ollama-cloud` | session / 周 / 月 | 裸 `Authorization`(不加 `Bearer`) | 关 |
|
|
58
|
+
| Vercel AI Gateway | `vercel-ai-gateway` | 余额 | Bearer | 关 |
|
|
59
|
+
| ZenMux | `zenmux` | 5 小时滚动窗口 + PAYG 余额 | **Management API Key** | 关 |
|
|
60
|
+
| LiteLLM | `litellm` | 预算 + 花费 | 虚拟 Key + **用户自填 proxy 地址** | 关 |
|
|
61
|
+
|
|
62
|
+
- 各家的**窗口语义、明细字段与单位换算**(例如 ZenMux / Ollama 的 0–1 小数、Novita 的 1/10000 USD、Kimi 随套餐变化的窗口集合)见 [提供商清单与额度语义](docs/providers.md)。
|
|
63
|
+
- 评估过但**不接入**的厂商与理由见 [提供商覆盖与取舍](docs/provider-coverage.md)(含已由用户决策跳过的 Anthropic)。
|
|
64
|
+
|
|
65
|
+
非公开控制台接口可能变更;缺失或非法百分比不会被当作零用量;「读取成功」只表示接口读取健康度,不保证仍有额度。
|
|
66
|
+
|
|
67
|
+
## Compatibility / 兼容性
|
|
68
|
+
|
|
69
|
+
| 项目 | 值 | 来源 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| 核心 peer 范围 | `@deepseek-ai/dsh` 及各核心包均为 `>=0.2.0-rc.1 <0.3.0-0`(允许该范围内的预发布版本) | [`package.json`](package.json) 的 `peerDependencies` |
|
|
72
|
+
| 开发目标版本 | DeepSeek Harness `0.2.0-rc.2` | [开发与验证说明](docs/development.md) |
|
|
73
|
+
| 最后验证日期 | `2026-10-09` | 同上 |
|
|
20
74
|
|
|
21
|
-
|
|
22
|
-
2. 打开 **设置 → 订阅用量 → 提供商管理**,为需要的订阅配置凭据。
|
|
23
|
-
3. **Z.ai 国际、Synthetic、NanoGPT、火山方舟 Ark 三家、SiliconFlow、OpenRouter、Novita、Hyperbolic、DeepInfra、Chutes、Ollama Cloud、Vercel AI Gateway、MiniMax 国际版、ZenMux、LiteLLM**默认关闭,必须手动开启(共十七项)。其余九项默认开启;升级保留已保存的开关。
|
|
24
|
-
4. 点击「刷新当前」检查结果;选中对应提供商的模型后,输入区显示余量药丸。
|
|
75
|
+
声明范围不代表范围内所有版本都已实机验证;实际验证范围(在线接口、真实账号、浏览器像素验收等)见 [开发与验证说明](docs/development.md)。这是树外 Host / Client bundle,通过 `cordis.patch.yml` 插入,不修改 DSH 核心、安装树或 ASAR。
|
|
25
76
|
|
|
26
|
-
|
|
77
|
+
## Install / 安装
|
|
27
78
|
|
|
28
|
-
|
|
79
|
+
| 来源 | 标识 | 说明 |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| npm | `dsh-subusage` | 已发布版本见 [npm](https://www.npmjs.com/package/dsh-subusage) |
|
|
82
|
+
| GitHub Release tarball | `https://github.com/KouzakiUmi/dsh-subusage/releases/latest/download/dsh-subusage.tgz` | 资产名固定不带版本号 |
|
|
83
|
+
| 本地开发目录 | 仓库路径 | 以链接方式使用工作副本 |
|
|
29
84
|
|
|
30
|
-
|
|
85
|
+
```console
|
|
86
|
+
# npm 包名
|
|
87
|
+
dsh plugin --profile <profile> add dsh-subusage
|
|
31
88
|
|
|
32
|
-
|
|
89
|
+
# GitHub Release tarball(尚未经市场收录时的完整 URL)
|
|
90
|
+
dsh plugin --profile <profile> add https://github.com/KouzakiUmi/dsh-subusage/releases/latest/download/dsh-subusage.tgz
|
|
33
91
|
|
|
34
|
-
|
|
92
|
+
# 本地开发目录(link 工作副本)
|
|
93
|
+
dsh plugin --profile <profile> add D:\src\dsh-subusage
|
|
94
|
+
```
|
|
35
95
|
|
|
36
|
-
|
|
96
|
+
升级与卸载:
|
|
37
97
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
98
|
+
```console
|
|
99
|
+
dsh plugin --profile <profile> update dsh-subusage
|
|
100
|
+
dsh plugin --profile <profile> update dsh-subusage@https://github.com/KouzakiUmi/dsh-subusage/releases/latest/download/dsh-subusage.tgz
|
|
101
|
+
dsh plugin --profile <profile> remove dsh-subusage
|
|
102
|
+
```
|
|
43
103
|
|
|
44
|
-
|
|
104
|
+
- `<profile>` 换成目标 profile;**Desktop 请使用随包 DSH CLI 或应用内插件管理页**,PATH 上的 npm 全局 `dsh` 不一定是它。参数与 profile 名以目标部署的 `dsh plugin --help` 为准;桌面插件页同样接受包名、GitHub 地址或本地目录。
|
|
105
|
+
- 安装只把包放进 profile,**不等于已启用**;按部署方式重载或重启,让 Host 与 Client 一起生效(只刷新页面不保证 Host 升级)。
|
|
106
|
+
- 只取压缩包:`npm pack dsh-subusage`;核对发布结果:`npm view dsh-subusage version dist-tags`。
|
|
45
107
|
|
|
46
|
-
|
|
108
|
+
## Quick start / 快速开始
|
|
47
109
|
|
|
48
|
-
|
|
110
|
+
1. 安装并启用 bundle,按部署方式重载或重启。
|
|
111
|
+
2. 打开 **设置 → 订阅用量 → 提供商管理**,开启需要的提供商(默认关闭的 19 条需手动开启)。
|
|
112
|
+
3. 为它配置凭据:在卡片里展开「连接与凭据」填 Key / AK-SK,或把 Key 放进凭据服务 / 启动环境;MiMo 用「登录并自动导入」。
|
|
113
|
+
4. 回到会话,选中该提供商的模型:输入区左侧出现余量药丸(绿色成功 / 红色失败),点开看窗口明细。
|
|
114
|
+
5. 排障顺序:点「立即刷新」→ 看设置页顶部 `X/Y 家数据获取成功`(可点击,跳到第一家没读成功的)。
|
|
49
115
|
|
|
50
|
-
|
|
51
|
-
|---|---|
|
|
52
|
-
| MiMo 未登录 / Cookie 被拒 | 「登录并自动导入」+「手动填写」 |
|
|
53
|
-
| MiMo Cookie 已过期或 2 小时内到期 | 「重新登录」+「手动填写」 |
|
|
54
|
-
| 其它厂商缺凭据或凭据被拒 | 「配置凭据」——展开该厂商的编辑器并滚动过去 |
|
|
116
|
+
可复现的最小示例(DeepSeek,余额型,默认开启,与推理同一把 Key):
|
|
55
117
|
|
|
56
|
-
|
|
118
|
+
```powershell
|
|
119
|
+
# 任选一种凭据来源
|
|
120
|
+
$env:DEEPSEEK_API_KEY = "<你的 DeepSeek API Key>" # 启动环境;改完需按部署方式重启
|
|
121
|
+
# 或在 设置 → 订阅用量 → DeepSeek →「连接与凭据」里手动填写
|
|
122
|
+
```
|
|
57
123
|
|
|
58
|
-
|
|
124
|
+
重启后在会话中选中 DeepSeek 模型,药丸显示账户余额;也可以直接让 Agent 调用 `subusage_quota(providers: ["deepseek"])` 验证(无需界面)。
|
|
59
125
|
|
|
60
|
-
|
|
61
|
-
- **顶部「X/Y 家数据获取成功」是可点击的**:一下就切到第一家没读成功的;全部正常时按钮禁用,不做无意义的跳转。
|
|
126
|
+
## Configuration / 配置
|
|
62
127
|
|
|
63
|
-
|
|
128
|
+
- **设置页路径**:设置 → **订阅用量** → 提供商管理。「没有检测到API的默认隐藏」默认开启;首次没有可见条目时管理区自动展开。关闭的提供商仍可配置凭据,只是不会发起用量请求。
|
|
129
|
+
- **默认开关策略**:新安装默认开启 11 条、默认关闭 19 条;开关即时保存,升级保留已保存的开关,不重算默认值。默认隐藏只影响显示,不会自动开启被关闭的提供商。
|
|
130
|
+
- **凭据来源优先级**(`inherit` 模式):**凭据服务 → 启动环境 → 旧手动配置兜底**;切到自定义(`manual`)模式时只使用保存的手动 Key。界面会区分这三种来源。
|
|
131
|
+
- **不接受本地 Key 的提供商**:`commandcode`(凭据链属提供方插件)、`xai-oauth`、`openai-codex`(只读各自 CLI 的登录文件),以及 MiMo(Cookie 会话)。
|
|
132
|
+
- **保存与验证分离**:凭据先确认持久化,再独立做在线验证;检测关闭的提供商只保存凭据、不发起验证,验证失败不代表保存失败。
|
|
133
|
+
- **出错时在卡片上修**:MiMo 未登录 / Cookie 被拒 → 「登录并自动导入」;Cookie 临近到期或已过期 → 「重新登录」;其他家缺凭据 → 「配置凭据」(展开该厂商编辑器并滚动过去)。正常状态不显示行动条。
|
|
64
134
|
|
|
65
|
-
|
|
135
|
+
<details>
|
|
136
|
+
<summary>凭据与环境:各家默认继承变量与说明(取自 <code>PROVIDERS[id].envName</code>)</summary>
|
|
66
137
|
|
|
67
|
-
|
|
138
|
+
「默认继承变量」指凭据服务 / 启动环境的默认引用名;留空表示不使用继承变量(凭据来自登录文件或官方平台会话)。
|
|
68
139
|
|
|
69
140
|
| 模型商 | 默认继承变量 | 说明 |
|
|
70
141
|
|---|---|---|
|
|
71
142
|
| DeepSeek | `DEEPSEEK_API_KEY` | 余额型:`GET /user/balance` 返回账户余额,**与推理是同一把 Key** |
|
|
72
|
-
| Z.ai | `ZAI_CODING_CN_API_KEY` | 团队套餐另填组织、项目 ID |
|
|
73
|
-
| Z.ai
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
| ZenMux | `ZENMUX_MANAGEMENT_API_KEY` | 额度端点**只认 Management API Key**(推理 key 不适用),所以变量名单独区分。在 <https://zenmux.ai/platform/management> 创建该 key |
|
|
77
|
-
| LiteLLM | `LITELLM_API_KEY` + 代理地址 | 自建网关:除虚拟 Key(**与推理同一把**)还要在凭据区填写自己的 proxy 地址。管理端点在 **proxy 根**——填了 `/v1` 也会被去掉,不会拼成 `/v1/key/info` |
|
|
78
|
-
| Kimi | `KIMI_CODING_API_KEY` | 需要 Kimi Coding Key,不是 Moonshot 开平台 Key;窗口集合按套餐体系下发(老套餐 5 小时 + 7 天,新套餐 Go / Plus 为 5 小时 + 月度) |
|
|
79
|
-
| MiMo | 不使用 API Key | 通过控制台 Cookie 会话读取 |
|
|
143
|
+
| Z.ai(中国) | `ZAI_CODING_CN_API_KEY` | 团队套餐另填组织、项目 ID(作为 `bigmodel-organization` / `bigmodel-project` 请求头) |
|
|
144
|
+
| Z.ai(国际) | `ZAI_CODING_API_KEY` | 独立国际 Coding Plan Key,鉴权用裸 `authorization`,不带 `Bearer` |
|
|
145
|
+
| Kimi | `KIMI_CODING_API_KEY` | 需要 Kimi Coding Key,不是 Moonshot 开放平台 Key |
|
|
146
|
+
| Xiaomi MiMo | 界面不使用继承 Key | 通过官方平台 Cookie 会话读取(`envName` 只作为服务侧引用名保留) |
|
|
80
147
|
| OpenCode Go | `OPENCODE_API_KEY` | OpenCode Go Key |
|
|
81
|
-
| MiniMax
|
|
82
|
-
| MiniMax
|
|
83
|
-
| Command Code | `COMMANDCODE_API_KEY` |
|
|
84
|
-
| SuperGrok |
|
|
85
|
-
|
|
|
148
|
+
| MiniMax(国际) | `MINIMAX_API_KEY` | `minimax` 路由;国际站订阅 Key |
|
|
149
|
+
| MiniMax(中国) | `MINIMAX_CN_API_KEY` | `minimax-cn` 路由;中国站订阅 Key |
|
|
150
|
+
| Command Code | `COMMANDCODE_API_KEY` | 凭据由提供方插件管理,本页只读继承;兜底读取 `~/.commandcode/auth.json`(`cmd login`) |
|
|
151
|
+
| SuperGrok | 无 | `xai-oauth` 路由;只读 dsh-grok-kit / Grok CLI 共享的 OAuth 登录文件 `~/.grok/auth.json`(旧版 `~/.dsh/.xai-oauth-auth.json`),不保存、不刷新 token;API Key(`XAI_API_KEY`)取不到订阅周池 |
|
|
152
|
+
| Codex | 无 | `openai-codex` 路由;只读 Codex CLI 的 ChatGPT 订阅登录文件(`$CODEX_HOME/auth.json` 或 `~/.codex/auth.json`,要求 `auth_mode` 为 `chatgpt`),不保存、不刷新 token |
|
|
153
|
+
| 火山方舟 Ark(7 条路由) | `VOLC_ACCESSKEY` + `VOLC_SECRETKEY` | 额度查询要 **IAM Access Key 的 AK/SK 配对**,与推理 API Key(`ARKCLI_*_API_KEY` / `ARK_*_API_KEY`)**不是同一套**;7 条路由共用一组 AK/SK |
|
|
86
154
|
| SiliconFlow | `SILICONFLOW_API_KEY` | 余额型:`GET /v1/user/info` 返回账户余额,**与推理是同一把 Key** |
|
|
87
|
-
| Codex | 不使用 API Key | `openai-codex` 路由由 DSH 内置或 `dsh-codex-connect` 等插件提供;只读 Codex CLI 的 ChatGPT 订阅登录文件(`$CODEX_HOME/auth.json` 或 `~/.codex/auth.json`,要求 `auth_mode` 为 `chatgpt`),不保存、不刷新 token。**API Key 模式(`auth_mode` 不是 `chatgpt`)取不到订阅额度**,此时按未配置处理 |
|
|
88
155
|
| OpenRouter | `OPENROUTER_API_KEY` | key 限额与用量用推理 key 即可;**账户余额需要 management / provisioning key**,普通 key 会被 403 拒——此时静默降级为只显示限额,不判失败 |
|
|
89
|
-
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
3. Host 从该会话获取适用于官方 API 的 `api-platform_serviceToken` 和 `userId`(包括 HttpOnly Cookie),先验证账户接口,再保存并更新用量;Cookie 不通过 RPC 返回页面。
|
|
100
|
-
4. 可随时取消或关闭登录窗口;五分钟未完成会超时并关闭。取消、认证失败、网络验证未完成或同一 MiMo 配置已被外部修改时不覆盖凭据。
|
|
101
|
-
5. 账户验证成功但无订阅额度时可以保存登录,界面明确显示「额度未知」,不冒充有额度。
|
|
156
|
+
| Synthetic | `SYNTHETIC_API_KEY` | 模型订阅请求额度 |
|
|
157
|
+
| NanoGPT | `NANOGPT_API_KEY` | 也可手动填写 Usage only 管理令牌;配额之外还会读一次账户余额(余额端点用 `x-api-key` 而不是 Bearer;拿不到就静默降级,不影响配额) |
|
|
158
|
+
| Novita AI | `NOVITA_API_KEY` | 余额型,与推理同一把 Key;金额单位 **1/10000 USD** |
|
|
159
|
+
| Hyperbolic | `HYPERBOLIC_API_KEY` | 余额型;金额单位是**美分** |
|
|
160
|
+
| DeepInfra | `DEEPINFRA_API_KEY` | 余额型 |
|
|
161
|
+
| Chutes | `CHUTES_API_KEY` | 通用限额窗口 |
|
|
162
|
+
| Ollama Cloud | `OLLAMA_API_KEY` | 余额型;鉴权是**裸 `Authorization`**(本插件已按其要求发送) |
|
|
163
|
+
| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | 余额型;金额是**十进制字符串** |
|
|
164
|
+
| ZenMux | `ZENMUX_MANAGEMENT_API_KEY` | 额度端点**只认 Management API Key**(推理 key 不适用),所以变量名单独区分。在 <https://zenmux.ai/platform/management> 创建该 key |
|
|
165
|
+
| LiteLLM | `LITELLM_API_KEY` + 代理地址 | 自建网关:除虚拟 Key(**与推理同一把**)还要在凭据区填写自己的 proxy 地址。管理端点在 **proxy 根**——填了 `/v1` 也会被去掉,不会拼成 `/v1/key/info` |
|
|
102
166
|
|
|
103
|
-
|
|
167
|
+
按 provider id 的字面映射(补齐上表按语义分组、未逐条列出的 7 条火山路由,逐条对应 `PROVIDERS[id].envName`):
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
deepseek DEEPSEEK_API_KEY
|
|
171
|
+
zai-coding-cn ZAI_CODING_CN_API_KEY
|
|
172
|
+
zai-coding ZAI_CODING_API_KEY
|
|
173
|
+
kimi-coding KIMI_CODING_API_KEY
|
|
174
|
+
xiaomi-token-plan-cn XIAOMI_TOKEN_PLAN_CN_API_KEY (界面走官方平台 Cookie)
|
|
175
|
+
opencode-go OPENCODE_API_KEY
|
|
176
|
+
commandcode COMMANDCODE_API_KEY
|
|
177
|
+
xai-oauth — (OAuth 登录文件)
|
|
178
|
+
openai-codex — (OAuth 登录文件)
|
|
179
|
+
minimax MINIMAX_API_KEY
|
|
180
|
+
minimax-cn MINIMAX_CN_API_KEY
|
|
181
|
+
synthetic SYNTHETIC_API_KEY
|
|
182
|
+
nanogpt NANOGPT_API_KEY
|
|
183
|
+
siliconflow SILICONFLOW_API_KEY
|
|
184
|
+
openrouter OPENROUTER_API_KEY
|
|
185
|
+
novita NOVITA_API_KEY
|
|
186
|
+
hyperbolic HYPERBOLIC_API_KEY
|
|
187
|
+
deepinfra DEEPINFRA_API_KEY
|
|
188
|
+
chutes CHUTES_API_KEY
|
|
189
|
+
ollama-cloud OLLAMA_API_KEY
|
|
190
|
+
vercel-ai-gateway AI_GATEWAY_API_KEY
|
|
191
|
+
zenmux ZENMUX_MANAGEMENT_API_KEY
|
|
192
|
+
litellm LITELLM_API_KEY (另需自填 proxy 地址)
|
|
193
|
+
arkcli-agent-plan ARKCLI_AGENT_PLAN_API_KEY (额度用 VOLC_ACCESSKEY / VOLC_SECRETKEY)
|
|
194
|
+
arkcli-coding-plan ARKCLI_CODING_PLAN_API_KEY
|
|
195
|
+
arkcli-agent-plan-team ARKCLI_AGENT_PLAN_TEAM_API_KEY
|
|
196
|
+
arkcli-coding-plan-team ARKCLI_CODING_PLAN_TEAM_API_KEY
|
|
197
|
+
ark-coding-plan-cn ARK_CODING_PLAN_CN_API_KEY
|
|
198
|
+
ark-agent-plan-cn ARK_AGENT_PLAN_CN_API_KEY
|
|
199
|
+
ark-coding-plan-byteplus ARK_CODING_PLAN_BYTEPLUS_API_KEY
|
|
200
|
+
```
|
|
104
201
|
|
|
105
|
-
|
|
202
|
+
解析顺序(`inherit` 模式):**凭据服务 → 启动环境 → 旧手动配置兜底**;自定义(`manual`)模式只使用保存的手动 Key。Cookie 与登录文件型凭据不走这条链,见 [MiMo 登录与 Cookie](docs/providers.md#mimo-登录与-cookie) 与 [Codex ChatGPT 订阅凭据](docs/providers.md#codex-chatgpt-订阅凭据)。修改启动环境后是否需要重启取决于目标部署,不能把「我已经改了环境变量」当作运行进程已经读到。
|
|
106
203
|
|
|
107
|
-
|
|
204
|
+
</details>
|
|
108
205
|
|
|
109
|
-
|
|
206
|
+
## Permissions & data / 权限与数据
|
|
110
207
|
|
|
111
|
-
|
|
208
|
+
**读取 / 写入的文件**
|
|
112
209
|
|
|
113
|
-
|
|
|
210
|
+
| 路径 | 用途 | 读写 |
|
|
114
211
|
|---|---|---|
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
| 额度已用尽 | 仍优先显示额度告警 | — |
|
|
120
|
-
|
|
121
|
-
药丸弹层常驻显示剩余时间与到期时刻。到期只是**提醒**:插件不会因为倒计时归零就停用 Cookie,是否真的失效仍以官方接口返回为准(失效时按认证错误处理,重新登录即可)。倒计时也永远不会盖过接口的判定——官方已判定凭据失效时,药丸显示的是失效原因而非剩余时间(唯一例外是「已过期」本身,因为它就是重新登录的提示)。若浏览器报告的 Cookie 过期时间早于 24 小时,以更早者为准;旧版本保存的 Cookie 没有计时记录,界面会明确说明「未记录登录时间」而不是编造倒计时。
|
|
122
|
-
|
|
123
|
-
**安全说明**:Key/Cookie 不再通过用量读取接口回传,界面不回填已保存的秘密;但本版仍兼容原本机 JSON 配置存储,**不是加密凭据库**。该配置位于 `~/.dsh/dsh-subusage.json`,新建目录/临时文件在 POSIX 上按 `0700`/`0600` 创建,替换前再次收紧文件权限;Windows 的实际访问权限仍取决于目录 ACL,`chmod` 不能替代 ACL 或加密库。请勿上传、分享或写入日志。隔离浏览器仅用于你主动发起的官方登录;它不是后台扫描或导入日常浏览器全部凭据的工具。加密凭据存储通道仍未实施。
|
|
124
|
-
|
|
125
|
-
### 火山方舟 Ark 凭据
|
|
126
|
-
|
|
127
|
-
火山方舟的套餐额度走**管控面 OpenAPI**(`open.volcengineapi.com`,HMAC-SHA256 签名),只能用 **IAM Access Key**(AK/SK 配对):推理用的方舟 API Key 交给上游会被拒绝,两者是两套凭据。三个 Ark 路由共用这一组 AK/SK。
|
|
128
|
-
|
|
129
|
-
1. 在火山引擎控制台创建 Access Key,建议用子用户并只授予方舟只读权限(额度接口不接受主账号之外的其它鉴权方式)。
|
|
130
|
-
2. 打开 **设置 → 订阅用量 → 提供商管理**,展开火山方舟任意一家的「连接与凭据」,填入 AccessKey ID 与 SecretAccessKey 并保存。
|
|
131
|
-
3. 或在启动环境里设置 `VOLC_ACCESSKEY` / `VOLC_SECRETKEY`;也可以先把凭据写进 DSH 凭据服务,由「继承凭据 / 环境变量」模式读取。
|
|
132
|
-
|
|
133
|
-
AK 与 SK 必须来自同一个 IAM 用户:跨来源拼凑只会得到 401(`SignatureDoesNotMatch`),而报错本身看不出根因。两个字段各自「留空表示不修改该项」,清除要显式点「清除 AK/SK」——只有一半凭据时按未配置显示,不会拿半个签名去请求。
|
|
134
|
-
|
|
135
|
-
额度按 provider 分派 Action:Coding Plan 走 `GetCodingPlanUsage`,Agent Plan 走 `GetAFPUsage`。账号没有对应套餐时接口返回 **HTTP 200 且窗口为空**,界面显示「未检测到该套餐订阅」,不会画成 0% 用量。401(签名/凭据)、403(权限或未订阅)、接口不存在三类失败分别给出不同的行动项。
|
|
136
|
-
|
|
137
|
-
> `GetCodingPlanUsage` 并未出现在官方 API 概览里(由官方 ark-cli 与多个第三方实现确证可用),存在变更风险;`GetAFPUsage` 有官方文档。
|
|
138
|
-
|
|
139
|
-
### Codex(ChatGPT 订阅)凭据
|
|
212
|
+
| `~/.dsh/dsh-subusage.json` | 本插件自己的配置:开关、手动 Key、MiMo Cookie、Z.ai 组织 / 项目、LiteLLM 地址、火山 AK/SK、计时元数据 | 读写;POSIX 上新建目录 `0700`、临时文件 `0600`,替换前再收紧 |
|
|
213
|
+
| `~/.codex/auth.json`(或 `$CODEX_HOME/auth.json`) | Codex CLI 的 ChatGPT 登录(要求 `auth_mode: chatgpt`) | **只读**,不保存、不刷新 |
|
|
214
|
+
| `~/.grok/auth.json`(回退 `~/.dsh/.xai-oauth-auth.json`) | Grok CLI / dsh-grok-kit 的 OAuth 登录 | **只读**,不保存、不刷新 |
|
|
215
|
+
| `~/.commandcode/auth.json` | Command Code CLI(`cmd login`)的 Key | **只读**兜底 |
|
|
140
216
|
|
|
141
|
-
|
|
217
|
+
**网络访问**:只连上面清单里各家自己的额度 / 余额端点,以及 `platform.xiaomimimo.com`(MiMo 用量与登录)、`api.commandcode.ai`、`cli-chat-proxy.grok.com`、`chatgpt.com/backend-api`、火山管控面 `open.volcengineapi.com` / `ark.ap-southeast-1.byteplusapi.com`,外加你自己填写的 LiteLLM proxy 地址。完整域名表见 [网络端点](docs/providers.md#网络端点)。MiMo 自动登录会另外打开官方平台页面,由你在该页面自行输入密码与验证码。
|
|
142
218
|
|
|
143
|
-
|
|
144
|
-
- 必须 `auth_mode` 为 `chatgpt`。若该文件是 API Key 模式,插件按「未配置」处理并在界面上说明——API Key 取不到订阅额度,不拿它去冒充订阅凭据。
|
|
145
|
-
- 插件**只读**这个文件:不保存、不刷新 token(token 由 Codex CLI 自己轮换,第三端刷新会互相顶掉)。请用 `codex login` 完成登录。
|
|
219
|
+
**密钥如何处理**
|
|
146
220
|
|
|
147
|
-
|
|
221
|
+
- Key 与 Cookie **不回传页面**:读取接口不返回凭据本身,界面不回填已保存的秘密(Cookie 只在需要替换时输入);公开设置只有 `hasKeys` / `hasCookie` 这类布尔状态与来源标记。
|
|
222
|
+
- **日志与产物不落盘**:真实 Key / Cookie 不会写进日志、测试、截图或读取结果;MiMo 响应在返回前做字段级 Cookie 脱敏。
|
|
223
|
+
- MiMo 自动登录使用**独立临时 Chrome 会话**:不读取日常 Chrome 配置,不读取密码 / 验证码字段,Cookie 不通过 RPC 返回页面;五分钟未完成自动超时。
|
|
224
|
+
- 计时元数据(`xiaomi.loginAt` / `xiaomi.expiresAt`)只是本地时间戳,不是秘密,也不参与凭据有效性判断。
|
|
225
|
+
- **这不是加密凭据库**:本版仍兼容本机 JSON 配置存储,凭据以明文落盘;加密凭据存储通道尚未实施。Windows 上的实际访问权限取决于目录 ACL,`chmod` 不能替代 ACL 或加密库。请不要上传、分享该配置文件,也不要把它写进日志或工单。
|
|
148
226
|
|
|
149
|
-
|
|
227
|
+
## 额度查询 API
|
|
150
228
|
|
|
151
|
-
|
|
229
|
+
**Agent 工具 `subusage_quota`**(模型可直接调用,只读:不写设置、不碰凭据)
|
|
152
230
|
|
|
153
|
-
|
|
231
|
+
| 参数 | 类型 | 说明 |
|
|
232
|
+
|---|---|---|
|
|
233
|
+
| `providers` | `string[]`(可选) | 限定 provider id,例如 `["deepseek", "openrouter"]`;省略表示读取全部 |
|
|
234
|
+
| `refresh` | `boolean`(可选) | `true` 绕过最多 60 秒的缓存,仅在确需最新数字时使用 |
|
|
154
235
|
|
|
155
|
-
**Host
|
|
236
|
+
**Host 服务**(Cordis `Service`,key 为 `subUsage`):
|
|
156
237
|
|
|
157
238
|
```js
|
|
158
239
|
const service = ctx.get("subUsage");
|
|
159
240
|
const view = await service.quota({ providerIds: ["deepseek"], force: false });
|
|
160
|
-
// view = {
|
|
241
|
+
// view = {
|
|
242
|
+
// updatedAt,
|
|
243
|
+
// providers: [{ providerId, label, state, windows, extras, coverage, freshness, lastSuccessAt, error? }]
|
|
244
|
+
// }
|
|
161
245
|
```
|
|
162
246
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
247
|
+
- `state`:`ok` | `no-key` | `no-cookie` | `error` | `disabled`
|
|
248
|
+
- `windows[].percent` 是**已用**百分比(0–100);`extras` 放余额与套餐名等补充项
|
|
249
|
+
- 视图**刻意精简**:不返回设置,也不返回 `keySource` / `apiDetected` / 继承变量名等内部状态,第三方消费者拿不到任何与 Key 相关的字段
|
|
250
|
+
- `providerIds` 里的未知 id 会被忽略;单家读取失败只影响它自己的条目,其余照常返回
|
|
251
|
+
- 与设置页共用同一份缓存(TTL 60 秒),频繁调用不会反复请求各家接口
|
|
166
252
|
|
|
167
|
-
##
|
|
168
|
-
|
|
169
|
-
| 模型商 | 窗口 | 明细 |
|
|
170
|
-
|---|---|---|
|
|
171
|
-
| DeepSeek | 无窗口(余额型) | 账户余额(`balance_infos` 的币种与总额);余额不可用时提示充值,不显示成 0 元 |
|
|
172
|
-
| Z.ai 中国 | 5 小时、每周(按响应) | Token / Credits 配额、套餐档 |
|
|
173
|
-
| Z.ai 国际 | 5 小时、每周(按响应) | Token / Credits 配额,区域凭据独立 |
|
|
174
|
-
| Synthetic | 滚动订阅池 | 已用/总请求次数、重置时间 |
|
|
175
|
-
| NanoGPT | 每日、每周、试用周期(按响应) | 输入 Token 数、重置时间;不包含图像额度 |
|
|
176
|
-
| Kimi | 5 小时、7 天、月度(按账户下发) | 已用百分比、套餐档 |
|
|
177
|
-
| MiMo | 本周期额度池 | 用量明细、重置时间、套餐与余额 |
|
|
178
|
-
| OpenCode Go | 滚动、每周、每月 | 已用百分比、重置时间 |
|
|
179
|
-
| MiniMax 国际 / 中国 | 短周期(通常 5 小时)、每周 | 通用/编程池已用百分比、可信计数明细、重置时间 |
|
|
180
|
-
| Command Code | 5 小时、每周、月额度池 | 已用/上限(月总额为套餐快照)、重置/账期、套餐(planId)、月剩余、已购 + 赠送余额 |
|
|
181
|
-
| SuperGrok | 周期池(通常每周,旧账户为月账期) | 统一用量池已用百分比(剩余 = 100 − 已用)、重置时间、套餐名(subscription_tier_display)、已购加量余额(美元) |
|
|
182
|
-
| Ark Coding Plan(中国 / BytePlus) | 5 小时、周、月 | 已用百分比、重置时间(接口只给百分比,不返回绝对量) |
|
|
183
|
-
| Ark Agent Plan(中国) | 5 小时、日、周、月 | AFP 已用/配额绝对值与百分比、重置时间、套餐档位(Small / Medium / Large / Max) |
|
|
184
|
-
| SiliconFlow | 无窗口(余额型) | 账户余额(元)。余额没有上限也就没有百分比,药丸直接显示余额,弹层与设置页列出明细 |
|
|
185
|
-
| Codex | 5 小时、7 天(免费档的次窗口是 30 天) | 已用百分比、重置时间;`credits` 只在真正报告了可用余额时显示(`has_credits=false`、`unlimited`、余额为 0 都不显示,不画成 0) |
|
|
186
|
-
| OpenRouter | 按 key 限额周期(月/周/日);免费档另有每日免费请求窗口 | 已用比例与限额明细;有 management key 时显示账户余额(`total_credits − total_usage`)。接口不给具体重置时刻,故不显示重置时间 |
|
|
187
|
-
| Novita / Hyperbolic / Vercel AI Gateway | 无窗口(余额型) | 账户余额(美元)。单位换算按各家接口:Novita 是 **1/10000 USD**、Hyperbolic 是**美分**、Vercel 是**十进制字符串** |
|
|
188
|
-
| DeepInfra | 无窗口(余额型) | 可用余额=`−stripe_balance`(接口用负数表示预付资金);欠款时单独提示,不显示成负余额 |
|
|
189
|
-
| Chutes | 通用限额窗口 | `{quota, used}` 绝对量;接口不给重置时刻,故不显示重置时间 |
|
|
190
|
-
| Ollama Cloud | session / 周 / 月 | `limits.*.usage` 是 **0–1 小数**;缺层跳过该窗口而不判失败 |
|
|
191
|
-
| ZenMux | 5 小时滚动窗口 | `usage_percentage` 是 **0–1 小数**,另附 flows 明细与 `resets_at`;有 Management Key 时显示 PAYG 余额。两个端点各拿各的——余额被拒不影响配额 |
|
|
192
|
-
| NanoGPT | 日 / 周 / 试用周期(按响应) | 输入 Token 数、重置时间、账户状态;另有账户余额(美元) |
|
|
193
|
-
| LiteLLM | 预算周期(`budget_reset_at` 存在时才显示重置) | 已用 / 预算与百分比;**没有预算上限时不编造百分比**,只如实显示已用金额 |
|
|
194
|
-
|
|
195
|
-
非公开控制台接口可能变更;缺失或非法百分比不会当作零用量。部分数据、未知额度和暂时失败有明确状态,不能据此保证推理接口一定可用。
|
|
196
|
-
|
|
197
|
-
评估过但未接入的厂商(Requesty / Portkey / Groq / Together / Cerebras 等)及理由见 [提供商覆盖与取舍](docs/provider-coverage.md),其中包括已由用户决策跳过的 Anthropic。
|
|
198
|
-
|
|
199
|
-
Kimi 的窗口集合随套餐体系变化:老套餐(节奏命名,如 Allegro)返回 5 小时与 7 天,新套餐(Go / Plus 命名)返回 5 小时与月度总额,因此同一插件在不同账号上显示的窗口数可以不同;月度池里的 Code 份额不是独立预算,不单独成窗。插件按接口实际下发的窗口解析,不要求固定集合。
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
## 常见问题
|
|
253
|
+
## Troubleshooting / 常见问题
|
|
203
254
|
|
|
204
255
|
| 现象 | 排查方法 |
|
|
205
256
|
|---|---|
|
|
257
|
+
| 药丸不显示 | 依次确认:当前模型的 provider id 是否与上表一致(自定义 id 不会自动映射)→ 该家检测开关是否开启 → 是否被「没有检测到API的默认隐藏」收起(可在设置页临时关掉它)→ 运行中的 Host 与 Client 是否都已重载 |
|
|
206
258
|
| 设置页没有提供商标签 | 展开提供商管理,确认开关和凭据;无凭据的条目默认隐藏,可临时关闭自动隐藏查看指引 |
|
|
207
|
-
|
|
|
208
|
-
| 显示认证失效 |
|
|
209
|
-
|
|
|
259
|
+
| 某家读取失败,其他家正常 | 单家失败只影响自己的条目。按文案分类处理:认证类(401 / 凭据被拒)换对应产品与区域的凭据;权限 / 未订阅类(403)检查账号权限与套餐;限流类等待退避后重试 |
|
|
260
|
+
| 显示认证失效 | 检查是否用了对应产品 / 区域的订阅 Key;MiMo 重新登录或导入 Cookie;旧额度不会当作有效数据保留 |
|
|
261
|
+
| MiMo Cookie 过期 | 会话 Cookie 自签发起 24 小时有效。药丸与设置页会在 2 小时内 / 30 分钟内分别变黄、变橙,到期显示「Cookie 已过期,请重新登录」;重新登录或重新导入即重新计时。倒计时只是提醒,真实失效以官方接口返回为准 |
|
|
262
|
+
| 药丸显示「未记录登录时间」 | 凭据由旧版本保存,没有计时元数据;重新登录或重新导入一次后开始计时 |
|
|
263
|
+
| Kimi 缺少 7 天窗口,或提示结构错误 | 窗口集合按套餐体系下发:老套餐(节奏命名,如 Allegro)为 5 小时 + 7 天,新套餐(Go / Plus 命名)为 5 小时 + 月度总额。0.8.3 起按实际下发的窗口解析,不再要求 7 天窗口;升级后仍报 `Invalid Kimi usage response` 即为未识别的字段结构,插件不会猜测额度,可回报该响应 |
|
|
210
264
|
| 显示缓存、部分数据或额度未知 | 查看更新时间和错误说明;缓存来自之前的成功读取,部分数据不保证有可用额度,余额也不等于订阅余量 |
|
|
211
|
-
| 刷新后数字暂未变化 | 成功结果按提供商缓存 60 秒;手动刷新可跳过普通 TTL,但仍遵守限流退避和 Retry-After |
|
|
265
|
+
| 刷新后数字暂未变化 | 成功结果按提供商缓存 60 秒;手动刷新可跳过普通 TTL,但仍遵守限流退避和 `Retry-After` |
|
|
212
266
|
| MiMo 自动登录无法启动 | Host 所在机器需安装 Google Chrome 并有桌面环境;远程或无桌面部署使用手动导入 |
|
|
213
|
-
| 药丸提示「Cookie N 小时后到期」 | MiMo 会话 Cookie 自签发起 24 小时有效,按提示重新登录即可续期;手动导入同样重新计时 |
|
|
214
|
-
| 药丸显示「未记录登录时间」 | 凭据由旧版本保存,没有计时元数据;重新登录或重新导入一次后开始计时 |
|
|
215
267
|
| 保存成功但验证失败 | 凭据已保存,检查网络、区域或账号后重试;保存与在线验证分别反馈 |
|
|
216
268
|
| 保存提示配置已改变 | 另一窗口或实例更新了设置;重新读取配置后再编辑,避免旧表单覆盖新值 |
|
|
217
269
|
| 升级后仍是旧界面或 RPC 不匹配 | 确认运行中的 Host 和 Client 都已重载;仅刷新页面不能保证 Host 升级 |
|
|
218
|
-
| Command Code 额度与实际账户不同 |
|
|
270
|
+
| Command Code 额度与实际账户不同 | 可在药丸弹层切换提供方插件的服务账户;「自动轮换」时仍显示默认账户(顶层 Key)的用量,不跟随轮换中的实际服务账户,提供方自定义 `apiBase` 也不跟随 |
|
|
219
271
|
| SuperGrok 提示认证失效 | 登录文件里的 OAuth token 会过期;在 设置 → Grok Kit 重新登录、运行 `grok login`,或让 Grok 侧使用一次以刷新共享登录文件后重试。API Key(`XAI_API_KEY`)取不到订阅周池 |
|
|
272
|
+
| 火山方舟报签名或权限错误 | AK 与 SK 必须来自同一个 IAM 用户(跨来源拼凑只会 401 且看不出根因);确认用的是 IAM Access Key 而不是方舟推理 API Key;403 多为未订阅或权限不足 |
|
|
220
273
|
|
|
221
|
-
##
|
|
274
|
+
## Development / 开发
|
|
222
275
|
|
|
223
|
-
|
|
|
224
|
-
|
|
225
|
-
|
|
|
226
|
-
|
|
|
227
|
-
|
|
|
228
|
-
|
|
|
229
|
-
|
|
|
230
|
-
|
|
|
231
|
-
|
|
|
232
|
-
| MiniMax 中国 | `minimax-cn` | 开启 |
|
|
233
|
-
| Z.ai 国际 | `zai-coding` | **关闭** |
|
|
234
|
-
| Synthetic | `synthetic` | **关闭** |
|
|
235
|
-
| NanoGPT | `nanogpt` | **关闭** |
|
|
276
|
+
| 路径 | 职责 |
|
|
277
|
+
|---|---|
|
|
278
|
+
| `lib/index.js` | 提供商适配、凭据解析、持久化、缓存、Host RPC 与 `subusage_quota` 工具 |
|
|
279
|
+
| `lib/client.js` | Client 模块、共享 store、设置页与模型药丸(含界面中英文文案) |
|
|
280
|
+
| `lib/volcengine.js` | 火山方舟 AK/SK 签名与窗口解析(纯函数、零依赖) |
|
|
281
|
+
| `lib/mimo-login.js` | 隔离 Chrome 登录、Cookie 提取与任务生命周期 |
|
|
282
|
+
| `cordis.patch.yml`、`package.json` | bundle 注册、入口、依赖与打包白名单 |
|
|
283
|
+
| `tests/`、`scripts/` | 桩网络 / 隔离文件系统回归;清单、打包、离线预览与浏览器检查 |
|
|
284
|
+
| `docs/` | 开发约定、提供商覆盖、UX 设计、发布与更新记录 |
|
|
236
285
|
|
|
237
|
-
|
|
286
|
+
没有源码转译步骤,直接维护 `lib/*.js`。基础检查不需要安装 DSH 或连接账号:
|
|
238
287
|
|
|
239
|
-
|
|
288
|
+
```console
|
|
289
|
+
node tests/run-all.mjs
|
|
290
|
+
node scripts/check-manifest.mjs
|
|
291
|
+
node --check lib/index.js
|
|
292
|
+
node --check lib/client.js
|
|
293
|
+
node --check lib/mimo-login.js
|
|
294
|
+
```
|
|
240
295
|
|
|
241
|
-
|
|
296
|
+
离线预览(生成 HTML,使用虚构用量与桩 Hook,不连接 DSH、不读取真实凭据):
|
|
242
297
|
|
|
243
|
-
|
|
298
|
+
```powershell
|
|
299
|
+
foreach ($theme in @('dark', 'light')) {
|
|
300
|
+
foreach ($width in @(530, 500, 499, 360)) {
|
|
301
|
+
node scripts/render-ui-preview.mjs $width xiaomi-token-plan-cn $theme credentials
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
node scripts/render-ui-preview.mjs 530 nanogpt light pill
|
|
305
|
+
node scripts/check-browser-runtime.mjs # 需要工作区可解析 playwright-core 且本机装有 Google Chrome
|
|
306
|
+
```
|
|
244
307
|
|
|
245
|
-
|
|
308
|
+
文档入口:[开发与验证](docs/development.md)(RPC 契约、缓存与凭据约定、离线预览与实机验收范围)、[提供商清单与额度语义](docs/providers.md)、[提供商覆盖与取舍](docs/provider-coverage.md)、[UX 设计](docs/design-ux.md)、[发布与市场收录](docs/publish.md)、[更新记录](docs/changelog.md)、[审查记录](docs/code-review.md)。
|
|
246
309
|
|
|
247
|
-
|
|
310
|
+
> 打包说明:`package.json` 的 `files` 白名单是 `lib` / `locale` / `cordis.patch.yml` / `README.md`,因此 `assets/title.svg`、`assets/screenshots/` 与 `docs/` **不进 npm 包**——顶部 title 图与下面的界面预览只在 GitHub 页面显示。改动白名单属于发布决策,需单独处理。
|
|
248
311
|
|
|
249
|
-
|
|
312
|
+
### 界面预览
|
|
250
313
|
|
|
314
|
+
以下为 0.7.0 的离线组件测试截图,使用虚构用量数据,不包含真实账号信息,也不是运行中的 DSH 截图。
|
|
251
315
|
|
|
252
|
-
|
|
316
|
+
深色设置页:查看订阅用量、管理凭据与提供商。
|
|
253
317
|
|
|
254
|
-
|
|
318
|
+

|
|
255
319
|
|
|
256
|
-
|
|
257
|
-
node tests/run-all.mjs
|
|
258
|
-
node scripts/check-manifest.mjs
|
|
259
|
-
node --check lib/index.js
|
|
260
|
-
node --check lib/client.js
|
|
261
|
-
node --check lib/mimo-login.js
|
|
262
|
-
```
|
|
320
|
+
浅色用量弹层:查看每日与每周额度、用量明细。
|
|
263
321
|
|
|
264
|
-
|
|
322
|
+

|
|
265
323
|
|
|
266
|
-
|
|
267
|
-
- [UX 设计](https://github.com/KouzakiUmi/dsh-subusage/blob/main/docs/design-ux.md):导航、状态、编辑保护与弹层行为。
|
|
268
|
-
- [发布与市场收录](https://github.com/KouzakiUmi/dsh-subusage/blob/main/docs/publish.md):GitHub Release、npm、条目与截图维护。
|
|
269
|
-
- [更新记录](https://github.com/KouzakiUmi/dsh-subusage/blob/main/docs/changelog.md):各版本功能变化。
|
|
270
|
-
- [0.7.0 审查记录](https://github.com/KouzakiUmi/dsh-subusage/blob/main/docs/code-review.md):修复、接口来源和验证边界。
|
|
324
|
+
截图声明见根目录 [`screenshots.json`](screenshots.json)。两张图都是**当前版本**的离线组件预览(虚构用量、桩网络、无凭据),已随本版本重做,底部水印明确标注「非运行中的 DSH 截图」。真实 DSH Loader、实际账号在线接口与多账户映射仍未验收,截图不代表已通过这些检查。
|
|
271
325
|
|
|
272
|
-
|
|
326
|
+
## License & security / 许可与安全
|
|
273
327
|
|
|
274
|
-
|
|
328
|
+
MIT,见 [`package.json`](package.json) 的 `license` 字段。本项目与 DeepSeek Harness 及各服务商**无官方关联**;非公开接口可能随时变更,请以各服务商官方条款为准。
|
|
275
329
|
|
|
276
|
-
|
|
330
|
+
报告安全问题:请**不要**在公开 issue 里粘贴 Key、Cookie 或完整配置文件。优先使用 GitHub 仓库的 **Security → Report a vulnerability** 私有渠道;该入口不可用时,也可以在 issue 中只描述问题与影响范围,凭据一律留空。任何复现步骤请先自行脱敏。
|
package/lib/client.js
CHANGED
|
@@ -252,9 +252,17 @@ window.__ModuleLoader__.load({
|
|
|
252
252
|
// 火山方舟:provider id 必须与官方插件 @volcengine/ark-plan-api 注册的路由逐字一致,
|
|
253
253
|
// 否则选中方舟模型时药丸不会出现。凭据是共享的 IAM AK/SK(volc),与这里的推理
|
|
254
254
|
// envName 是两套:推理 Key 查不到额度。
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
"
|
|
255
|
+
// arkcli(官方 CLI)安装的路由:id 由 `arkcli helper` 写成 arkcli-<planType>,
|
|
256
|
+
// 官方 CLI 是当前最主流的安装方式,故默认开启。
|
|
257
|
+
"arkcli-agent-plan": { label: "ARK Agent Plan (arkcli)", short: "ARK Plan", envName: "ARKCLI_AGENT_PLAN_API_KEY", volc: true },
|
|
258
|
+
"arkcli-coding-plan": { label: "ARK Coding Plan (arkcli)", short: "ARK Code", envName: "ARKCLI_CODING_PLAN_API_KEY", volc: true },
|
|
259
|
+
// 企业版/团队版:额度按席位下发,需 AK/SK 有查看席位的权限。
|
|
260
|
+
"arkcli-agent-plan-team": { label: "ARK Agent Plan Team (arkcli)", short: "ARK Plan Team", envName: "ARKCLI_AGENT_PLAN_TEAM_API_KEY", volc: true, defaultEnabled: false },
|
|
261
|
+
"arkcli-coding-plan-team": { label: "ARK Coding Plan Team (arkcli)", short: "ARK Code Team", envName: "ARKCLI_CODING_PLAN_TEAM_API_KEY", volc: true, defaultEnabled: false },
|
|
262
|
+
// 旧插件 @volcengine/ark-plan-api 的路由(保留兼容);short 与上面区分开,避免标签重名。
|
|
263
|
+
"ark-coding-plan-cn": { label: "Ark Coding Plan (CN, legacy plugin)", short: "Ark Coding", envName: "ARK_CODING_PLAN_CN_API_KEY", volc: true, defaultEnabled: false },
|
|
264
|
+
"ark-agent-plan-cn": { label: "Ark Agent Plan (CN, legacy plugin)", short: "Ark Agent", envName: "ARK_AGENT_PLAN_CN_API_KEY", volc: true, defaultEnabled: false },
|
|
265
|
+
"ark-coding-plan-byteplus": { label: "Ark Coding Plan (BytePlus, legacy plugin)", short: "Ark BytePlus", envName: "ARK_CODING_PLAN_BYTEPLUS_API_KEY", volc: true, defaultEnabled: false },
|
|
258
266
|
// 余额型:接口给账户余额而不是订阅窗口,药丸用余额兜底文案显示。
|
|
259
267
|
"siliconflow": { label: "SiliconFlow", short: "SiliconFlow", envName: "SILICONFLOW_API_KEY", defaultEnabled: false },
|
|
260
268
|
// 余额端点只对 management / provisioning key 开放;普通 key 只显示限额与用量。
|
|
@@ -272,7 +280,7 @@ window.__ModuleLoader__.load({
|
|
|
272
280
|
"litellm": { label: "LiteLLM", short: "LiteLLM", envName: "LITELLM_API_KEY", defaultEnabled: false }
|
|
273
281
|
};
|
|
274
282
|
// 顺序只影响展示:DeepSeek 放在末尾以免打乱既有条目位置;药丸匹配靠 provider id,与顺序无关。
|
|
275
|
-
const PROVIDER_ORDER = ["zai-coding-cn", "kimi-coding", "xiaomi-token-plan-cn", "opencode-go", "commandcode", "openai-codex", "xai-oauth", "minimax", "minimax-cn", "zai-coding", "synthetic", "nanogpt", "ark-coding-plan-cn", "ark-agent-plan-cn", "ark-coding-plan-byteplus", "siliconflow", "deepseek", "openrouter", "novita", "hyperbolic", "deepinfra", "chutes", "ollama-cloud", "vercel-ai-gateway", "zenmux", "litellm"];
|
|
283
|
+
const PROVIDER_ORDER = ["zai-coding-cn", "kimi-coding", "xiaomi-token-plan-cn", "opencode-go", "commandcode", "openai-codex", "xai-oauth", "minimax", "minimax-cn", "zai-coding", "synthetic", "nanogpt", "ark-coding-plan-cn", "ark-agent-plan-cn", "ark-coding-plan-byteplus", "siliconflow", "deepseek", "openrouter", "novita", "hyperbolic", "deepinfra", "chutes", "ollama-cloud", "vercel-ai-gateway", "zenmux", "litellm", "arkcli-agent-plan", "arkcli-coding-plan", "arkcli-agent-plan-team", "arkcli-coding-plan-team"];
|
|
276
284
|
function providerEnabled(settings, id) { return settings?.visibility?.providers?.[id] ?? (PROVIDER_META[id]?.defaultEnabled !== false); }
|
|
277
285
|
function providerVisible(settings, id, entry) {
|
|
278
286
|
if (!providerEnabled(settings, id)) return false;
|
|
@@ -1267,7 +1275,11 @@ window.__ModuleLoader__.load({
|
|
|
1267
1275
|
}, [usageStore, usageId]);
|
|
1268
1276
|
const meta = PROVIDER_META[tab];
|
|
1269
1277
|
const source = entry && entry.keySource || "none";
|
|
1270
|
-
|
|
1278
|
+
// auth-file 有三家(Grok / Codex / Command Code),凭据来源文件各不相同,不能只特判一家。
|
|
1279
|
+
const authFileLabel = tab === "xai-oauth" ? text("来自 ~/.grok/auth.json(Grok / Grok Kit 登录)", "From ~/.grok/auth.json (Grok / Grok Kit sign-in)")
|
|
1280
|
+
: tab === "openai-codex" ? text("来自 $CODEX_HOME/auth.json 或 ~/.codex/auth.json(codex login)", "From $CODEX_HOME/auth.json or ~/.codex/auth.json (codex login)")
|
|
1281
|
+
: text("来自 ~/.commandcode/auth.json(cmd login)", "From ~/.commandcode/auth.json (cmd login)");
|
|
1282
|
+
const sources = { credentials: text("来自凭据服务", "From credential service"), env: text("来自环境变量", "From environment"), manual: text("手动 Key", "Manual key"), none: text("无可用凭据", "No credentials"), cookie: text("官方平台 Cookie", "Platform Cookie"), "auth-file": authFileLabel };
|
|
1271
1283
|
const credentialButtons = (kind, hasSaved) => h("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" } },
|
|
1272
1284
|
draft[kind + "Action"] !== "replace" ? h("button", { type: "button", style: btnStyle, disabled: kind === "cookie" && loginActive, onClick: () => editDraft({ [kind + "Action"]: "replace", [kind]: "", ...(kind === "key" ? { keyMode: "manual" } : {}) }) }, text(kind === "cookie" ? "手动导入" : "更换", kind === "cookie" ? "Manual import" : "Change")) : null,
|
|
1273
1285
|
hasSaved && draft[kind + "Action"] !== "clear" ? h("button", { type: "button", style: btnStyle, disabled: kind === "cookie" && loginActive, onClick: () => { editDraft({ [kind + "Action"]: "clear", [kind]: "" }); setNotice({ ok: false, text: text("待清除:保存设置后将删除已保存的敏感凭据;取消编辑可保留。", "Pending removal: saving will delete the saved secret. Cancel edits to keep it.") }); } }, text(kind === "cookie" ? "清除登录凭据" : "清除", kind === "cookie" ? "Clear login credentials" : "Clear")) : null,
|
|
@@ -1283,7 +1295,7 @@ window.__ModuleLoader__.load({
|
|
|
1283
1295
|
h("p", null, `${result.configured && result.configured[tab] ? t("detected") : t("notDetected")} · ${sources[source] || sources.none}`),
|
|
1284
1296
|
tab === "commandcode" ? h(react.Fragment, null, h("p", null, t("commandcodeManaged"))) : tab === "xai-oauth" ? h(react.Fragment, null, h("p", null, t("grokManaged"))) : tab === "openai-codex" ? h(react.Fragment, null,
|
|
1285
1297
|
h("p", null, text("只读 Codex CLI 的 ChatGPT 订阅登录($CODEX_HOME/auth.json 或 ~/.codex/auth.json):本页不保存凭据、不刷新 token。请用 codex login 登录;API Key 模式(auth_mode 不是 chatgpt)取不到订阅额度。", "Reads the Codex CLI ChatGPT subscription login ($CODEX_HOME/auth.json or ~/.codex/auth.json) read-only: this page never saves credentials or refreshes tokens. Sign in with codex login; API-key mode (auth_mode not chatgpt) cannot read subscription quota."))) : PROVIDER_META[tab].volc ? h(react.Fragment, null,
|
|
1286
|
-
h("p", null, text("额度查询要用火山引擎 IAM 的 Access Key(AK/SK 配对,权限建议只给方舟只读),与推理用的方舟 API Key
|
|
1298
|
+
h("p", null, text("额度查询要用火山引擎 IAM 的 Access Key(AK/SK 配对,权限建议只给方舟只读),与推理用的方舟 API Key 不是同一套;Ark 的这几条路由共用这一组凭据。", "Quota lookup needs a Volcengine IAM Access Key pair (Ark read-only is enough). This is NOT the Ark inference API key; every Ark route here shares this pair.")),
|
|
1287
1299
|
h("div", { role: "group", "aria-label": text("Key 来源模式", "Key source mode"), style: { display: "flex", gap: 8, flexWrap: "wrap" } },
|
|
1288
1300
|
["inherit", "manual"].map((mode) => h("button", { key: mode, type: "button", "aria-pressed": draft.keyMode === mode, style: draft.keyMode === mode ? primary : btnStyle, onClick: () => editDraft({ keyMode: mode }) }, `${draft.keyMode === mode ? "✓ " : ""}${mode === "inherit" ? text("继承凭据 / 环境变量", "Inherit credentials / environment") : text("手动 AK/SK", "Manual AK/SK")}`))),
|
|
1289
1301
|
h("p", { style: { fontSize: 12 } }, text("继承变量:", "Inherited variables: "), h("code", null, "VOLC_ACCESSKEY"), " / ", h("code", null, "VOLC_SECRETKEY")),
|
package/lib/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createHash } from "node:crypto";
|
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { MimoLogin } from "./mimo-login.js";
|
|
11
|
-
import { parseVolcAgentPlan, parseVolcCodingPlan, volcError, volcSignature } from "./volcengine.js";
|
|
11
|
+
import { parseVolcAgentPlan, parseVolcCodingPlan, parseVolcSeatAfp, parseVolcSeatCoding, parseVolcSeatIds, volcError, volcSignature } from "./volcengine.js";
|
|
12
12
|
|
|
13
13
|
const name = "dsh-subusage";
|
|
14
14
|
const inject = ["llm"];
|
|
@@ -35,14 +35,30 @@ const PROVIDERS = {
|
|
|
35
35
|
// OpenAI Codex / ChatGPT 订阅:路由由 DSH 内置的 openai-codex(或 dsh-codex-connect 等插件)提供,
|
|
36
36
|
// 额度凭据只认 Codex CLI 的 OAuth 登录文件,不提供本地凭据编辑。
|
|
37
37
|
"openai-codex": { label: "Codex (ChatGPT)", envName: "", managedByPlugin: true },
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
"
|
|
43
|
-
|
|
38
|
+
// 火山方舟:方舟路由由两种方式写入——官方 CLI(arkcli helper,见下)或官方插件
|
|
39
|
+
// @volcengine/ark-plan-api(legacy 段)。**provider id 必须与写入方逐字一致**,
|
|
40
|
+
// 否则选中方舟模型时药丸不会出现。额度读取走管控面 OpenAPI,凭据是 IAM 的 AK/SK 配对
|
|
41
|
+
// (HMAC-SHA256 签名),与这些路由的推理 API Key 是两套:拿推理 Key 去查额度会被上游拒绝
|
|
42
|
+
// ("GetAFPUsage requires Volcengine Ark SSO STS")。
|
|
43
|
+
// 7 条 volc 路由共用一组 AK/SK(settings.volc):个人版各自固定一个 Action,
|
|
44
|
+
// 企业版/团队版走 ListSeatInfos → GetSeatAFPUsage / GetSeatInfoUsage 两步。
|
|
45
|
+
// ── arkcli(官方 CLI)安装的路由 ──────────────────────────────────────────
|
|
46
|
+
// 官方 CLI 的 `arkcli helper configure deepseek-harness` 把 provider 写成
|
|
47
|
+
// `arkcli-<planType>`(本机实测:`arkcli-agent-plan`),凭据引用是
|
|
48
|
+
// `ARKCLI_<PLAN>_API_KEY`。这是目前使用人数最多的安装方式,故默认开启
|
|
49
|
+
// (没配 AK/SK 时会被「没有检测到 API 的默认隐藏」收起)。
|
|
50
|
+
// 注意:路由(数据面,模型访问)与额度查询(管控面,需 IAM AK/SK)是两码事。
|
|
51
|
+
"arkcli-agent-plan": { label: "ARK Agent Plan (arkcli)", envName: "ARKCLI_AGENT_PLAN_API_KEY", credentialKind: "volc", volcAction: "GetAFPUsage", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing" },
|
|
52
|
+
"arkcli-coding-plan": { label: "ARK Coding Plan (arkcli)", envName: "ARKCLI_CODING_PLAN_API_KEY", credentialKind: "volc", volcAction: "GetCodingPlanUsage", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing" },
|
|
53
|
+
// 企业版/团队版:额度挂在席位上,先 ListSeatInfos 取 SeatID 再查该席位(volcSeat 决定用哪对 Action)。
|
|
54
|
+
"arkcli-agent-plan-team": { label: "ARK Agent Plan Team (arkcli)", envName: "ARKCLI_AGENT_PLAN_TEAM_API_KEY", credentialKind: "volc", volcSeat: "agent", volcScene: "agent_plan_enterprise", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing", defaultEnabled: false },
|
|
55
|
+
"arkcli-coding-plan-team": { label: "ARK Coding Plan Team (arkcli)", envName: "ARKCLI_CODING_PLAN_TEAM_API_KEY", credentialKind: "volc", volcSeat: "coding", volcScene: "coding_plan_enterprise", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing", defaultEnabled: false },
|
|
56
|
+
// ── 旧插件 @volcengine/ark-plan-api 注册的路由(保留兼容)─────────────────
|
|
57
|
+
// 两条路线的 provider id 不同,故并存;装旧插件的用户仍会走这几条。
|
|
58
|
+
"ark-coding-plan-cn": { label: "Ark Coding Plan (CN, legacy plugin)", envName: "ARK_CODING_PLAN_CN_API_KEY", credentialKind: "volc", volcAction: "GetCodingPlanUsage", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing", defaultEnabled: false },
|
|
59
|
+
"ark-agent-plan-cn": { label: "Ark Agent Plan (CN, legacy plugin)", envName: "ARK_AGENT_PLAN_CN_API_KEY", credentialKind: "volc", volcAction: "GetAFPUsage", volcHost: "open.volcengineapi.com", volcRegion: "cn-beijing", defaultEnabled: false },
|
|
44
60
|
// BytePlus(海外)Coding Plan:同一套 AK/SK,但走 BytePlus 管控面与 ap-southeast-1 区域。
|
|
45
|
-
"ark-coding-plan-byteplus": { label: "Ark Coding Plan (BytePlus)", envName: "ARK_CODING_PLAN_BYTEPLUS_API_KEY", credentialKind: "volc", volcAction: "GetCodingPlanUsage", volcHost: "ark.ap-southeast-1.byteplusapi.com", volcRegion: "ap-southeast-1", defaultEnabled: false },
|
|
61
|
+
"ark-coding-plan-byteplus": { label: "Ark Coding Plan (BytePlus, legacy plugin)", envName: "ARK_CODING_PLAN_BYTEPLUS_API_KEY", credentialKind: "volc", volcAction: "GetCodingPlanUsage", volcHost: "ark.ap-southeast-1.byteplusapi.com", volcRegion: "ap-southeast-1", defaultEnabled: false },
|
|
46
62
|
// DeepSeek 官方:余额型(不是订阅窗口),与推理**同一把** API Key,端点与响应见官方文档
|
|
47
63
|
// https://api-docs.deepseek.com/zh-cn/api/get-user-balance
|
|
48
64
|
"deepseek": { label: "DeepSeek", envName: "DEEPSEEK_API_KEY" },
|
|
@@ -280,7 +296,7 @@ function volcFailure(action, error) {
|
|
|
280
296
|
}
|
|
281
297
|
async function fetchJson(fetcher, url, headers, lifetimeSignal, init = {}) {
|
|
282
298
|
let response;
|
|
283
|
-
try { response = await fetcher(url, { method: init.method ?? "GET", ...(init.body !== undefined ? { body: init.body } : {}), headers: { accept: "application/json", "user-agent": "dsh-subusage/0.
|
|
299
|
+
try { response = await fetcher(url, { method: init.method ?? "GET", ...(init.body !== undefined ? { body: init.body } : {}), headers: { accept: "application/json", "user-agent": "dsh-subusage/0.10.0", ...headers }, signal: lifetimeSignal ? AbortSignal.any([AbortSignal.timeout(10000), lifetimeSignal]) : AbortSignal.timeout(10000), redirect: "manual" }); }
|
|
284
300
|
catch { throw failure("subusage/network", "网络请求失败或超时", true); }
|
|
285
301
|
if (response.status >= 300 && response.status < 400 || response.status === 401 || response.status === 403) throw failure("subusage/auth", "登录或凭据已失效");
|
|
286
302
|
if (!response.ok) {
|
|
@@ -843,19 +859,46 @@ export class SubUsageService extends TypertRemoteService {
|
|
|
843
859
|
// 火山方舟额度走管控面 OpenAPI:POST 根路径 + Action/Version 在 query,鉴权是 AK/SK 签名。
|
|
844
860
|
if (PROVIDERS[id].credentialKind === "volc") {
|
|
845
861
|
const spec = PROVIDERS[id];
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
862
|
+
const call = async (action, payload) => {
|
|
863
|
+
const body = payload === undefined ? "{}" : JSON.stringify(payload);
|
|
864
|
+
const signed = volcSignature({ accessKeyId: identity.key, secretAccessKey: identity.secret, action, host: spec.volcHost, region: spec.volcRegion, body });
|
|
865
|
+
const response = await get(signed.url, signed.headers, { method: "POST", body });
|
|
866
|
+
const apiError = volcError(response);
|
|
867
|
+
if (apiError) throw volcFailure(action, apiError);
|
|
868
|
+
return response;
|
|
869
|
+
};
|
|
870
|
+
const toWindows = parsed => cascadeRateLimited(parsed.windows.map(w => windowRow(w.kind, w.percent, w.resetsAt, w.percent >= 100 ? "rate-limited" : undefined, w.detail)));
|
|
871
|
+
// 企业版/团队版:额度挂在席位上,先取 SeatID 再查该席位。
|
|
872
|
+
if (spec.volcSeat) {
|
|
873
|
+
const seatIds = parseVolcSeatIds(await call("ListSeatInfos", { Filter: {}, Scene: spec.volcScene, PageNum: 1, PageSize: 100 }));
|
|
874
|
+
if (!seatIds.length) return { windows: [], extras: [{ kind: "plan", value: "该账号下没有可读的席位(或当前身份无权查看)" }], coverage: "partial" };
|
|
875
|
+
const seatId = seatIds[0];
|
|
876
|
+
// 多个席位时只看第一个,但如实说明总数,不假装看全了。
|
|
877
|
+
const seatNote = seatIds.length > 1 ? `${seatId}(共 ${seatIds.length} 个席位)` : seatId;
|
|
878
|
+
let parsed;
|
|
879
|
+
try {
|
|
880
|
+
parsed = spec.volcSeat === "agent"
|
|
881
|
+
? parseVolcSeatAfp(await call("GetSeatAFPUsage", { SeatIDs: [seatId] }))
|
|
882
|
+
// GetSeatInfoUsage **确实有 Scene 参数**(官方契约 + ark-cli 源码一致):Coding Plan 企业版传空字符串。
|
|
883
|
+
// 传错值(如 "coding_plan")会**静默返回空 SeatID 而不报错**,所以这里必须精确。
|
|
884
|
+
: parseVolcSeatCoding(await call("GetSeatInfoUsage", { SeatID: seatId, Scene: "" }));
|
|
885
|
+
} catch (error) { if (error instanceof RemoteError) throw error; throw failure("subusage/response", `火山方舟 ${spec.volcSeat === "agent" ? "GetSeatAFPUsage" : "GetSeatInfoUsage"} 的额度结构无法解析`); }
|
|
886
|
+
const extras = [];
|
|
887
|
+
if (parsed.plan) extras.push({ kind: "plan", value: parsed.plan });
|
|
888
|
+
// 与个人版口径一致:没有窗口时说清「本周期没有可读额度」,而不是只给一个 partial 状态。
|
|
889
|
+
extras.push({ kind: "account", value: parsed.windows.length ? `席位 ${seatNote}` : `席位 ${seatNote}(本周期没有可读的额度窗口)` });
|
|
890
|
+
return { windows: toWindows(parsed), extras, coverage: parsed.windows.length ? "complete" : "partial" };
|
|
891
|
+
}
|
|
892
|
+
const body = await call(spec.volcAction);
|
|
850
893
|
let parsed;
|
|
851
894
|
try { parsed = spec.volcAction === "GetAFPUsage" ? parseVolcAgentPlan(body) : parseVolcCodingPlan(body); }
|
|
852
895
|
catch { throw failure("subusage/response", `火山方舟 ${spec.volcAction} 的额度结构无法解析`); }
|
|
853
|
-
const windows = parsed
|
|
896
|
+
const windows = toWindows(parsed);
|
|
854
897
|
const extras = [];
|
|
855
898
|
if (parsed.plan) extras.push({ kind: "plan", value: parsed.plan });
|
|
856
899
|
// HTTP 200 但窗口为空是「未订阅」,不是 0% 用量:显式说明,不画零额度的假象。
|
|
857
900
|
else if (!windows.length) extras.push({ kind: "plan", value: parsed.subscribed ? "本周期暂无窗口数据" : "未检测到该套餐订阅" });
|
|
858
|
-
return { windows
|
|
901
|
+
return { windows, extras, coverage: windows.length ? "complete" : "partial" };
|
|
859
902
|
}
|
|
860
903
|
const headers = { authorization: `Bearer ${identity.key}` };
|
|
861
904
|
if (id === "synthetic") return normalizeSynthetic(await get("https://api.synthetic.new/v2/quotas", headers));
|
package/lib/volcengine.js
CHANGED
|
@@ -151,3 +151,58 @@ export function parseVolcAgentPlan(body) {
|
|
|
151
151
|
const plan = typeof result.PlanType === "string" && result.PlanType.trim() ? result.PlanType.trim() : undefined;
|
|
152
152
|
return { subscribed: windows.length > 0 || reported, windows, plan };
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
// ── 企业版/团队版:额度挂在「席位」上,要先 ListSeatInfos 取 SeatID ────────────────
|
|
156
|
+
|
|
157
|
+
/** 从 ListSeatInfos 的响应里取席位 ID 列表(路径为 Result.Data[].SeatID)。 */
|
|
158
|
+
export function parseVolcSeatIds(body) {
|
|
159
|
+
const data = body?.Result?.Data;
|
|
160
|
+
if (!Array.isArray(data)) return [];
|
|
161
|
+
return data.map(row => typeof row?.SeatID === "string" && row.SeatID.trim() ? row.SeatID.trim() : undefined).filter(Boolean);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Agent Plan 企业版单个席位:GetSeatAFPUsage 的响应(Result.SeatAFPUsages[],取第一条)。 */
|
|
165
|
+
export function parseVolcSeatAfp(body) {
|
|
166
|
+
const rows = body?.Result?.SeatAFPUsages;
|
|
167
|
+
if (!Array.isArray(rows) || !rows.length) return { subscribed: false, windows: [] };
|
|
168
|
+
const row = rows[0] && typeof rows[0] === "object" ? rows[0] : {};
|
|
169
|
+
const windows = [];
|
|
170
|
+
let reported = false;
|
|
171
|
+
for (const [field, kind] of AFP_WINDOWS) {
|
|
172
|
+
const window = row[field];
|
|
173
|
+
if (!window || typeof window !== "object") continue;
|
|
174
|
+
reported = true;
|
|
175
|
+
const quota = amount(window.Quota), used = amount(window.Used);
|
|
176
|
+
if (quota === undefined || used === undefined) throw new Error("Volcengine seat AFP window is invalid");
|
|
177
|
+
if (quota <= 0) continue;
|
|
178
|
+
windows.push({ kind, percent: Math.min(100, used / quota * 100), resetsAt: fromEpoch(window.ResetTime, "milliseconds"), detail: { used, limit: quota, unit: "AFP" } });
|
|
179
|
+
}
|
|
180
|
+
const plan = typeof row.PlanType === "string" && row.PlanType.trim() ? row.PlanType.trim() : undefined;
|
|
181
|
+
const seatId = typeof row.SeatID === "string" && row.SeatID.trim() ? row.SeatID.trim() : undefined;
|
|
182
|
+
return { subscribed: windows.length > 0 || reported, windows, plan, seatId };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Coding Plan 企业版单个席位:GetSeatInfoUsage 的响应(Result.SeatInfoUsage)。
|
|
187
|
+
* 三个字段都是**已用百分比**(字符串),缺字段不产出行、不当作 0。
|
|
188
|
+
* 窗口语义:WeeklyUsage→周、MonthlyUsage→月;ShortTermUsage 国内文档写「近 5 分钟用量」、
|
|
189
|
+
* 国际站英文文档写「5 小时百分比」,两站冲突——按短周期窗口处理,存疑见 docs/development.md。
|
|
190
|
+
*/
|
|
191
|
+
export function parseVolcSeatCoding(body) {
|
|
192
|
+
// **站点间结构不同**(已核实):国际站是 `Result.SeatInfoUsage.*`,中国站契约是 `Result.*` 直挂。
|
|
193
|
+
// 只认一种会让另一站点静默读不到数据,所以两条路径都试。
|
|
194
|
+
const result = body?.Result && typeof body.Result === "object" && !Array.isArray(body.Result) ? body.Result : undefined;
|
|
195
|
+
const nested = result?.SeatInfoUsage;
|
|
196
|
+
const info = nested && typeof nested === "object" && !Array.isArray(nested) ? nested : result;
|
|
197
|
+
if (!info || typeof info !== "object" || Array.isArray(info)) return { subscribed: false, windows: [] };
|
|
198
|
+
const windows = [];
|
|
199
|
+
// 三个 usage 字段各带一个同族的重置时刻(毫秒):只给百分比不给重置会让它和别家不一致。
|
|
200
|
+
// 注意 5 小时用量为 0 时 ShortTermResetMilestone 返回 -1,fromEpoch 的 ms>0 判定会挡掉。
|
|
201
|
+
for (const [field, kind, resetField] of [["ShortTermUsage", "5h", "ShortTermResetMilestone"], ["WeeklyUsage", "week", "WeeklyResetMilestone"], ["MonthlyUsage", "month", "MonthlyResetMilestone"]]) {
|
|
202
|
+
const value = amount(info[field]);
|
|
203
|
+
if (value === undefined) continue;
|
|
204
|
+
windows.push({ kind, percent: Math.min(100, value), resetsAt: fromEpoch(info[resetField], "milliseconds") });
|
|
205
|
+
}
|
|
206
|
+
const seatId = typeof info.SeatID === "string" && info.SeatID.trim() ? info.SeatID.trim() : undefined;
|
|
207
|
+
return { subscribed: windows.length > 0, windows, seatId };
|
|
208
|
+
}
|
package/locale/en.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Subscription usage",
|
|
3
|
-
"description": "Shows subscription quota for Z.ai, Kimi, MiMo,
|
|
3
|
+
"description": "Shows subscription quota and balance for 30 AI providers (DeepSeek, Volcengine Ark, Z.ai, Kimi, MiMo, Codex, MiniMax, OpenRouter, SiliconFlow, LiteLLM, ...), with one settings page for detection switches and credentials plus a read-only quota API."
|
|
4
4
|
}
|
package/locale/zh.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "订阅用量显示",
|
|
3
|
-
"description": "显示
|
|
3
|
+
"description": "显示 30 家 AI 提供商(DeepSeek、火山方舟 Ark、Z.ai、Kimi、MiMo、Codex、MiniMax、OpenRouter、SiliconFlow、LiteLLM 等)的订阅余量与余额;统一管理检测开关与凭据,并提供只读额度查询接口。"
|
|
4
4
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-subusage",
|
|
3
3
|
"displayName": "订阅用量显示",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/KouzakiUmi/dsh-subusage.git"
|
|
12
12
|
},
|
|
13
|
-
"description": "显示 DeepSeek
|
|
13
|
+
"description": "显示 DeepSeek 官方余额、火山方舟(arkcli 官方 CLI 路由与旧插件路由、Agent/Coding Plan 个人版与企业版席位)、Z.ai(中国与国际)、Kimi、MiMo、OpenCode Go、Command Code、SuperGrok、Codex(ChatGPT 订阅)、MiniMax(中国)、Synthetic、NanoGPT、SiliconFlow、OpenRouter、Novita、Hyperbolic、DeepInfra、Chutes、Ollama Cloud、Vercel AI Gateway、ZenMux、LiteLLM 的订阅余量与余额;共享刷新与凭据管理,默认关闭 19 家。",
|
|
14
14
|
"keywords": [
|
|
15
15
|
"dsh",
|
|
16
16
|
"dsh-plugin",
|