feat: 为第三方模型供应商增加受控的余额与配额查询能力
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 395
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
### 使用场景 / Use case
我在 Cindy 中通过快速接入预设配置 DeepSeek、OpenRouter 等第三方模型供应商后,希望不用离开应用,就能判断:
- 当前还有多少余额或密钥配额;
- 今天、这周或本月已经使用了多少;
- 是否需要充值、换 key 或切换供应商;
- 数据最后一次成功更新是什么时候,当前显示的是否只是旧快照。
这和“Cindy 本地统计了多少 Token / 估算了多少费用”是两类信息。本 Issue 关注供应商接口返回的**权威余额或配额状态**;本地历史用量与费用估算继续由 #774、#2618 等议题承接。
### 当前问题 / Current limitation
Cindy 已经能展示部分内置账号体系的用量信息,例如 Claude、Codex、xAI 和 Cindy AI;但通过快速接入预设创建的第三方供应商,目前只有推理路由和模型配置,没有主动余额/配额查询能力,用户仍需逐一打开供应商控制台。
直接给现有自定义供应商加一个通用余额接口也不可行:
1. **创建后无法识别原始预设。** `ProviderPreset` 只是 UI 模板,创建时会快照为新的 `CustomProviderConfig`,随后与 preset 脱钩;名称和 Provider ID 还可改变,不能靠它们或 base URL 猜测集成身份。
2. **凭证是 per-runtime 的。** 同一个自定义供应商可以给 Claude Code、Codex、Pi 配置不同端点、协议和 API key;只有 `providerId` 无法决定查询哪个账号。
3. **余额、配额和用量不是一种数据。** 不同供应商会返回多币种余额、密钥级限额、窗口配额或产品分项,不能强压成一个会丢字段的通用原始快照。
4. **查询跨越凭证边界。** Renderer 不能决定请求 URL、headers,或把 Main 变成携带已存密钥的任意网络代理。
### 期望方案 / Proposed solution
建议先建立一套小而受控的“供应商余额与配额集成”能力,再逐家接入。
#### 1. 保存稳定的集成身份
在每个 runtime 的自定义供应商配置中保存一个非敏感、版本化的能力标记,例如:
```ts
accountUsage: {
integrationId: 'deepseek-balance-v1'
}
```
- `integrationId` 是 Cindy 内置注册表的稳定 ID,不等于 preset ID;
- 新版本通过官方预设创建连接时写入;
- 已存在但没有标记的连接保持现状,不按名称或 URL 静默猜测;
- 后续可提供一次显式确认入口,让用户绑定存量连接;
- 修改相关 runtime 的端点、鉴权方式或凭证后,旧快照立即失效。
#### 2. 第一阶段只接入官方契约已确认的能力
| 供应商 | 第一阶段数据源 | 展示内容 |
|---|---|---|
| DeepSeek | [`GET https://api.deepseek.com/user/balance`](https://api-docs.deepseek.com/api/get-user-balance/) | 展示 `balance_infos[]` 中全部币种,以及总余额、赠送余额、充值余额;该接口不提供今日/月用量,不从其它数据推算 |
| OpenRouter | [`GET https://openrouter.ai/api/v1/key`](https://openrouter.ai/docs/api_reference/limits) | 展示当前推理 key 的 limit、limit remaining、reset,以及日/周/月 usage;`limit_remaining = null` 时显示“未设置密钥配额”,不冒充账户余额 |
OpenRouter 的账户级 `/api/v1/credits` 需要 management key,不在第一阶段要求用户新增第二类高权限凭证。
Kimi Code 官方资料确认有 5 小时和周配额,但目前没有公开稳定的查询 API 契约,因此先保留控制台外链,不在第一阶段承诺原生查询。
GLM Coding Plan 已有独立提案 #2720 和实现讨论 #2768/#2773,本 Issue 不重复定义其未文档化接口;它未来可以复用这里的集成身份、缓存和展示基础设施。
#### 3. Main 进程负责查询与安全裁决
- 设置页按经过验证的 `(providerId, agentKind)` 查询;未来会话摘要按 `sessionId` 查询,由 Main 推导实际 Provider、runtime、模型和执行位置;
- Renderer 只能选择已存在的供应商/runtime,不能提供 endpoint、path、headers 或 integration ID;
- Main 从自己的配置和内置注册表解析固定 HTTPS 端点,并读取对应 runtime 的凭证;
- 每个集成只允许发送明确白名单中的鉴权头,不转发任意自定义 headers;
- 使用现有代理感知出网通道,并限制超时、响应体大小、重定向和可返回的错误信息;
- API key、完整鉴权头、敏感响应和凭证指纹不进入 Renderer、日志或持久快照。
第一阶段不提供用户自定义余额 fetcher、自定义 quota URL 或自定义查询 headers。
#### 4. 保留供应商原始快照,统一展示投影
每个集成保留自己的原始、可验证数据模型,再转换为供 UI 使用的只读展示投影,例如余额行、已用百分比窗口、重置时间和数据状态。
百分比统一以 `usedPercent` 作为领域口径,剩余百分比只在展示时派生,避免与现有 `QuotaBar` 的填充和告警方向相反。`unsupported / 未绑定集成` 属于能力状态,不伪装成成功快照。
#### 5. Main 集中调度缓存与刷新
- cached-first,同一身份 singleflight,每个集成独立 TTL;
- 缓存身份至少包含数据 owner、Provider、runtime、integration 版本、凭证指纹、配置修订和执行设备;
- 429 尊重 `Retry-After` 并指数退避,手动刷新同样节流;
- 网络错误可保留同一身份的最后有效快照,并明确标记更新时间/陈旧状态;
- 换账号、换 key、编辑/删除供应商或改变集成身份时同步失效;
- 旧凭证请求迟到返回时不得重新写入或广播,需用 generation/当前身份复核防止旧数据复活。
#### 6. 分阶段展示
**第一阶段:设置 → 模型供应商**
复用供应商详情头部现有的资产模块槽位,只在当前选中的已连接供应商中展示:
- DeepSeek 的多币种余额与充值/赠送拆分;
- OpenRouter 当前 key 的配额、剩余量和日/周/月用量;
- 更新中、更新时间、陈旧、鉴权失败、请求失败和手动刷新状态;
- 可用时保留“打开供应商用量页面”外链。
不支持或未绑定集成的供应商不增加噪音占位。
**后续:当前任务摘要**
数据链路稳定后,再让 `TodaySpendChip` 消费同一展示投影:
- 最多显示两个由集成声明的稳定主指标,不动态选择“剩余最少窗口”造成摘要跳变;
- 浮层展示完整余额/配额明细;
- 即使没有外部看板,触发器也必须是键盘可达的真实按钮,并支持 Esc、焦点进入和焦点归还;
- 错误与告警不能只依赖颜色。
现有 Claude、Codex、xAI、Cindy AI 的权威原始快照暂不迁移;后续只按需增加展示适配器,避免一次性改动现有复杂计费路径。
#### 7. 本地与远程边界
第一阶段只展示本机配置和本机执行任务的第三方余额/配额。
SSH、device-link 和 Mobile 场景中,配额事实属于实际执行设备及其凭证;在没有远程只读协议前,控制端不得回退展示自己的本机余额。是否开放远程查询及相应 allowlist,作为独立后续议题处理。
### 建议验收标准
- [ ] 新建的 DeepSeek/OpenRouter 官方预设连接保存稳定、非敏感的 per-runtime 集成身份
- [ ] 存量未标记连接不会被名称、Provider ID 或 URL 自动识别
- [ ] DeepSeek 正确展示多个 `balance_infos` 币种,不虚构今日/月用量
- [ ] OpenRouter 使用普通推理 key 查询 `/api/v1/key`,不要求或误用 management key
- [ ] Main 不接受 Renderer 提供的 URL、path、headers 或 integration ID
- [ ] 不同 Provider、runtime、账号、key 和执行设备之间不会串快照
- [ ] 修改/删除 Provider 或换 key 后,旧请求迟到也不会让旧快照复活
- [ ] 401/403、429、5xx、超时、字段缺失和 schema 变化都有 fail-safe 降级
- [ ] 设置页覆盖 Light/Dark、键盘访问、五语言 i18n 和非颜色告警
- [ ] SSH/device-link 任务不会显示控制端本机供应商余额
- [ ] Main 服务、解析器、缓存竞态、IPC sender/payload 校验和设置页状态均有对应测试
### 已考虑的替代方案 / Alternatives considered
- **只跳转供应商控制台**:安全且可作为不支持供应商的降级,但无法在选择/切换供应商时快速判断可用状态。
- **按 preset ID、名称或 base URL 自动识别**:创建后的 Provider 已与 preset 脱钩,名称和 URL 可编辑,也可能是中转或兼容端点;容易误绑账号,因此不采用。
- **提供任意自定义余额 URL/headers**:会显著扩大凭证外发和 SSRF 风险,第一阶段不开放。
- **把全部供应商压成一个通用原始 `ProviderQuotaSnapshot`**:会丢失 Claude 分模型窗口、Codex 多 bucket、xAI 产品分项、Cindy AI 多池账本等语义;改为“供应商原始快照 + 通用展示投影”。
- **首期同时接入 Kimi、GLM、现有四家和多端**:公开契约和回归面都不足,先用两家官方稳定 API 验证基础能力。
### 相关 Issue
- #119:Claude、Codex、xAI、Cindy AI 的跨供应商统一视图
- #774:自定义供应商的本地 Token、缓存和费用估算
- #1261:结构化用量卡片和 `QuotaBar` 交互先例
- #2618:历史用量入口与 Cindy 外部 CLI 用量
- #2720、#2768、#2773:GLM Coding Plan 专项用量能力
- #2633:SuperGrok 订阅用量专项
Contributor guide
Research direction
Start by tracing the existing ProviderPreset and CustomProviderConfig flow across Main, Renderer, and IPC, then inspect the provider details asset slot, QuotaBar, and TodaySpendChip. Review the existing configuration, credential, proxy, cache, and test entry points before designing the controlled integrations. Done requires the listed DeepSeek and OpenRouter behaviors, safe cache invalidation, validated IPC boundaries, and corresponding service, parser, race, accessibility, i18n, and UI tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- electron, typescript
- Domain
- api, full-stack, security, testing-qa
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100