dsh-usage-state 0.4.7 → 0.4.9
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/CHANGELOG.md +23 -2
- package/README.en.md +12 -10
- package/README.md +12 -10
- package/docs/adapters.md +27 -58
- package/lib/client.js +61 -41
- package/lib/index.js +247 -142
- package/lib/typert.js +5 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
# 更新日志(Changelog)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
用户可见的变更按版本记录于此。当前设计见 [设计共识](https://github.com/takboo/dsh-usage-state/blob/main/docs/design-consensus.md),发布验证流程见 [发布与验收](https://github.com/takboo/dsh-usage-state/blob/main/docs/release.md)。
|
|
4
4
|
|
|
5
5
|
格式参照 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循语义化版本。0.3.0起发布npm,之前仅GitHub分发。已发布日期按 [npm registry](https://registry.npmjs.org/dsh-usage-state) 的UTC记录;历史比较引用说明见文末。
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.9] - 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- DeepSeek 新增账号余额模式:`deepseek-account` 使用 Harness 登录服务读取充值与赠送钱包,`deepseek-official` 保持 API Key 余额;凭据、缓存及登录提示分别处理,账号模式无需 API Key。
|
|
14
|
+
- 退出登录、换号与晚返回响应隔离;非法钱包不当成零,失败只保留同一登录身份的上次成功余额。
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- 重写当前文档入口、架构、排查和待办,归档旧审计、研究与设计过程;删除重复研究索引和过时发布操作,集中保留真实验证边界。
|
|
19
|
+
|
|
20
|
+
## [0.4.8] - 2026-10-17
|
|
21
|
+
|
|
22
|
+
### Compatibility
|
|
23
|
+
|
|
24
|
+
- 完成 DSH 0.2.1-rc.2 兼容性复验:隔离 tarball 安装与宿主启动、设置页、模型目录、getState/describeCredentials、已保存凭据优先级和隐藏/API配置往返均通过;输入区状态行可与新增的1017模型标记同时显示。
|
|
25
|
+
- 未发现新的运行兼容性问题,源码及三个预构建入口与0.4.7一致。现有 `^0.2.0-rc.2 || ^0.2.1-rc.1` 已覆盖rc.2,保留支持下界及peer范围,更新中英兼容性声明。
|
|
26
|
+
- CI将0.2.1-rc.1与0.2.1-rc.2纳入独立矩阵,分别运行精确版本的真实Typert host/browser及产物契约测试;333项基线测试及rc.2的18项契约测试通过。浏览器验证使用本地模拟余额与模型流,不访问真实厂商账户。
|
|
27
|
+
|
|
9
28
|
## [0.4.7] - 2026-10-17
|
|
10
29
|
|
|
11
30
|
### Fixed
|
|
@@ -127,7 +146,9 @@
|
|
|
127
146
|
|
|
128
147
|
0.4.5起建立版本tag/Release;更早发布没有保存这些引用。0.3.0至0.4.4的比较端点来自各npm版本的gitHead;0.2系列引用对应实现提交,不能替代当时没有保存的正式发布产物。早期日期沿用历史版本记录。今后版本应按发布流程建立稳定引用。
|
|
129
148
|
|
|
130
|
-
[Unreleased]: https://github.com/takboo/dsh-usage-state/compare/v0.4.
|
|
149
|
+
[Unreleased]: https://github.com/takboo/dsh-usage-state/compare/v0.4.9...HEAD
|
|
150
|
+
[0.4.9]: https://github.com/takboo/dsh-usage-state/compare/v0.4.8...v0.4.9
|
|
151
|
+
[0.4.8]: https://github.com/takboo/dsh-usage-state/compare/v0.4.7...v0.4.8
|
|
131
152
|
[0.4.7]: https://github.com/takboo/dsh-usage-state/compare/v0.4.6...v0.4.7
|
|
132
153
|
[0.4.6]: https://github.com/takboo/dsh-usage-state/compare/v0.4.5...v0.4.6
|
|
133
154
|
[0.4.5]: https://github.com/takboo/dsh-usage-state/compare/3ad9a71e2f7e4b9a05c69bc529e396a364ae6aeb...v0.4.5
|
package/README.en.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://github.com/takboo/dsh-usage-state/actions/workflows/ci.yml) [](https://www.npmjs.com/package/dsh-usage-state)
|
|
6
6
|
|
|
7
|
-
> **
|
|
7
|
+
> **0.4.9** adds DeepSeek Harness account sign-in balances, separate from API Key queries. See the [GitHub Release](https://github.com/takboo/dsh-usage-state/releases/tag/v0.4.9) for the tarball and checksum and the [Changelog](CHANGELOG.md#049---2026-10-23) for details.
|
|
8
8
|
|
|
9
9
|
Show the current model provider's **account balance** or **coding-plan quota** above the composer input in [DSH (DeepSeek Harness)](https://github.com/deepseek-ai/deepseek-harness). The line matches the input card's width and preserves the native statistics and context meter.
|
|
10
10
|
|
|
@@ -23,7 +23,7 @@ DeepSeek · ¥58.13
|
|
|
23
23
|
|
|
24
24
|
## Install
|
|
25
25
|
|
|
26
|
-
Requires DSH matching `^0.2.0-rc.2 || ^0.2.1-rc.1` (
|
|
26
|
+
Requires DSH matching `^0.2.0-rc.2 || ^0.2.1-rc.1` (verified on 0.2.0-rc.2, 0.2.1-rc.1, and 0.2.1-rc.2). The prebuilt plugin declares Node ≥20. Install into the `web` profile you actually use.
|
|
27
27
|
|
|
28
28
|
Install from npm:
|
|
29
29
|
|
|
@@ -47,22 +47,25 @@ Use historical version 0.3.2 on DSH 0.1; it does not work on 0.2. Version 0.4.0
|
|
|
47
47
|
## Quick start
|
|
48
48
|
|
|
49
49
|
1. Open **Settings → Usage state** to inspect providers, models and the detected source.
|
|
50
|
-
2. Keep Auto for recognized providers, or choose a supported API, Coding Plan or Hidden option.
|
|
50
|
+
2. Keep Auto for recognized providers, or choose a supported API Key balance, Account balance, Coding Plan or Hidden option.
|
|
51
51
|
3. Set your instance URL for a self-hosted Sub2API source. The current model's reading appears above the input.
|
|
52
52
|
4. Use Refresh now for an explicit query. Check the settings error and the [troubleshooting guide (Chinese)](https://github.com/takboo/dsh-usage-state/blob/main/docs/troubleshooting.md) if it fails.
|
|
53
53
|
|
|
54
|
-
Advanced controls offer source
|
|
54
|
+
Advanced controls offer source selection; API Key mode also provides endpoint, credential name and key writes. Explicit provider overrides take precedence over legacy source defaults. Descriptions and requests share the candidate order; saves prioritize the explicit ref. Pinned endpoints do not try mirrors, and account configuration/key changes discard previous-identity readings. Configuration and credential write permissions are separate; saves/clears report failure and prevent repeated in-flight operations.
|
|
55
55
|
|
|
56
56
|
## Sources
|
|
57
57
|
|
|
58
|
-
| Source |
|
|
58
|
+
| Source | Balance reading | Coding-plan reading | Main credential |
|
|
59
59
|
|---|---|---|---|
|
|
60
|
-
| DeepSeek official | CNY/USD balance | — | DEEPSEEK_API_KEY |
|
|
60
|
+
| DeepSeek official API Key | CNY/USD balance | — | DEEPSEEK_API_KEY |
|
|
61
|
+
| DeepSeek account sign-in | CNY/USD recharge and bonus balance | — | Current Harness login |
|
|
61
62
|
| z.ai / Zhipu GLM | — | 5h / 7d used % | ZAI_API_KEY and related refs |
|
|
62
63
|
| Kimi / Moonshot | CNY balance | Kimi Code subscription windows | MOONSHOT_API_KEY / KIMI_CODING_API_KEY |
|
|
63
64
|
| OpenCode Zen Go | — | 5h / 7d / 30d used % | OPENCODE_GO_API_KEY / OPENCODE_API_KEY |
|
|
64
65
|
| Sub2API | Balance or key allowance | Depends on the instance, e.g. 5h / 1d / 7d / 30d | SUB2API_API_KEY and instance URL |
|
|
65
66
|
|
|
67
|
+
**DeepSeek Desktop account sign-in:** `deepseek-account` automatically uses “Account balance” through the Harness account service, adding recharge and bonus wallets. `deepseek-official` uses “API Key balance”. Queries and caches are separate; account mode never probes or falls back to `DEEPSEEK_API_KEY`. Sign in through Harness account settings. Account mode requires the host account service; older hosts without it need an upgrade. Harness owns the endpoint and login grant; account mode has no API key fields.
|
|
68
|
+
|
|
66
69
|
Kimi Code routes `kimi-code`, `kimi-coding`, and `kimi-for-coding`, plus custom providers declaring `api.kimi.com` / `api.kimi.ai`, automatically read subscription usage. The plugin displays only returned windows: 5h, 7d, monthly total, and monthly code usage. Moonshot regional APIs retain CNY balance mode. Explicit settings take precedence.
|
|
67
70
|
|
|
68
71
|
Percentages mean **used**. z.ai keys belong to their China/global region; China is the default endpoint and failures may try the mirror. The current cache merges by source and mode, so separate keys/instances must not be treated as supported independent accounts. Endpoints, field semantics and extension steps are in the [adapter guide (Chinese)](https://github.com/takboo/dsh-usage-state/blob/main/docs/adapters.md).
|
|
@@ -83,15 +86,14 @@ Provider settings and advanced controls:
|
|
|
83
86
|
|
|
84
87
|
The host schedules refreshes 2 seconds after a turn ends and has a 5-minute fallback timer. The browser reads the RPC mirror every 30 seconds. Ordinary polling initializes a missing or changed account identity, otherwise it does not shorten idle queries to once a minute. Successful requests have a default 60-second minimum; explicit refresh can bypass it, and active retries after failure are not throttled by that successful-request interval. Editing the idle interval reschedules the timer.
|
|
85
88
|
|
|
86
|
-
The plugin queries balances/quotas and reads the selected model and provider catalog. It writes no session logs and reports no readings to third parties. The host does not return stored key values to the browser; a key the user actively pastes is sent through DSH's credential service to the credential file in the selected home. Credential precedence follows the host: DSH 0.2.1-rc.1 Web/Desktop prefers saved credentials, with environment values as fallback; 0.2.0-rc.2 prefers the environment. The settings page reflects the host’s actual write permissions.
|
|
89
|
+
The plugin queries balances/quotas and reads the selected model and provider catalog. It writes no session logs and reports no readings to third parties. The host does not return stored key values to the browser; a key the user actively pastes is sent through DSH's credential service to the credential file in the selected home. Credential precedence follows the host: DSH 0.2.1-rc.1/rc.2 Web/Desktop prefers saved credentials, with environment values as fallback; 0.2.0-rc.2 prefers the environment. The settings page reflects the host’s actual write permissions.
|
|
87
90
|
|
|
88
91
|
## Known limitations
|
|
89
92
|
|
|
90
93
|
See the [Backlog (Chinese)](https://github.com/takboo/dsh-usage-state/blob/main/docs/backlog.md) for remediation status and verification scope.
|
|
91
94
|
|
|
92
|
-
- **Older npm 0.4.4 and earlier:** still has credential override, pin/cache identity, legacy hiding, default ordering and failure-display defects. It still divides Moonshot balances ≥100 yuan by 100 and may turn invalid DeepSeek amounts into zero. These are fixed in 0.4.5; upgrade if you use an older version. Cross-check old-version amounts against the [official API](https://platform.kimi.com/docs/api/balance.md).
|
|
93
95
|
- **Account scope:** one target is still selected per source and mode. Identity isolation does not add independent multi-account support.
|
|
94
|
-
- **Live verification:** Kimi Code monthly quotas, Sub2API accounts, high-threshold colors, real credential writes and nonzero OpenCode readings still lack live coverage. Local interaction tests do not replace that acceptance.
|
|
96
|
+
- **Live verification:** this release’s real Desktop account login and visuals, Kimi Code monthly quotas, Sub2API accounts, high-threshold colors, real credential writes and nonzero OpenCode readings still lack live coverage. Local interaction tests do not replace that acceptance.
|
|
95
97
|
- **Scope:** the line can be absent without a selected model or when hidden; clicking it does not open settings; readings are not saved as history.
|
|
96
98
|
|
|
97
99
|
## Development and docs
|
|
@@ -109,6 +111,6 @@ See the [development guide (Chinese)](https://github.com/takboo/dsh-usage-state/
|
|
|
109
111
|
|
|
110
112
|
## Acknowledgements and license
|
|
111
113
|
|
|
112
|
-
Thanks to [dsh-cost-meter](https://github.com/Han-1413141/dsh-cost-meter) by Han-1413141 (MIT) for data-source behavior references. This TypeScript implementation was written independently; endpoint, field and compatibility provenance is recorded in the [upstream analysis](https://github.com/takboo/dsh-usage-state/blob/main/docs/research/dsh-cost-meter-analysis.md). DSH provides the settings, credentials, RPC, slots and UI primitives.
|
|
114
|
+
Thanks to [dsh-cost-meter](https://github.com/Han-1413141/dsh-cost-meter) by Han-1413141 (MIT) for data-source behavior references. This TypeScript implementation was written independently; endpoint, field and compatibility provenance is recorded in the [upstream analysis](https://github.com/takboo/dsh-usage-state/blob/main/docs/archive/research/dsh-cost-meter-analysis.md). DSH provides the settings, credentials, RPC, slots and UI primitives.
|
|
113
115
|
|
|
114
116
|
[MIT](LICENSE). Report problems or attribution suggestions through [Issues](https://github.com/takboo/dsh-usage-state/issues).
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://github.com/takboo/dsh-usage-state/actions/workflows/ci.yml) [](https://www.npmjs.com/package/dsh-usage-state)
|
|
6
6
|
|
|
7
|
-
>
|
|
7
|
+
> **0.4.9** 新增 DeepSeek Harness 账号登录余额,与 API Key 查询独立。安装包及校验文件见 [GitHub Release](https://github.com/takboo/dsh-usage-state/releases/tag/v0.4.9),详情见 [更新日志](CHANGELOG.md#049---2026-10-23)。
|
|
8
8
|
|
|
9
9
|
在 [DSH(DeepSeek Harness)](https://github.com/deepseek-ai/deepseek-harness) 输入框上方显示当前模型供应商的**账户余额**或**套餐额度**。状态行与输入卡片同宽,保留原生统计行和上下文计量器。
|
|
10
10
|
|
|
@@ -23,7 +23,7 @@ DeepSeek · ¥58.13
|
|
|
23
23
|
|
|
24
24
|
## 安装
|
|
25
25
|
|
|
26
|
-
要求:DSH版本满足 `^0.2.0-rc.2 || ^0.2.1-rc.1
|
|
26
|
+
要求:DSH版本满足 `^0.2.0-rc.2 || ^0.2.1-rc.1`(已验证0.2.0-rc.2、0.2.1-rc.1和0.2.1-rc.2),运行预构建插件声明Node≥20,安装到正在使用的 `web` profile。
|
|
27
27
|
|
|
28
28
|
从npm安装:
|
|
29
29
|
|
|
@@ -47,22 +47,25 @@ DSH0.1使用历史版本0.3.2;该版本不适配0.2。0.4.0有RPC挂载缺陷
|
|
|
47
47
|
## 快速开始
|
|
48
48
|
|
|
49
49
|
1. 打开 **设置→用量状态**,查看供应商、模型和检测到的数据源。
|
|
50
|
-
2. 对已识别供应商保留“自动”,或选择其支持的API
|
|
50
|
+
2. 对已识别供应商保留“自动”,或选择其支持的API Key余额、账号余额、Coding Plan、隐藏。
|
|
51
51
|
3. 自建Sub2API在高级区填写实例地址;已配置模型的当前读数显示在输入框上方。
|
|
52
52
|
4. “立即刷新”用于主动查询;出错先查看设置页原因及 [排查指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/troubleshooting.md)。
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
高级区提供数据源选择;API Key模式另有端点、凭据名和密钥写入入口。显式provider覆盖优先于旧source默认;凭据描述与读取共用候选顺序,保存优先写显式ref。固定端点不试镜像,配置或凭据身份变化后清除旧身份读数。只读配置与凭据写入权限分别处理,保存/清除过程会显示失败并禁用重复操作。
|
|
55
55
|
|
|
56
56
|
## 数据源
|
|
57
57
|
|
|
58
|
-
| 来源 |
|
|
58
|
+
| 来源 | 余额读数 | Coding Plan读数 | 主要凭据 |
|
|
59
59
|
|---|---|---|---|
|
|
60
|
-
| DeepSeek官方 | CNY/USD余额 | — | DEEPSEEK_API_KEY |
|
|
60
|
+
| DeepSeek官方 API Key | CNY/USD余额 | — | DEEPSEEK_API_KEY |
|
|
61
|
+
| DeepSeek账号登录 | CNY/USD充值与赠送余额 | — | Harness当前登录账号 |
|
|
61
62
|
| z.ai / 智谱GLM | — | 5h / 7d已用% | ZAI_API_KEY等 |
|
|
62
63
|
| Kimi / Moonshot | 人民币余额 | Kimi Code订阅窗口 | MOONSHOT_API_KEY / KIMI_CODING_API_KEY |
|
|
63
64
|
| OpenCode Zen Go | — | 5h / 7d / 30d已用% | OPENCODE_GO_API_KEY / OPENCODE_API_KEY |
|
|
64
65
|
| Sub2API | 余额或key配额 | 按实例响应,如5h / 1d / 7d / 30d | SUB2API_API_KEY及实例地址 |
|
|
65
66
|
|
|
67
|
+
**DeepSeek 桌面端账号登录**:`deepseek-account` 路由自动选择「账号余额」,通过 Harness 登录服务读取充值与赠送余额之和,无需 API Key;`deepseek-official` 路由选择「API Key 余额」。两者独立查询和缓存,账号模式不会探测或回退使用 `DEEPSEEK_API_KEY`。请在 Harness 账号设置中登录;账号模式要求宿主提供账号服务;旧宿主缺少该服务时需升级宿主。账号模式的端点与登录凭据由宿主管理,高级区不提供 API Key 输入。
|
|
68
|
+
|
|
66
69
|
Kimi Code 的 `kimi-code`、`kimi-coding`、`kimi-for-coding` provider,以及声明 `api.kimi.com` / `api.kimi.ai` 的自定义 provider,会自动读取套餐用量。支持 5h、7d、月总额度及编程月用量,仅展示账户实际返回的窗口。Moonshot 国内/国际 API 继续显示人民币余额;可在设置中显式选择模式。
|
|
67
70
|
|
|
68
71
|
百分比表示**已用**。z.ai的key分国内/国际区域;默认国内地址,失败可尝试镜像。当前缓存按数据源与模式合并,不能将不同密钥/实例视为已支持独立多账户。厂商端点、字段和扩展步骤见 [适配器指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/adapters.md)。
|
|
@@ -83,15 +86,14 @@ OpenCode Zen Go三个窗口,独立状态行位于输入框上方:
|
|
|
83
86
|
|
|
84
87
|
宿主默认在回合结束2秒后调度刷新,并有5分钟兜底定时器;浏览器每30秒拉取RPC镜像。普通轮询仅在读数缺失或有效身份变化时初始化查询,不把idle查询改成每分钟一次。成功请求的默认最小间隔为60秒,显式刷新可绕过,失败的主动重试不受该成功间隔限制;修改空闲间隔会重调定时器。
|
|
85
88
|
|
|
86
|
-
插件查询余额/额度并读取当前模型及供应商目录,不写会话日志,也不向第三方上报这些读数。宿主不把已保存的密钥值返回浏览器;用户主动粘贴的草稿经DSH凭据服务写入所用home的凭据文件。凭据优先级跟随宿主:DSH 0.2.1-rc.1 的 Web/桌面端优先使用已保存凭据,环境变量作为回退;0.2.0-rc.2 则优先使用环境变量。凭据权限以设置页实际返回为准。
|
|
89
|
+
插件查询余额/额度并读取当前模型及供应商目录,不写会话日志,也不向第三方上报这些读数。宿主不把已保存的密钥值返回浏览器;用户主动粘贴的草稿经DSH凭据服务写入所用home的凭据文件。凭据优先级跟随宿主:DSH 0.2.1-rc.1/rc.2 的 Web/桌面端优先使用已保存凭据,环境变量作为回退;0.2.0-rc.2 则优先使用环境变量。凭据权限以设置页实际返回为准。
|
|
87
90
|
|
|
88
91
|
## 已知限制
|
|
89
92
|
|
|
90
93
|
完整整改状态及验证范围见 [待办清单](https://github.com/takboo/dsh-usage-state/blob/main/docs/backlog.md)。
|
|
91
94
|
|
|
92
|
-
- **旧版npm0.4.4及更早版本**:仍有provider凭据覆盖、pin/缓存身份、legacy隐藏、默认排序和失败展示缺陷;Moonshot≥100元仍会被除以100,DeepSeek非法金额仍可能变0。这些已在0.4.5修复;使用旧版请升级。旧版金额应与 [官方接口](https://platform.kimi.com/docs/api/balance.md) 核对。
|
|
93
95
|
- **账户范围**:仍按source+mode选择一个目标;配置身份隔离不等于新增独立多账户支持。
|
|
94
|
-
-
|
|
96
|
+
- **未验证范围**:本次账号模式的真实 Desktop 登录账户与视觉、Kimi Code月额度真实账户、Sub2API真实账户、高阈值视觉、真实凭据写入和OpenCode非零路径仍缺活体验证;本地交互测试不代替这些验收。
|
|
95
97
|
- **范围**:无模型选择或隐藏时可以不显示;点击状态行不打开设置;不保存历史读数。
|
|
96
98
|
|
|
97
99
|
## 开发与文档
|
|
@@ -109,6 +111,6 @@ npm test
|
|
|
109
111
|
|
|
110
112
|
## 致谢与许可
|
|
111
113
|
|
|
112
|
-
感谢 [dsh-cost-meter](https://github.com/Han-1413141/dsh-cost-meter)(Han-1413141,MIT
|
|
114
|
+
感谢 [dsh-cost-meter](https://github.com/Han-1413141/dsh-cost-meter)(Han-1413141,MIT)提供的数据源行为参考。本实现独立编写;历史端点与行为溯源见 [归档上游分析](https://github.com/takboo/dsh-usage-state/blob/main/docs/archive/research/dsh-cost-meter-analysis.md)。宿主平台为DSH,使用其设置、凭据、RPC、插槽和UI原语。
|
|
113
115
|
|
|
114
116
|
[MIT](LICENSE)。问题和署名建议通过 [Issues](https://github.com/takboo/dsh-usage-state/issues) 提交。
|
package/docs/adapters.md
CHANGED
|
@@ -1,72 +1,41 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 数据源适配器
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
当前支持DeepSeek、z.ai、Kimi/Moonshot、OpenCode和Sub2API。用户排查见 [指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/troubleshooting.md),代码入口见 [架构](https://github.com/takboo/dsh-usage-state/blob/main/docs/architecture.md),未验证项见 [待办](https://github.com/takboo/dsh-usage-state/blob/main/docs/backlog.md)。本文随npm分发。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## HTTP来源契约
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[UsageSource](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/types.ts) 定义id、modes、credentialRefs、defaultBaseUrl、request、可选fallbackRequests及parse。request纯构造url/headers,parse纯解析;网络由 [read.ts](https://github.com/takboo/dsh-usage-state/blob/main/src/host/read.ts) 执行,单请求默认15秒超时。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
interface UsageSource {
|
|
11
|
-
id: string
|
|
12
|
-
displayName: string
|
|
13
|
-
modes: readonly UsageMode[]
|
|
14
|
-
credentialRefs(mode: UsageMode): readonly string[]
|
|
15
|
-
defaultBaseUrl(mode: UsageMode): string | undefined
|
|
16
|
-
requiresBaseUrl?: boolean
|
|
17
|
-
requiresApiKey?: boolean
|
|
18
|
-
request(input: RequestInput): UsageRequest
|
|
19
|
-
fallbackRequests?(input: RequestInput): readonly UsageRequest[]
|
|
20
|
-
parse(payload: unknown, mode: UsageMode): UsageReading
|
|
21
|
-
}
|
|
22
|
-
```
|
|
9
|
+
HTTP401/403归auth;JSON语法错归parse,响应体超时/中断归network。仅未固定端点时按Adapter声明尝试镜像。无有效金额/窗口抛SourceError,不以伪造零覆盖成功值。金额必须根据官方字段主单位解析,百分比与计数按明确口径转换。
|
|
23
10
|
|
|
24
|
-
|
|
11
|
+
`requiresApiKey`字段本身不会增加免密钥读取能力。HTTP来源仍需完成凭据解析;DeepSeek账号模式使用下面单独的宿主服务通道。
|
|
25
12
|
|
|
26
|
-
|
|
13
|
+
## 字段语义
|
|
27
14
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
1. 用官方文档或固定commit接口定义查清端点、鉴权、单位、百分比口径、重置时刻及错误信封;没有官方资料时明确标社区观察/未验证。fixture去除key和账户信息。
|
|
31
|
-
2. 新建Adapter和源测试,覆盖正常余额/窗口、真实0、非法/缺失字段、数字串、不同时间形态,以及HTTP200错误信封。无有效读数应抛SourceError,不填伪造0。
|
|
32
|
-
3. 在 [注册表](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/index.ts) 注册;用目录测试验证元数据可JSON传输。基础设置选项可从该注册表派生。
|
|
33
|
-
4. 若需要自动发现provider,在 [共享hints](https://github.com/takboo/dsh-usage-state/blob/main/src/shared/config.ts) 维护路由名/host规则并加冲突测试。注册Adapter不会自动识别新provider;宿主会将可用端点的origin提示经RPC传到两UI,扩展时校验不含userinfo/query/key。
|
|
34
|
-
5. 若新增窗口id或用户文案,同步 [中英词典](https://github.com/takboo/dsh-usage-state/blob/main/src/client/locales.ts)。窗口顺序由Adapter决定,展示层按返回顺序显示。
|
|
35
|
-
6. 补provider配置到fake fetch和RPC/显示的装配用例,再执行typecheck→build→test→pack。更新两份README、此文字段语义、Backlog验证状态和Unreleased。
|
|
36
|
-
|
|
37
|
-
开发命令和toolchain见 [开发指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/development.md)。新Adapter不应引入成本计算或历史记录。
|
|
38
|
-
|
|
39
|
-
## 归一化和错误约定
|
|
40
|
-
|
|
41
|
-
- 金额保留明确币种/单位,使用来源契约换算,不按数值大小猜“元或分”。数值必须有限;缺失与真实0分别处理。
|
|
42
|
-
- percentage已是0..100时用clampPercent。normalizePercent把0..1视为比例,因此1的解释有歧义;只在来源确需这种兼容时使用并注明。不得拿它解析明确的1%。
|
|
43
|
-
- used/limit只在limit有效且>0时换算;remaining需按来源说明反转。unlimited哨兵不能展示成负余额/负额度。
|
|
44
|
-
- normalizeResetAt支持unix秒、毫秒及ISO。不存在的重置时间保持undefined,不猜一个日期。
|
|
45
|
-
- normalizeBaseUrl去尾斜杠和/vN;provider声明端点目前先取origin。若来源路径有额外前缀,必须测试请求路径,不能假定都从根目录挂载。
|
|
46
|
-
- SourceError区分config/auth/http/network/parse;detail不得主动包含密钥。401/403由读取层归auth,JSON语法错归parse、body中断归network;HTTP200错误信封由Adapter识别。宿主快照边界精确替换本次已解析key的原文回显,不可依赖猜测key形状的正则。
|
|
47
|
-
- source+mode仍只选择一个目标,宿主用有效签名、私有凭据摘要和generation隔离身份/在途;不表示新增多账户,也不在Adapter另建缓存。
|
|
48
|
-
- 共享平台实例按DSH指南声明peer+dev,独立第三方依赖放dependencies;不要将旧宿主link解析经验写成所有平台包都不能import的规则。
|
|
49
|
-
|
|
50
|
-
归一化实现见 [normalize](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/normalize.ts),网络实现见 [read](https://github.com/takboo/dsh-usage-state/blob/main/src/host/read.ts)。
|
|
51
|
-
|
|
52
|
-
## 当前端点和字段语义
|
|
53
|
-
|
|
54
|
-
| 来源 | 请求及鉴权 | 读数语义与证据 |
|
|
15
|
+
| 来源/模式 | 请求与鉴权 | 归一化语义 |
|
|
55
16
|
|---|---|---|
|
|
56
|
-
| DeepSeek
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
| Kimi Code | GET api.kimi.com/coding/v1/usages
|
|
60
|
-
|
|
|
61
|
-
|
|
|
17
|
+
| DeepSeek账号 | `deepseekAccount.getBalance(client)`;不构造HTTP、不读取API Key | `value`为充值钱包,`bonusWallets`为赠送钱包;同币种求和,保留toppedUp/granted,优先有余额/CNY |
|
|
18
|
+
| DeepSeek API Key | GET `{base}/user/balance`,Bearer | 多币种balance_infos;优先有余额/CNY,保留赠送/充值拆分;非法total跳过,无有效行报parse |
|
|
19
|
+
| z.ai Coding Plan | `/api/monitor/usage/quota/limit`,Key | 国内/国际端点与错误信封;保留5h/7d已用百分比,跳过无效窗口 |
|
|
20
|
+
| Kimi Code | GET `https://api.kimi.com/coding/v1/usages`,Bearer;区域镜像 | usages比例池或旧usage/limits计数;支持5h/7d/月总/编程月;仅显示实际返回窗口 |
|
|
21
|
+
| Moonshot | GET `{base}/v1/users/me/balance`,Bearer | available_balance单位为人民币元,不按数值大小推测分/元 |
|
|
22
|
+
| OpenCode Zen Go | GET `{base}/zen/go/usage`,Bearer及浏览器UA | rolling/weekly/monthly归一为5h/7d/30d;percent已是0–100 |
|
|
23
|
+
| Sub2API | GET `{base}/v1/usage`,Bearer;需实例地址 | Key配额/余额与实例实际返回的订阅窗口;不把无限值当可用金额 |
|
|
24
|
+
|
|
25
|
+
厂商语义优先依 [DeepSeek官方余额接口](https://api-docs.deepseek.com/api/get-user-balance) 与 [Moonshot官方余额定义](https://platform.kimi.com/docs/api/balance.md)。Kimi契约与兼容样本见 [实现](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/kimi.ts) 和 [源测试](https://github.com/takboo/dsh-usage-state/blob/main/tests/sources/kimi.test.ts)。社区接口与历史逆向证据在 [归档](https://github.com/takboo/dsh-usage-state/blob/main/docs/archive/README.md),使用前重新核验。
|
|
62
26
|
|
|
63
|
-
|
|
27
|
+
## DeepSeek Harness 账号与 API Key
|
|
64
28
|
|
|
65
|
-
|
|
29
|
+
自动识别 `deepseek-account → deepseek:account`,`deepseek-official → deepseek:api`。账号模式忽略API Key ref与余额端点覆盖,无服务或无登录时不回退。Host通过 `getPlatformSession()` 的issuer/token摘要隔离身份,原始token不进入通用Key字段、RPC、配置或错误详情;读取前后复核身份,签出事件清缓存。
|
|
66
30
|
|
|
67
|
-
|
|
31
|
+
账号服务负责目的地址、登录协议、超时和grant失效。client元数据使用Host包版本、Host locale与时区;无可解析版本时留空。`getBalance()` 返回安全投影:`status: ready`、充值钱包数组value、赠送钱包数组bonusWallets;每行含CNY/USD及主单位balance。金额/数组非法报parse,真实零保留;同币种充值与赠送合计为显示余额。
|
|
68
32
|
|
|
69
|
-
|
|
33
|
+
`status: failed`保守归http,因为Host已折叠底层失败;插件无法再细分网络与协议错误。null表示登录不可用。失败只保留同一登录身份的上次成功值;身份改变后清除。契约核对来源为2026-10-23安装的Desktop包 `dsh-deepseek-account-platform` 和 `dsh-llm-deepseek-account`,以及 [官方账号服务文档](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/credentials/deepseek-account-platform/README.md)。真实账户验收范围见待办,不由契约核对代替。
|
|
70
34
|
|
|
35
|
+
## 增加或修改来源
|
|
71
36
|
|
|
72
|
-
|
|
37
|
+
1. 核对官方接口、鉴权、单位和错误形状;未文档化契约标明来源、日期及不确定性,fixture去除账户信息。
|
|
38
|
+
2. 在 [sources](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/index.ts) 注册,使用 [模板](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/_template.ts);模式必须是来源确实支持的读数。
|
|
39
|
+
3. 如需自动发现,更新共享provider/source规则并测命名冲突;新模式同步线上类型、Host codec、两UI及目录。
|
|
40
|
+
4. 覆盖实际装配链、非法字段/真实0、身份切换与失败;保证凭据描述和读取采用同一有效选择。
|
|
41
|
+
5. 按 [开发流程](https://github.com/takboo/dsh-usage-state/blob/main/docs/development.md) 完成typecheck、build、test、pack;更新双语README、当前文档与Changelog,未做的真实验收留在待办。
|
package/lib/client.js
CHANGED
|
@@ -67,11 +67,13 @@ window.__ModuleLoader__.load({
|
|
|
67
67
|
};
|
|
68
68
|
const MODES = [
|
|
69
69
|
"api",
|
|
70
|
+
"account",
|
|
70
71
|
"coding-plan",
|
|
71
72
|
"hidden"
|
|
72
73
|
];
|
|
73
74
|
const PROVIDER_MODES = [
|
|
74
75
|
"api",
|
|
76
|
+
"account",
|
|
75
77
|
"coding-plan",
|
|
76
78
|
"hidden",
|
|
77
79
|
"auto"
|
|
@@ -340,7 +342,7 @@ window.__ModuleLoader__.load({
|
|
|
340
342
|
mode: null,
|
|
341
343
|
reason: "unknown-source"
|
|
342
344
|
};
|
|
343
|
-
if (entry?.mode === "api" || entry?.mode === "coding-plan") {
|
|
345
|
+
if (entry?.mode === "api" || entry?.mode === "account" || entry?.mode === "coding-plan") {
|
|
344
346
|
if (!source.modes.includes(entry.mode)) return {
|
|
345
347
|
...base,
|
|
346
348
|
sourceId,
|
|
@@ -360,7 +362,7 @@ window.__ModuleLoader__.load({
|
|
|
360
362
|
mode: null,
|
|
361
363
|
reason: "needs-endpoint"
|
|
362
364
|
};
|
|
363
|
-
const mode = sourceId === "kimi" ? kimiMode(input.provider, baseUrl) : source.modes[0];
|
|
365
|
+
const mode = sourceId === "deepseek" && input.provider === "deepseek-account" ? "account" : sourceId === "kimi" ? kimiMode(input.provider, baseUrl) : source.modes[0];
|
|
364
366
|
if (mode === void 0 || !source.modes.includes(mode)) return {
|
|
365
367
|
...base,
|
|
366
368
|
sourceId,
|
|
@@ -546,6 +548,7 @@ window.__ModuleLoader__.load({
|
|
|
546
548
|
/** Models are listed for orientation only, so the line stays short. */
|
|
547
549
|
const MAX_MODELS_SHOWN = 6;
|
|
548
550
|
function modeLabel(mode, t) {
|
|
551
|
+
if (mode === "account") return t("modeAccount");
|
|
549
552
|
if (mode === "api") return t("modeApi");
|
|
550
553
|
if (mode === "coding-plan") return t("modeCodingPlan");
|
|
551
554
|
if (mode === "hidden") return t("modeHidden");
|
|
@@ -729,6 +732,7 @@ window.__ModuleLoader__.load({
|
|
|
729
732
|
const resolvedSource = row.resolution.sourceId === null ? void 0 : props.catalog.find((candidate) => candidate.id === row.resolution.sourceId);
|
|
730
733
|
const refMode = row.resolution.mode ?? resolvedSource?.modes[0];
|
|
731
734
|
const refs = refMode === void 0 ? [] : [...resolvedSource?.credentialRefs[refMode] ?? []];
|
|
735
|
+
const accountMode = row.resolution.mode === "account";
|
|
732
736
|
const modelNames = row.models.map((model) => model.name);
|
|
733
737
|
const listed = modelNames.slice(0, MAX_MODELS_SHOWN).join(" · ");
|
|
734
738
|
const suffix = modelNames.length > MAX_MODELS_SHOWN ? ", …" : "";
|
|
@@ -786,7 +790,7 @@ window.__ModuleLoader__.load({
|
|
|
786
790
|
style: MUTED,
|
|
787
791
|
children: [
|
|
788
792
|
"⚠ ",
|
|
789
|
-
t(`error.${props.failure.kind}`),
|
|
793
|
+
t(accountMode && props.failure.kind === "config" ? "accountSignInRequired" : accountMode && props.failure.kind === "auth" ? "accountAuthFailed" : `error.${props.failure.kind}`),
|
|
790
794
|
props.failure.detail === void 0 ? "" : ` — ${props.failure.detail}`
|
|
791
795
|
]
|
|
792
796
|
}),
|
|
@@ -794,6 +798,10 @@ window.__ModuleLoader__.load({
|
|
|
794
798
|
style: MUTED,
|
|
795
799
|
children: modelNames.length === 0 ? t("noModels") : t("modelsPrefix", { list: `${listed}${suffix}` })
|
|
796
800
|
}),
|
|
801
|
+
accountMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
802
|
+
style: MUTED,
|
|
803
|
+
children: t(props.description?.configured ? "accountConnected" : "accountSignInRequired")
|
|
804
|
+
}) : null,
|
|
797
805
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
|
|
798
806
|
style: {
|
|
799
807
|
...MUTED,
|
|
@@ -807,37 +815,39 @@ window.__ModuleLoader__.load({
|
|
|
807
815
|
gap: "8px",
|
|
808
816
|
paddingTop: "8px"
|
|
809
817
|
},
|
|
810
|
-
children: [
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
818
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
819
|
+
style: ROW,
|
|
820
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
821
|
+
style: MUTED,
|
|
822
|
+
children: t("sourceLabel")
|
|
823
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
824
|
+
disabled: props.disabled,
|
|
825
|
+
value: entry?.sourceId ?? "",
|
|
826
|
+
onChange: (event) => {
|
|
827
|
+
const value = event.target.value;
|
|
828
|
+
if (value === "") props.onClearField([
|
|
829
|
+
"providers",
|
|
830
|
+
row.provider,
|
|
831
|
+
"sourceId"
|
|
832
|
+
]);
|
|
833
|
+
else props.onField([
|
|
834
|
+
"providers",
|
|
835
|
+
row.provider,
|
|
836
|
+
"sourceId"
|
|
837
|
+
], value);
|
|
838
|
+
},
|
|
839
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
840
|
+
value: "",
|
|
841
|
+
children: t("sourceAuto")
|
|
842
|
+
}), props.catalog.map((candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
843
|
+
value: candidate.id,
|
|
844
|
+
children: candidate.displayName
|
|
845
|
+
}, candidate.id))]
|
|
846
|
+
})]
|
|
847
|
+
}), accountMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
848
|
+
style: MUTED,
|
|
849
|
+
children: t("accountHint")
|
|
850
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
841
851
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
842
852
|
style: ROW,
|
|
843
853
|
children: [
|
|
@@ -907,7 +917,7 @@ window.__ModuleLoader__.load({
|
|
|
907
917
|
credentials: props.credentials,
|
|
908
918
|
onChanged: props.onCredentialChanged
|
|
909
919
|
})
|
|
910
|
-
]
|
|
920
|
+
] })]
|
|
911
921
|
})] })
|
|
912
922
|
]
|
|
913
923
|
});
|
|
@@ -1551,10 +1561,10 @@ window.__ModuleLoader__.load({
|
|
|
1551
1561
|
display: config.display,
|
|
1552
1562
|
now
|
|
1553
1563
|
}),
|
|
1554
|
-
t,
|
|
1564
|
+
t: status.kind === "ready" && status.mode === "account" ? (key, params) => t(key === "error.config" ? "accountSignInRequired" : key === "error.auth" ? "accountAuthFailed" : key, params) : t,
|
|
1555
1565
|
now,
|
|
1556
1566
|
...sourceLabel === "" ? {} : { sourceLabel },
|
|
1557
|
-
...status.kind === "ready" ? { modeLabel: status.mode === "api" ? t("modeApi") : t("modeCodingPlan") } : {}
|
|
1567
|
+
...status.kind === "ready" ? { modeLabel: status.mode === "account" ? t("modeAccount") : status.mode === "api" ? t("modeApi") : t("modeCodingPlan") } : {}
|
|
1558
1568
|
});
|
|
1559
1569
|
if (fullParts.length === 0) return null;
|
|
1560
1570
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -1577,7 +1587,7 @@ window.__ModuleLoader__.load({
|
|
|
1577
1587
|
const zh = {
|
|
1578
1588
|
nav: "用量状态",
|
|
1579
1589
|
title: "用量状态",
|
|
1580
|
-
intro: "
|
|
1590
|
+
intro: "为每个供应商选择显示账户余额(账号登录或 API Key 模式)或套餐额度(Coding Plan 模式)。数据由宿主定时刷新,密钥交给 DSH 凭据库保存,本插件不另存明文。",
|
|
1581
1591
|
refreshNow: "立即刷新",
|
|
1582
1592
|
refreshing: "刷新中…",
|
|
1583
1593
|
lastChecked: "更新于 {time}",
|
|
@@ -1589,7 +1599,12 @@ window.__ModuleLoader__.load({
|
|
|
1589
1599
|
sectionProvidersHint: "余额与额度是账户级的,所以每个供应商只需设置一次:默认「自动」会识别出该用哪个数据源并直接生效。",
|
|
1590
1600
|
sectionDisplay: "显示",
|
|
1591
1601
|
modeAuto: "自动",
|
|
1592
|
-
modeApi: "API 余额",
|
|
1602
|
+
modeApi: "API Key 余额",
|
|
1603
|
+
modeAccount: "账号余额",
|
|
1604
|
+
accountConnected: "已连接 Harness 登录账号",
|
|
1605
|
+
accountSignInRequired: "请先在 Harness 账号设置中登录",
|
|
1606
|
+
accountAuthFailed: "账号登录已失效,请重新登录",
|
|
1607
|
+
accountHint: "使用 Harness 当前登录账号查询充值与赠送余额,登录由 Harness 管理,无需 API Key。",
|
|
1593
1608
|
modeCodingPlan: "Coding Plan",
|
|
1594
1609
|
modeHidden: "隐藏",
|
|
1595
1610
|
modeUnsupported: "该数据源不支持此模式",
|
|
@@ -1655,7 +1670,7 @@ window.__ModuleLoader__.load({
|
|
|
1655
1670
|
const en = {
|
|
1656
1671
|
nav: "Usage state",
|
|
1657
1672
|
title: "Usage state",
|
|
1658
|
-
intro: "Choose what to show for each provider: account balance (API mode) or coding-plan quota (Coding Plan mode). The host owns refresh timing, and DSH stores credentials — this plugin keeps no separate plaintext copy.",
|
|
1673
|
+
intro: "Choose what to show for each provider: account balance (account sign-in or API Key mode) or coding-plan quota (Coding Plan mode). The host owns refresh timing, and DSH stores credentials — this plugin keeps no separate plaintext copy.",
|
|
1659
1674
|
refreshNow: "Refresh now",
|
|
1660
1675
|
refreshing: "Refreshing…",
|
|
1661
1676
|
lastChecked: "Updated {time}",
|
|
@@ -1667,7 +1682,12 @@ window.__ModuleLoader__.load({
|
|
|
1667
1682
|
sectionProvidersHint: "Balance and quota are account-level, so one setting per provider is enough. “Auto” detects which data source to use and works right away.",
|
|
1668
1683
|
sectionDisplay: "Display",
|
|
1669
1684
|
modeAuto: "Auto",
|
|
1670
|
-
modeApi: "API balance",
|
|
1685
|
+
modeApi: "API Key balance",
|
|
1686
|
+
modeAccount: "Account balance",
|
|
1687
|
+
accountConnected: "Connected to the Harness account",
|
|
1688
|
+
accountSignInRequired: "Sign in through Harness account settings",
|
|
1689
|
+
accountAuthFailed: "Account sign-in expired; sign in again",
|
|
1690
|
+
accountHint: "Reads recharge and bonus wallets for the account signed into Harness. Harness manages sign-in; no API key is needed.",
|
|
1671
1691
|
modeCodingPlan: "Coding plan",
|
|
1672
1692
|
modeHidden: "Hidden",
|
|
1673
1693
|
modeUnsupported: "This data source does not serve that mode",
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,233 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
1
3
|
import { readFileSync } from "node:fs";
|
|
2
4
|
import { homedir } from "node:os";
|
|
3
5
|
import { join } from "node:path";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
6
|
import z from "@deepseek-ai/schemastery";
|
|
7
|
+
//#region src/host/sources/normalize.ts
|
|
8
|
+
/** Shared normalization helpers for provider payloads. */
|
|
9
|
+
/** Numbers and numeric strings to a finite number; anything else is absent. */
|
|
10
|
+
function toFiniteNumber(value) {
|
|
11
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
12
|
+
if (typeof value === "string") {
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
if (trimmed === "") return void 0;
|
|
15
|
+
const parsed = Number(trimmed);
|
|
16
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Clamp to 0..100 and round to one decimal. */
|
|
20
|
+
function clampPercent(value) {
|
|
21
|
+
if (!Number.isFinite(value)) return 0;
|
|
22
|
+
return Math.min(100, Math.max(0, Math.round(value * 10) / 10));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Percentages arrive in two conventions: 0..1 fractions and 0..100 percentages.
|
|
26
|
+
* Values `<= 1` are read as fractions (so a literal "1" means 100%), anything
|
|
27
|
+
* larger is read as a percentage. Invalid or negative input yields `null`.
|
|
28
|
+
*/
|
|
29
|
+
function normalizePercent(value) {
|
|
30
|
+
const parsed = toFiniteNumber(value);
|
|
31
|
+
if (parsed === void 0 || parsed < 0) return null;
|
|
32
|
+
return clampPercent(parsed <= 1 ? parsed * 100 : parsed);
|
|
33
|
+
}
|
|
34
|
+
function fromNumericInstant(value) {
|
|
35
|
+
if (!Number.isFinite(value) || value <= 0) return void 0;
|
|
36
|
+
return value > 0xe8d4a51000 ? Math.round(value) : Math.round(value * 1e3);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reset instants arrive as unix seconds, unix milliseconds, numeric strings or
|
|
40
|
+
* ISO timestamps; all of them become epoch milliseconds. Missing or nonsense
|
|
41
|
+
* values become `undefined` rather than a bogus date.
|
|
42
|
+
*/
|
|
43
|
+
function normalizeResetAt(value) {
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
const trimmed = value.trim();
|
|
46
|
+
if (trimmed === "") return void 0;
|
|
47
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
48
|
+
const numeric = toFiniteNumber(trimmed);
|
|
49
|
+
return numeric === void 0 ? void 0 : fromNumericInstant(numeric);
|
|
50
|
+
}
|
|
51
|
+
const parsed = Date.parse(trimmed);
|
|
52
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
53
|
+
}
|
|
54
|
+
const numeric = toFiniteNumber(value);
|
|
55
|
+
return numeric === void 0 ? void 0 : fromNumericInstant(numeric);
|
|
56
|
+
}
|
|
57
|
+
/** Trim whitespace, trailing slashes and a trailing `/vN` API version segment. */
|
|
58
|
+
function normalizeBaseUrl(value) {
|
|
59
|
+
if (typeof value !== "string") return void 0;
|
|
60
|
+
const trimmed = value.trim().replace(/\/+$/, "");
|
|
61
|
+
if (trimmed === "") return void 0;
|
|
62
|
+
const withoutVersion = trimmed.replace(/\/v\d+$/i, "");
|
|
63
|
+
return withoutVersion === "" ? void 0 : withoutVersion;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/host/sources/types.ts
|
|
67
|
+
var SourceError = class extends Error {
|
|
68
|
+
kind;
|
|
69
|
+
constructor(kind, message) {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = "SourceError";
|
|
72
|
+
this.kind = kind;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/host/sources/deepseek.ts
|
|
77
|
+
const DEFAULT_BASE_URL$1 = "https://api.deepseek.com";
|
|
78
|
+
const BALANCE_PATH = "/user/balance";
|
|
79
|
+
/**
|
|
80
|
+
* DeepSeek returns one entry per currency and the order is not stable, so a
|
|
81
|
+
* fixed "first entry" read makes the displayed balance flip between the real
|
|
82
|
+
* value and zero. Rule: prefer an entry with a positive balance, prefer CNY
|
|
83
|
+
* among equals (the open platform's primary currency), and fall back to CNY or
|
|
84
|
+
* the first entry so the result never depends on response ordering.
|
|
85
|
+
*/
|
|
86
|
+
function pickBalanceInfo(infos) {
|
|
87
|
+
if (!Array.isArray(infos)) return void 0;
|
|
88
|
+
const entries = [];
|
|
89
|
+
for (const raw of infos) {
|
|
90
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
91
|
+
const entry = raw;
|
|
92
|
+
const amount = toFiniteNumber(entry.total_balance);
|
|
93
|
+
if (amount === void 0) continue;
|
|
94
|
+
const granted = toFiniteNumber(entry.granted_balance);
|
|
95
|
+
const toppedUp = toFiniteNumber(entry.topped_up_balance);
|
|
96
|
+
entries.push({
|
|
97
|
+
amount,
|
|
98
|
+
currency: typeof entry.currency === "string" ? entry.currency : "",
|
|
99
|
+
...granted === void 0 ? {} : { granted },
|
|
100
|
+
...toppedUp === void 0 ? {} : { toppedUp }
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (entries.length === 0) return void 0;
|
|
104
|
+
const cnyOf = (list) => list.find((entry) => entry.currency.toUpperCase() === "CNY");
|
|
105
|
+
const funded = entries.filter((entry) => entry.amount > 0);
|
|
106
|
+
return cnyOf(funded) ?? funded[0] ?? cnyOf(entries) ?? entries[0];
|
|
107
|
+
}
|
|
108
|
+
/** DeepSeek balances: API keys and Harness account grants are separate identities. */
|
|
109
|
+
const deepseek = {
|
|
110
|
+
id: "deepseek",
|
|
111
|
+
displayName: "DeepSeek",
|
|
112
|
+
modes: ["api", "account"],
|
|
113
|
+
credentialRefs: (mode) => mode === "account" ? [] : ["DEEPSEEK_API_KEY"],
|
|
114
|
+
defaultBaseUrl: (mode) => mode === "account" ? "https://platform.deepseek.com" : DEFAULT_BASE_URL$1,
|
|
115
|
+
request(input) {
|
|
116
|
+
if (input.mode === "account") throw new SourceError("config", "Account balance requires the Harness account service");
|
|
117
|
+
return {
|
|
118
|
+
url: `${normalizeBaseUrl(input.baseUrl) ?? DEFAULT_BASE_URL$1}${BALANCE_PATH}`,
|
|
119
|
+
headers: {
|
|
120
|
+
authorization: `Bearer ${input.apiKey}`,
|
|
121
|
+
accept: "application/json"
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
parse(payload, mode) {
|
|
126
|
+
if (mode === "account") return parseAccountBalance(payload);
|
|
127
|
+
const root = payload !== null && typeof payload === "object" ? payload : void 0;
|
|
128
|
+
const picked = root === void 0 ? void 0 : pickBalanceInfo(root.balance_infos);
|
|
129
|
+
if (picked === void 0) throw new SourceError("parse", "DeepSeek balance response contains no usable balance_infos");
|
|
130
|
+
return {
|
|
131
|
+
balances: [{
|
|
132
|
+
amount: picked.amount,
|
|
133
|
+
currency: picked.currency,
|
|
134
|
+
...picked.granted === void 0 ? {} : { granted: picked.granted },
|
|
135
|
+
...picked.toppedUp === void 0 ? {} : { toppedUp: picked.toppedUp }
|
|
136
|
+
}],
|
|
137
|
+
windows: []
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
/** Parse the account service's sanitized normal and bonus wallets (major units). */
|
|
142
|
+
function parseAccountBalance(payload) {
|
|
143
|
+
const root = payload !== null && typeof payload === "object" ? payload : void 0;
|
|
144
|
+
if (root?.status === "failed") throw new SourceError("http", "Harness account balance is temporarily unavailable");
|
|
145
|
+
if (root?.status !== "ready" || !Array.isArray(root.value) || !Array.isArray(root.bonusWallets)) throw new SourceError("parse", "Invalid Harness account balance response");
|
|
146
|
+
const wallets = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const [rows, field] of [[root.value, "toppedUp"], [root.bonusWallets, "granted"]]) for (const raw of rows) {
|
|
148
|
+
const row = raw !== null && typeof raw === "object" ? raw : void 0;
|
|
149
|
+
const amount = toFiniteNumber(row?.balance);
|
|
150
|
+
const currency = row?.currency;
|
|
151
|
+
if (amount === void 0 || currency !== "CNY" && currency !== "USD") throw new SourceError("parse", "Invalid Harness account wallet");
|
|
152
|
+
const wallet = wallets.get(currency) ?? {
|
|
153
|
+
toppedUp: 0,
|
|
154
|
+
granted: 0
|
|
155
|
+
};
|
|
156
|
+
wallet[field] += amount;
|
|
157
|
+
if (!Number.isFinite(wallet[field])) throw new SourceError("parse", "Invalid Harness account wallet total");
|
|
158
|
+
wallets.set(currency, wallet);
|
|
159
|
+
}
|
|
160
|
+
const picked = pickBalanceInfo([...wallets].map(([currency, wallet]) => ({
|
|
161
|
+
currency,
|
|
162
|
+
total_balance: wallet.toppedUp + wallet.granted,
|
|
163
|
+
granted_balance: wallet.granted,
|
|
164
|
+
topped_up_balance: wallet.toppedUp
|
|
165
|
+
})));
|
|
166
|
+
if (picked === void 0) throw new SourceError("parse", "Harness account has no usable wallets");
|
|
167
|
+
return {
|
|
168
|
+
balances: [picked],
|
|
169
|
+
windows: []
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/host/deepseek-account.ts
|
|
174
|
+
function clientMetadata() {
|
|
175
|
+
let version = "";
|
|
176
|
+
try {
|
|
177
|
+
version = createRequire(import.meta.url)("@deepseek-ai/dsh-llm/package.json").version;
|
|
178
|
+
} catch {}
|
|
179
|
+
return {
|
|
180
|
+
version,
|
|
181
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
182
|
+
timezoneOffsetSeconds: -(/* @__PURE__ */ new Date()).getTimezoneOffset() * 60
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** Do not export the grant or its digest across the plugin RPC boundary. */
|
|
186
|
+
async function resolveAccountCredential(account) {
|
|
187
|
+
if (typeof account?.getPlatformSession !== "function" || typeof account.getBalance !== "function") return void 0;
|
|
188
|
+
try {
|
|
189
|
+
const session = await account.getPlatformSession();
|
|
190
|
+
if (session === null || typeof session.token !== "string" || session.token.trim() === "") return void 0;
|
|
191
|
+
return {
|
|
192
|
+
apiKey: "",
|
|
193
|
+
ref: "deepseek-account",
|
|
194
|
+
origin: "account",
|
|
195
|
+
identity: createHash("sha256").update(JSON.stringify([session.origin, session.token])).digest("hex")
|
|
196
|
+
};
|
|
197
|
+
} catch {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async function describeAccount(account) {
|
|
202
|
+
return {
|
|
203
|
+
candidates: [],
|
|
204
|
+
configured: await resolveAccountCredential(account) !== void 0,
|
|
205
|
+
source: "account",
|
|
206
|
+
writable: false
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/** The host service owns authorization, destination validation, timeouts and grant expiry. */
|
|
210
|
+
async function readAccountBalance(account, credential, invalidate) {
|
|
211
|
+
const matches = async () => (await resolveAccountCredential(account))?.identity === credential.identity;
|
|
212
|
+
if (account === void 0 || !await matches()) {
|
|
213
|
+
invalidate();
|
|
214
|
+
throw new SourceError("config", "DeepSeek account changed; sign in through Harness account settings");
|
|
215
|
+
}
|
|
216
|
+
let payload;
|
|
217
|
+
try {
|
|
218
|
+
payload = await account.getBalance(clientMetadata());
|
|
219
|
+
} catch {
|
|
220
|
+
if (!await matches()) invalidate();
|
|
221
|
+
throw new SourceError("http", "Harness account balance is temporarily unavailable");
|
|
222
|
+
}
|
|
223
|
+
if (!await matches()) {
|
|
224
|
+
invalidate();
|
|
225
|
+
throw new SourceError("config", "DeepSeek account changed; sign in through Harness account settings");
|
|
226
|
+
}
|
|
227
|
+
if (payload === null) throw new SourceError("auth", "DeepSeek account balance requires a valid Harness login");
|
|
228
|
+
return parseAccountBalance(payload);
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
6
231
|
//#region src/shared/config.ts
|
|
7
232
|
const DEFAULT_CONFIG = {
|
|
8
233
|
models: [],
|
|
@@ -22,11 +247,13 @@ const DEFAULT_CONFIG = {
|
|
|
22
247
|
};
|
|
23
248
|
const MODES = [
|
|
24
249
|
"api",
|
|
250
|
+
"account",
|
|
25
251
|
"coding-plan",
|
|
26
252
|
"hidden"
|
|
27
253
|
];
|
|
28
254
|
const PROVIDER_MODES = [
|
|
29
255
|
"api",
|
|
256
|
+
"account",
|
|
30
257
|
"coding-plan",
|
|
31
258
|
"hidden",
|
|
32
259
|
"auto"
|
|
@@ -295,7 +522,7 @@ function resolveProvider(input) {
|
|
|
295
522
|
mode: null,
|
|
296
523
|
reason: "unknown-source"
|
|
297
524
|
};
|
|
298
|
-
if (entry?.mode === "api" || entry?.mode === "coding-plan") {
|
|
525
|
+
if (entry?.mode === "api" || entry?.mode === "account" || entry?.mode === "coding-plan") {
|
|
299
526
|
if (!source.modes.includes(entry.mode)) return {
|
|
300
527
|
...base,
|
|
301
528
|
sourceId,
|
|
@@ -315,7 +542,7 @@ function resolveProvider(input) {
|
|
|
315
542
|
mode: null,
|
|
316
543
|
reason: "needs-endpoint"
|
|
317
544
|
};
|
|
318
|
-
const mode = sourceId === "kimi" ? kimiMode(input.provider, baseUrl) : source.modes[0];
|
|
545
|
+
const mode = sourceId === "deepseek" && input.provider === "deepseek-account" ? "account" : sourceId === "kimi" ? kimiMode(input.provider, baseUrl) : source.modes[0];
|
|
319
546
|
if (mode === void 0 || !source.modes.includes(mode)) return {
|
|
320
547
|
...base,
|
|
321
548
|
sourceId,
|
|
@@ -330,139 +557,6 @@ function resolveProvider(input) {
|
|
|
330
557
|
});
|
|
331
558
|
}
|
|
332
559
|
//#endregion
|
|
333
|
-
//#region src/host/sources/normalize.ts
|
|
334
|
-
/** Shared normalization helpers for provider payloads. */
|
|
335
|
-
/** Numbers and numeric strings to a finite number; anything else is absent. */
|
|
336
|
-
function toFiniteNumber(value) {
|
|
337
|
-
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
338
|
-
if (typeof value === "string") {
|
|
339
|
-
const trimmed = value.trim();
|
|
340
|
-
if (trimmed === "") return void 0;
|
|
341
|
-
const parsed = Number(trimmed);
|
|
342
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
/** Clamp to 0..100 and round to one decimal. */
|
|
346
|
-
function clampPercent(value) {
|
|
347
|
-
if (!Number.isFinite(value)) return 0;
|
|
348
|
-
return Math.min(100, Math.max(0, Math.round(value * 10) / 10));
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Percentages arrive in two conventions: 0..1 fractions and 0..100 percentages.
|
|
352
|
-
* Values `<= 1` are read as fractions (so a literal "1" means 100%), anything
|
|
353
|
-
* larger is read as a percentage. Invalid or negative input yields `null`.
|
|
354
|
-
*/
|
|
355
|
-
function normalizePercent(value) {
|
|
356
|
-
const parsed = toFiniteNumber(value);
|
|
357
|
-
if (parsed === void 0 || parsed < 0) return null;
|
|
358
|
-
return clampPercent(parsed <= 1 ? parsed * 100 : parsed);
|
|
359
|
-
}
|
|
360
|
-
function fromNumericInstant(value) {
|
|
361
|
-
if (!Number.isFinite(value) || value <= 0) return void 0;
|
|
362
|
-
return value > 0xe8d4a51000 ? Math.round(value) : Math.round(value * 1e3);
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Reset instants arrive as unix seconds, unix milliseconds, numeric strings or
|
|
366
|
-
* ISO timestamps; all of them become epoch milliseconds. Missing or nonsense
|
|
367
|
-
* values become `undefined` rather than a bogus date.
|
|
368
|
-
*/
|
|
369
|
-
function normalizeResetAt(value) {
|
|
370
|
-
if (typeof value === "string") {
|
|
371
|
-
const trimmed = value.trim();
|
|
372
|
-
if (trimmed === "") return void 0;
|
|
373
|
-
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
374
|
-
const numeric = toFiniteNumber(trimmed);
|
|
375
|
-
return numeric === void 0 ? void 0 : fromNumericInstant(numeric);
|
|
376
|
-
}
|
|
377
|
-
const parsed = Date.parse(trimmed);
|
|
378
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
379
|
-
}
|
|
380
|
-
const numeric = toFiniteNumber(value);
|
|
381
|
-
return numeric === void 0 ? void 0 : fromNumericInstant(numeric);
|
|
382
|
-
}
|
|
383
|
-
/** Trim whitespace, trailing slashes and a trailing `/vN` API version segment. */
|
|
384
|
-
function normalizeBaseUrl(value) {
|
|
385
|
-
if (typeof value !== "string") return void 0;
|
|
386
|
-
const trimmed = value.trim().replace(/\/+$/, "");
|
|
387
|
-
if (trimmed === "") return void 0;
|
|
388
|
-
const withoutVersion = trimmed.replace(/\/v\d+$/i, "");
|
|
389
|
-
return withoutVersion === "" ? void 0 : withoutVersion;
|
|
390
|
-
}
|
|
391
|
-
//#endregion
|
|
392
|
-
//#region src/host/sources/types.ts
|
|
393
|
-
var SourceError = class extends Error {
|
|
394
|
-
kind;
|
|
395
|
-
constructor(kind, message) {
|
|
396
|
-
super(message);
|
|
397
|
-
this.name = "SourceError";
|
|
398
|
-
this.kind = kind;
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
//#endregion
|
|
402
|
-
//#region src/host/sources/deepseek.ts
|
|
403
|
-
const DEFAULT_BASE_URL$1 = "https://api.deepseek.com";
|
|
404
|
-
const BALANCE_PATH = "/user/balance";
|
|
405
|
-
/**
|
|
406
|
-
* DeepSeek returns one entry per currency and the order is not stable, so a
|
|
407
|
-
* fixed "first entry" read makes the displayed balance flip between the real
|
|
408
|
-
* value and zero. Rule: prefer an entry with a positive balance, prefer CNY
|
|
409
|
-
* among equals (the open platform's primary currency), and fall back to CNY or
|
|
410
|
-
* the first entry so the result never depends on response ordering.
|
|
411
|
-
*/
|
|
412
|
-
function pickBalanceInfo(infos) {
|
|
413
|
-
if (!Array.isArray(infos)) return void 0;
|
|
414
|
-
const entries = [];
|
|
415
|
-
for (const raw of infos) {
|
|
416
|
-
if (raw === null || typeof raw !== "object") continue;
|
|
417
|
-
const entry = raw;
|
|
418
|
-
const amount = toFiniteNumber(entry.total_balance);
|
|
419
|
-
if (amount === void 0) continue;
|
|
420
|
-
const granted = toFiniteNumber(entry.granted_balance);
|
|
421
|
-
const toppedUp = toFiniteNumber(entry.topped_up_balance);
|
|
422
|
-
entries.push({
|
|
423
|
-
amount,
|
|
424
|
-
currency: typeof entry.currency === "string" ? entry.currency : "",
|
|
425
|
-
...granted === void 0 ? {} : { granted },
|
|
426
|
-
...toppedUp === void 0 ? {} : { toppedUp }
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
if (entries.length === 0) return void 0;
|
|
430
|
-
const cnyOf = (list) => list.find((entry) => entry.currency.toUpperCase() === "CNY");
|
|
431
|
-
const funded = entries.filter((entry) => entry.amount > 0);
|
|
432
|
-
return cnyOf(funded) ?? funded[0] ?? cnyOf(entries) ?? entries[0];
|
|
433
|
-
}
|
|
434
|
-
/** DeepSeek official API: balance only — the platform has no coding plan. */
|
|
435
|
-
const deepseek = {
|
|
436
|
-
id: "deepseek",
|
|
437
|
-
displayName: "DeepSeek",
|
|
438
|
-
modes: ["api"],
|
|
439
|
-
credentialRefs: () => ["DEEPSEEK_API_KEY"],
|
|
440
|
-
defaultBaseUrl: () => DEFAULT_BASE_URL$1,
|
|
441
|
-
request(input) {
|
|
442
|
-
return {
|
|
443
|
-
url: `${normalizeBaseUrl(input.baseUrl) ?? DEFAULT_BASE_URL$1}${BALANCE_PATH}`,
|
|
444
|
-
headers: {
|
|
445
|
-
authorization: `Bearer ${input.apiKey}`,
|
|
446
|
-
accept: "application/json"
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
},
|
|
450
|
-
parse(payload, _mode) {
|
|
451
|
-
const root = payload !== null && typeof payload === "object" ? payload : void 0;
|
|
452
|
-
const picked = root === void 0 ? void 0 : pickBalanceInfo(root.balance_infos);
|
|
453
|
-
if (picked === void 0) throw new SourceError("parse", "DeepSeek balance response contains no usable balance_infos");
|
|
454
|
-
return {
|
|
455
|
-
balances: [{
|
|
456
|
-
amount: picked.amount,
|
|
457
|
-
currency: picked.currency,
|
|
458
|
-
...picked.granted === void 0 ? {} : { granted: picked.granted },
|
|
459
|
-
...picked.toppedUp === void 0 ? {} : { toppedUp: picked.toppedUp }
|
|
460
|
-
}],
|
|
461
|
-
windows: []
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
//#endregion
|
|
466
560
|
//#region src/host/sources/kimi.ts
|
|
467
561
|
const MOONSHOT_BASE_URL = "https://api.moonshot.cn";
|
|
468
562
|
const KIMI_CODE_BASE_URL = "https://api.kimi.com";
|
|
@@ -1252,6 +1346,7 @@ function asRecord(value) {
|
|
|
1252
1346
|
* extra configuration in this plugin.
|
|
1253
1347
|
*/
|
|
1254
1348
|
function providerCredentialRefs(input) {
|
|
1349
|
+
if (input.mode === "account") return [];
|
|
1255
1350
|
const suggest = input.suggest ?? suggestSourceId;
|
|
1256
1351
|
const refs = [];
|
|
1257
1352
|
const push = (value) => {
|
|
@@ -1414,6 +1509,10 @@ var UsageStateStore = class {
|
|
|
1414
1509
|
constructor(deps) {
|
|
1415
1510
|
this.deps = deps;
|
|
1416
1511
|
}
|
|
1512
|
+
/** Clear an account grant immediately when the host signs out. */
|
|
1513
|
+
clear(key) {
|
|
1514
|
+
this.invalidate(key);
|
|
1515
|
+
}
|
|
1417
1516
|
invalidate(key) {
|
|
1418
1517
|
this.generations.set(key, (this.generations.get(key) ?? 0) + 1);
|
|
1419
1518
|
this.identities.delete(key);
|
|
@@ -1462,7 +1561,7 @@ var UsageStateStore = class {
|
|
|
1462
1561
|
async refresh(key, options = {}) {
|
|
1463
1562
|
if (this.stopped) return {
|
|
1464
1563
|
sourceId: key.split(":")[0] ?? "",
|
|
1465
|
-
mode: key.endsWith(":coding-plan") ? "coding-plan" : "api",
|
|
1564
|
+
mode: key.endsWith(":account") ? "account" : key.endsWith(":coding-plan") ? "coding-plan" : "api",
|
|
1466
1565
|
balances: [],
|
|
1467
1566
|
windows: [],
|
|
1468
1567
|
fetchedAt: this.deps.clock.now(),
|
|
@@ -1474,7 +1573,7 @@ var UsageStateStore = class {
|
|
|
1474
1573
|
const target = this.currentTargets().find((candidate) => candidate.key === key);
|
|
1475
1574
|
if (target === void 0) return {
|
|
1476
1575
|
sourceId: key.split(":")[0] ?? "",
|
|
1477
|
-
mode: key.endsWith(":coding-plan") ? "coding-plan" : "api",
|
|
1576
|
+
mode: key.endsWith(":account") ? "account" : key.endsWith(":coding-plan") ? "coding-plan" : "api",
|
|
1478
1577
|
balances: [],
|
|
1479
1578
|
windows: [],
|
|
1480
1579
|
fetchedAt: this.deps.clock.now(),
|
|
@@ -1506,7 +1605,8 @@ var UsageStateStore = class {
|
|
|
1506
1605
|
credential?.baseUrl ?? target.baseUrl ?? source.defaultBaseUrl(target.mode),
|
|
1507
1606
|
credential?.baseUrlPinned === true || target.baseUrlPinned === true,
|
|
1508
1607
|
credential?.ref ?? target.apiKeyRef,
|
|
1509
|
-
credential?.apiKey
|
|
1608
|
+
credential?.apiKey,
|
|
1609
|
+
credential?.identity
|
|
1510
1610
|
])).digest("hex");
|
|
1511
1611
|
if ((this.generations.get(key) ?? 0) !== generationAtLookup && this.identities.get(key) !== identity) return this.refresh(key, options);
|
|
1512
1612
|
if (this.identities.get(key) !== identity) {
|
|
@@ -1518,7 +1618,7 @@ var UsageStateStore = class {
|
|
|
1518
1618
|
}
|
|
1519
1619
|
if (credential === void 0) return this.fail(target, {
|
|
1520
1620
|
kind: "config",
|
|
1521
|
-
detail: "no credential configured"
|
|
1621
|
+
detail: target.mode === "account" ? "Sign in through Harness account settings" : "no credential configured"
|
|
1522
1622
|
}, this.readings.get(key));
|
|
1523
1623
|
const cached = this.readings.get(key);
|
|
1524
1624
|
const lastSuccess = this.succeededAt.get(key);
|
|
@@ -1903,7 +2003,7 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1903
2003
|
sourceId: target.sourceId,
|
|
1904
2004
|
mode: target.mode,
|
|
1905
2005
|
config,
|
|
1906
|
-
deepseekSettings: readNamespace(ctx, "llm-deepseek"),
|
|
2006
|
+
deepseekSettings: readNamespace(ctx, "llm-deepseek") ?? readNamespace(ctx, "llm-deepseek-api-key"),
|
|
1907
2007
|
piAiSettings: readNamespace(ctx, "llm-pi-ai")
|
|
1908
2008
|
})
|
|
1909
2009
|
}));
|
|
@@ -1915,6 +2015,8 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1915
2015
|
preferredRefs: target.preferredRefs ?? []
|
|
1916
2016
|
};
|
|
1917
2017
|
};
|
|
2018
|
+
const account = () => ctx.get("deepseekAccount");
|
|
2019
|
+
const httpReader = createTargetReader({ fetch: fetchImpl });
|
|
1918
2020
|
const store = new UsageStateStore({
|
|
1919
2021
|
clock: {
|
|
1920
2022
|
now,
|
|
@@ -1925,6 +2027,7 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1925
2027
|
targets,
|
|
1926
2028
|
findSource,
|
|
1927
2029
|
credentials: { resolve: async (target) => {
|
|
2030
|
+
if (target.sourceId === "deepseek" && target.mode === "account") return resolveAccountCredential(account());
|
|
1928
2031
|
const source = findSource(target.sourceId);
|
|
1929
2032
|
if (source === void 0) return void 0;
|
|
1930
2033
|
const resolved = await resolveApiKey(source, target.mode, optionsFor(target), lookup);
|
|
@@ -1936,9 +2039,10 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1936
2039
|
...target.baseUrlPinned === true ? { baseUrlPinned: true } : {}
|
|
1937
2040
|
};
|
|
1938
2041
|
} },
|
|
1939
|
-
read:
|
|
2042
|
+
read: (target, credential, source) => target.sourceId === "deepseek" && target.mode === "account" ? readAccountBalance(account(), credential, () => store.clear(target.key)) : httpReader(target, credential, source)
|
|
1940
2043
|
});
|
|
1941
2044
|
reconfigure = () => store.reconfigure();
|
|
2045
|
+
ctx.on("deepseek-account/signed-out", () => store.clear("deepseek:account"));
|
|
1942
2046
|
const service = new UsageStateService({
|
|
1943
2047
|
store,
|
|
1944
2048
|
targets,
|
|
@@ -1948,6 +2052,7 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1948
2052
|
return origin === void 0 ? [] : [[provider, origin]];
|
|
1949
2053
|
})),
|
|
1950
2054
|
describe: async (target) => {
|
|
2055
|
+
if (target.sourceId === "deepseek" && target.mode === "account") return describeAccount(account());
|
|
1951
2056
|
const source = findSource(target.sourceId);
|
|
1952
2057
|
if (source === void 0) return {
|
|
1953
2058
|
candidates: [],
|
package/lib/typert.js
CHANGED
|
@@ -24,7 +24,11 @@ function strictCodec(name, schema) {
|
|
|
24
24
|
create: () => schema
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
const usageModeSchema = z.enum([
|
|
27
|
+
const usageModeSchema = z.enum([
|
|
28
|
+
"api",
|
|
29
|
+
"account",
|
|
30
|
+
"coding-plan"
|
|
31
|
+
]);
|
|
28
32
|
const balanceSchema = z.object({
|
|
29
33
|
amount: z.number(),
|
|
30
34
|
currency: z.string(),
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-usage-state",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"packageManager": "npm@11.19.1",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "DSH plugin: show account
|
|
6
|
+
"description": "DSH plugin: show account sign-in or API key balances and coding-plan quotas for the model in use",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=20",
|