Skip to content

Commit d68cd13

Browse files
committed
feat: add codex api key quota curl
1 parent 7ff0085 commit d68cd13

35 files changed

Lines changed: 1428 additions & 39 deletions

.agents/skills/gettokens-ops-governance/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This skill unifies the procedural rules for working on GetTokens, ensuring consi
1111
- **Restart**: Always restart the app if Go files, Wails bindings, or sidecar logic change. Restart for Svelte/CSS if HMR shows stale results.
1212
- **Readiness**: Sidecar `ready` status is required for account data flow. UI mount success does not guarantee data flow.
1313
- **Verification**: Only claim a fix is live after verifying it in the actual desktop app window, not just the browser.
14+
- **Binding Boundary**: Wails binds the root `main.App`, not `internal/wailsapp.App`. Any new Wails-facing method or DTO added under `internal/wailsapp` must also be exposed through root-level `app.go`, `app_types.go`, and mappers as needed before regenerating bindings; otherwise `wails dev` will remove the frontend export.
1415

1516
### 1.1 Browser Preview & Screenshot Loop
1617
- **When to use**:

app.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,34 @@ func (a *App) GetCodexQuota(name string) (*CodexQuotaResponse, error) {
358358
}, nil
359359
}
360360

361+
func (a *App) TestCodexAPIKeyQuotaCurl(input TestCodexAPIKeyQuotaCurlInput) (*CodexQuotaResponse, error) {
362+
result, err := a.core.TestCodexAPIKeyQuotaCurl(wailsapp.TestCodexAPIKeyQuotaCurlInput{
363+
APIKey: input.APIKey,
364+
BaseURL: input.BaseURL,
365+
Prefix: input.Prefix,
366+
QuotaCurl: input.QuotaCurl,
367+
})
368+
if err != nil {
369+
return nil, err
370+
}
371+
372+
windows := make([]CodexQuotaWindow, 0, len(result.Windows))
373+
for _, window := range result.Windows {
374+
windows = append(windows, CodexQuotaWindow{
375+
ID: window.ID,
376+
Label: window.Label,
377+
RemainingPercent: window.RemainingPercent,
378+
ResetLabel: window.ResetLabel,
379+
ResetAtUnix: window.ResetAtUnix,
380+
})
381+
}
382+
383+
return &CodexQuotaResponse{
384+
PlanType: result.PlanType,
385+
Windows: windows,
386+
}, nil
387+
}
388+
361389
func (a *App) ListAccounts() ([]AccountRecord, error) {
362390
result, err := a.core.ListAccounts()
363391
if err != nil {
@@ -538,6 +566,8 @@ func (a *App) CreateCodexAPIKey(input CreateCodexAPIKeyInput) error {
538566
ProxyURL: input.ProxyURL,
539567
Headers: input.Headers,
540568
ExcludedModels: input.ExcludedModels,
569+
QuotaCurl: input.QuotaCurl,
570+
QuotaEnabled: input.QuotaEnabled,
541571
})
542572
}
543573

@@ -550,10 +580,12 @@ func (a *App) UpdateCodexAPIKeyLabel(input UpdateCodexAPIKeyLabelInput) error {
550580

551581
func (a *App) UpdateCodexAPIKeyConfig(input UpdateCodexAPIKeyConfigInput) error {
552582
return a.core.UpdateCodexAPIKeyConfig(wailsapp.UpdateCodexAPIKeyConfigInput{
553-
ID: input.ID,
554-
APIKey: input.APIKey,
555-
BaseURL: input.BaseURL,
556-
Prefix: input.Prefix,
583+
ID: input.ID,
584+
APIKey: input.APIKey,
585+
BaseURL: input.BaseURL,
586+
Prefix: input.Prefix,
587+
QuotaCurl: input.QuotaCurl,
588+
QuotaEnabled: input.QuotaEnabled,
557589
})
558590
}
559591

app_mappers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ func mapAccountRecord(record accountsdomain.AccountRecord) AccountRecord {
4040
Prefix: record.Prefix,
4141
AuthIndex: record.AuthIndex,
4242
QuotaKey: record.QuotaKey,
43+
QuotaCurl: record.QuotaCurl,
44+
QuotaEnabled: record.QuotaEnabled,
4345
LocalOnly: record.LocalOnly,
4446
}
4547
}

app_types.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ type AccountRecord struct {
7878
Prefix string `json:"prefix,omitempty"`
7979
AuthIndex interface{} `json:"authIndex,omitempty"`
8080
QuotaKey string `json:"quotaKey,omitempty"`
81+
QuotaCurl string `json:"quotaCurl,omitempty"`
82+
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
8183
LocalOnly bool `json:"localOnly,omitempty"`
8284
}
8385

@@ -90,6 +92,8 @@ type CreateCodexAPIKeyInput struct {
9092
ProxyURL string `json:"proxyUrl,omitempty"`
9193
Headers map[string]string `json:"headers,omitempty"`
9294
ExcludedModels []string `json:"excludedModels,omitempty"`
95+
QuotaCurl string `json:"quotaCurl,omitempty"`
96+
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
9397
}
9498

9599
type UpdateCodexAPIKeyPriorityInput struct {
@@ -103,10 +107,19 @@ type UpdateCodexAPIKeyLabelInput struct {
103107
}
104108

105109
type UpdateCodexAPIKeyConfigInput struct {
106-
ID string `json:"id"`
107-
APIKey string `json:"apiKey"`
108-
BaseURL string `json:"baseUrl"`
109-
Prefix string `json:"prefix,omitempty"`
110+
ID string `json:"id"`
111+
APIKey string `json:"apiKey"`
112+
BaseURL string `json:"baseUrl"`
113+
Prefix string `json:"prefix,omitempty"`
114+
QuotaCurl string `json:"quotaCurl,omitempty"`
115+
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
116+
}
117+
118+
type TestCodexAPIKeyQuotaCurlInput struct {
119+
APIKey string `json:"apiKey"`
120+
BaseURL string `json:"baseUrl"`
121+
Prefix string `json:"prefix,omitempty"`
122+
QuotaCurl string `json:"quotaCurl"`
110123
}
111124

112125
type UpdateAccountPriorityInput struct {

docs-linhay/memory/2026-05-05.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# 2026-05-05
2+
3+
## 请求编排设计稿降复杂度
4+
5+
- 关键反馈:用户认为四卡横向版仍然复杂;复杂度主要来自同屏四张重卡、重复的当前配置 KV 和显眼定位器标签。
6+
- 设计决策:展开组改为“路径摘要 + 单面板编辑”,先用一行展示 `入口 -> 账号组 -> 账号 / 出口 -> 测试`,下方只保留 `请求入口 / 账号 / 出口与测试` 三个决策区。
7+
- 交互收敛:未启用代理池的账号只显示 `直连` 出口;启用代理池后才展开代理出口列表。定位器保留点击复制能力,但降为小角标,不再作为主视觉元素。
8+
- 浏览器验收:已验证旧 `.flow-card-panel` 数量为 0,展开态只有 3 个决策区;切换到 `claude-code` 后账号与出口联动,测试后路径摘要显示 `测试通过`。截图归档 `docs-linhay/spaces/20260502-request-orchestration-menu/screenshots/20260505/request-orchestration/20260505-request-orchestration-simplified-after-v28.png`
9+
10+
## 请求编排开始施工
11+
12+
- 施工边界:先落 APP 层静态工作台,不改 CLIProxyAPI / sidecar 策略枚举,不写后端配置。
13+
- 前端入口:新增一级菜单 `request-orchestration / 请求编排`,新增 `frontend/src/pages/RequestOrchestrationPage.tsx``frontend/src/features/request-orchestration/`
14+
- 模型层:新增纯模型测试覆盖入口切换选第一个兼容账号、直连账号只显示直连、代理账号只显示代理出口、不可参与账号测试失败。
15+
- 整理沉淀:新增 `docs-linhay/dev/20260505-request-orchestration-implementation-start.md`,并把复杂编排页降复杂度规则补入 `.agents/skills/gettokens-domain-engineering/SKILL.md`
16+
17+
## 请求编排接入 APP 层真实数据
18+
19+
- 用户确认开始接真实数据后,当前实现保持不改 CLIProxyAPI,只在 APP 层读取现有资产:sidecar `ready` 后通过 `ListAuthFiles` / `ListAccounts` 读取账号池,代理出口读取代理池 localStorage。
20+
- 请求编排新增数据 hook,浏览器无 Wails bridge 时显式标记 `preview`,Wails 环境且 sidecar 未 ready 时标记 `等待 sidecar`,避免把静态数据误判为真实数据。
21+
- 账号到请求编排账号的映射先采用保守规则:Codex / OpenAI-Compatible 参与 `codex`,Anthropic / Claude 参与 `claude-code`,bridge 可参与两者;OpenAI-Compatible provider 已接 `ListOpenAICompatibleProviders` 中的真实模型列表,其他账号先用入口默认模型直连同名目标模型。
22+
- 流程组改为 localStorage 持久化,恢复时过滤失效账号和代理出口引用;代理池只把 `available` 节点映射为可选出口并始终保留 `direct`
23+
- 账号级 `disabled / proxyPoolEnabled` 覆盖改为请求编排页自己的 localStorage 状态,刷新后保留,但不回写账号池或 CLIProxyAPI。
24+
- 浏览器验收发现 1280×720 下右侧测试按钮落到下方不可见区域,因此已把 `测试当前流程组` 上移到出口卡头部;截图归档 `docs-linhay/spaces/20260502-request-orchestration-menu/screenshots/20260505/request-orchestration/20260505-request-orchestration-real-data-after-v02.png`
25+
- 继续验收:刷新页面后账号级代理池覆盖仍保留,展开默认组后可见 `codex-lab-02` 保持代理状态与 `HK-RESI-03` 出口,测试按钮可直接触发;截图归档 `docs-linhay/spaces/20260502-request-orchestration-menu/screenshots/20260505/request-orchestration/20260505-request-orchestration-model-overrides-after-v01.png`
26+
- 代理池语义修正:启用账号代理池时,如果账号原本是 `direct/null`,APP 层会自动选择首个可用代理;启用后出口列表不再展示 `direct`,没有可用代理时账号不可出站并提示未选择可用代理。浏览器验收截图归档 `docs-linhay/spaces/20260502-request-orchestration-menu/screenshots/20260505/request-orchestration/20260505-request-orchestration-proxy-auto-route-after-v01.png`
27+
28+
## cc-switch 业务覆盖差异
29+
30+
- 对照结论:GetTokens 已覆盖账号池、Codex quota、本地 Claude Code/Codex 写入、请求编排、代理池、Codex 会话管理和本地 usage;未覆盖 cc-switch 的 MCP/Prompts/Skills、Deep Link 导入、WebDAV/数据库备份、系统托盘轻量切换、跨 Claude/Codex/Gemini/OpenCode/OpenClaw/Hermes 的 provider 级切换,以及 provider 级故障转移/熔断/健康监控/请求日志成本账。
31+
- 优先级判断:若 GetTokens 继续定位为 token/account/workbench,应优先补 provider 级测试配置、请求日志成本账和 MCP/Prompts/Skills;WebDAV、托盘、全 CLI provider 切换属于平台化扩展,不应抢在核心账号池与 sidecar relay 闭环之前。
32+
- 已保存 space:`docs-linhay/spaces/20260505-cc-switch-coverage-roadmap/README.md`,当前状态 `parked`;后续有空再从该 space 重启,不在本轮创建 worktree 或进入实现。
33+
34+
## Codex API Key 自定义 Curl 在线额度
35+
36+
- 需求边界:已创建 `docs-linhay/spaces/20260505-codex-api-key-custom-quota-curl/`,目标是让 `codex-api-key` 支持用户填写 curl 模板请求在线额度,并把响应归一为现有 auth-file quota 同款 `CodexQuotaResponse` 展示。
37+
- 关键约束:curl 只作为结构化 HTTP 请求模板解析,不执行 shell;v1 支持 URL、method、headers、body、`{{apiKey}}` 占位符和敏感信息脱敏,不支持管道、重定向、命令替换或多条 curl 串联。
38+
- 验收重点:前端需展示未配置、加载中、成功、错误状态;已配置 quota 的 `codex-api-key` 后续应能参与最长额度筛选,未配置或失败状态不满足筛选。
39+
- 实现完成:`codex-api-key` 本地存储新增 `quota-curl / quota-enabled``GetCodexQuota` 兼容 `codex-api-key:<id>` 并执行结构化 HTTP 请求;响应复用现有 quota builder 输出 `planType + windows`。同步 sidecar 前会剥离 quota curl 字段,避免把 GetTokens 本地配置写入 CLIProxyAPI。
40+
- 安全边界:curl 解析拒绝 shell 管道、重定向、多命令、反引号、`$()`;debug 记录会脱敏敏感 header,并把 URL 中出现的 API key 替换为 `<redacted>`
41+
- 保存链路修复:问题根因是 `frontend/wailsjs/go/models.ts` 缺少 `quotaCurl / quotaEnabled`,导致 `UpdateCodexAPIKeyConfigInput.createFrom()` 丢字段;已补回 Wails 模型字段和回归测试。
42+
- 详情面板补齐:新增 `TestCodexAPIKeyQuotaCurl` 草稿测试接口与“测试请求”按钮,用户无需先保存即可用当前 curl 草稿发起额度请求并看到成功 / 错误摘要。
43+
- 验证:`go test ./...``cd frontend && npm run test:unit``cd frontend && npm run typecheck` 均通过;`cd frontend && npm run build` 已输出 Vite `built` 和产物大小,但本机进程未自行退出,已手动结束。
44+
- Wails 绑定修正:`wails dev` 绑定的是根包 `main.App`,不是 `internal/wailsapp.App`;已在根层 `app.go / app_types.go / app_mappers.go` 暴露 `TestCodexAPIKeyQuotaCurl` 并转发 quota 字段,重新生成 bindings 后 `App.js` 已导出该方法。
45+
- 卡片不显示额度条根因:用户实际本地 store 存在 `quota-curl` 但缺少 `quota-enabled`,导致 `supportsQuota()` 为 false,大刷新不会触发 `GetCodexQuota`。已在加载 store 时迁移“有 curl 但缺 enabled”的旧数据为启用,同时保留显式 `quota-enabled:false`
46+
- 真实接口验证:用户配置的 quota endpoint 返回 `plan_type=pro`,primary / secondary window 均有 used/reset 数据;同时补充 curl parser 对浏览器复制的反斜杠换行格式支持。
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Codex API Key 自定义 Curl 在线额度
2+
3+
## 背景
4+
当前 GetTokens 已支持 `auth-file + codex` 通过在线接口获取额度快照,并将结果归一为 `planType + windows` 展示在账号卡片上。
5+
6+
`codex-api-key` 当前只作为 API key 资产参与配置、路由和真实请求观测统计,没有一条可由用户配置的在线额度查询链路。标准 Codex / 兼容服务的额度接口在不同部署里可能存在差异,例如请求地址、请求头、鉴权 token 和响应字段并不完全固定。
7+
8+
本需求引入“用户填 curl 模板”的方式,让 `codex-api-key` 可以配置自己的在线额度请求,并要求返回内容被归一成与现有 auth-file quota 相同的前端展示格式。
9+
10+
## 目标
11+
1.`codex-api-key` 配置中支持用户填写一段 curl,用作在线额度请求模板。
12+
2. 后端解析 curl,执行请求,并将响应归一为现有 `CodexQuotaResponse` 兼容结构。
13+
3. 前端在 `codex-api-key` 卡片和详情中展示与 auth-file 一致的额度窗口信息。
14+
4. 请求配置、调试信息、错误提示和安全脱敏对后续维护者可理解、可排查。
15+
16+
## 范围
17+
1. `codex-api-key` 新增在线额度配置:
18+
- 支持保存 curl 文本。
19+
- 支持启用 / 禁用该额度配置。
20+
- 支持编辑、测试请求、刷新额度。
21+
2. curl 请求能力:
22+
- 支持常见 `curl` 参数:URL、method、header、body。
23+
- 支持 `Authorization` 等敏感头脱敏展示。
24+
- 支持占位符替换,至少覆盖当前账号 API key,例如 `{{apiKey}}`
25+
3. 响应归一:
26+
- 优先支持与 `auth-file` 在线额度一致的 payload:`plan_type``rate_limit.primary_window``rate_limit.secondary_window`
27+
- 输出给前端的结构必须与现有 `CodexQuotaResponse` 对齐:`planType` + `windows[]`
28+
- `windows` 至少保留 `id / label / remainingPercent / resetLabel / resetAtUnix`
29+
4. 前端展示:
30+
- `codex-api-key` 卡片支持额度加载中、成功、错误、未配置状态。
31+
- 详情弹窗能查看原始请求配置摘要、最近刷新时间、错误摘要。
32+
- 与现有 auth-file 额度展示视觉层级一致。
33+
5. 测试与验收:
34+
- 后端覆盖 curl 解析、占位符替换、响应归一、错误脱敏。
35+
- 前端覆盖 `codex-api-key` 支持额度后的显示和筛选行为。
36+
37+
## 非目标
38+
1. 不为所有 OpenAI-compatible provider 通用化额度查询,本期只覆盖 `codex-api-key`
39+
2. 不强行发现或维护所有第三方 Codex 兼容服务的额度接口。
40+
3. 不把真实请求观测统计、local projected usage 与 quota snapshot 合并为同一个模型。
41+
4. 不要求本期支持复杂 shell 语法、管道、重定向、命令替换或多条 curl 串联。
42+
5. 不在前端保存明文调试响应中的敏感字段,调试展示必须脱敏。
43+
44+
## 验收标准
45+
### 场景 1:配置 curl 后显示在线额度
46+
Given 用户已有一个 `codex-api-key`
47+
When 用户在详情或编辑入口填写可执行的额度查询 curl,并点击测试或刷新
48+
Then 后端发起在线请求
49+
And 响应被归一为与 auth-file 相同的 `CodexQuotaResponse`
50+
And 前端卡片显示 `5H / 7D` 或响应中可识别的额度窗口、剩余百分比和重置时间
51+
52+
### 场景 2:响应格式与 auth-file 返回一致
53+
Given 用户配置的 curl 返回:
54+
55+
```json
56+
{
57+
"plan_type": "pro",
58+
"rate_limit": {
59+
"allowed": true,
60+
"limit_reached": false,
61+
"primary_window": {
62+
"used_percent": 11,
63+
"limit_window_seconds": 18000,
64+
"reset_at": 1777980010
65+
},
66+
"secondary_window": {
67+
"used_percent": 4,
68+
"limit_window_seconds": 604800,
69+
"reset_at": 1778546810
70+
}
71+
}
72+
}
73+
```
74+
75+
When 后端解析该响应
76+
Then 输出结构与现有 auth-file quota 展示结构一致
77+
And `remainingPercent` 分别为 `89``96`
78+
79+
### 场景 3:curl 配置错误可排查
80+
Given 用户填写了无法解析、缺少 URL、或请求失败的 curl
81+
When 用户点击测试或刷新
82+
Then 前端显示明确错误状态
83+
And 不把 API key、Authorization、Cookie 等敏感信息明文展示到调试面板
84+
85+
### 场景 4:未配置 curl 不影响现有账号池
86+
Given `codex-api-key` 没有配置额度 curl
87+
When 账号池加载
88+
Then 该账号显示“未配置额度查询”或等价状态
89+
And auth-file 现有在线额度链路不受影响
90+
91+
### 场景 5:最长额度筛选语义更新
92+
Given `codex-api-key` 已成功加载额度窗口
93+
When 用户开启“仅最长额度可用”筛选
94+
Then auth-file 与已配置 quota 的 codex-api-key 都按最长窗口剩余额度参与筛选
95+
And 未配置、加载失败、无窗口的 codex-api-key 不满足该筛选
96+
97+
## 设计稿入口
98+
99+
- 本期设计稿:`(未产出)`
100+
- 约束:单期只保留一个 HTML 文件;若存在多稿对比,也必须收敛在同一个 HTML 文件内。
101+
102+
## Worktree 映射
103+
104+
- branch:`feat/20260505-codex-api-key-custom-quota-curl`
105+
- worktree:`../GetTokens-worktrees/20260505-codex-api-key-custom-quota-curl/`
106+
107+
## 相关链接
108+
- 现有 auth-file quota 后端入口:`internal/wailsapp/quota.go`
109+
- 现有 quota 前端状态:`frontend/src/features/accounts/hooks/useAccountsQuotaState.ts`
110+
- 现有 quota 展示模型:`frontend/src/features/accounts/model/accountQuota.ts`
111+
- Codex 参考在线额度协议:`docs-linhay/references/codex/codex-rs/backend-client/src/client.rs`
112+
113+
## 当前状态
114+
- 状态:implemented
115+
- 最近更新:2026-05-05
116+
117+
## 实现记录
118+
- 后端新增 curl 模板解析,不执行 shell,仅支持单条 curl 的 URL、method、header、body,并拒绝管道、重定向、多命令、反引号和 `$()` 等 shell 语法。
119+
- `codex-api-key` 本地存储新增 `quota-curl / quota-enabled`,同步 sidecar 时会剥离这两个 GetTokens 本地字段,避免污染 sidecar 配置。
120+
- `GetCodexQuota` 兼容 `quotaKey = codex-api-key:<id>`,对配置了 quota curl 的 API key 执行在线额度请求,并归一成现有 `CodexQuotaResponse`
121+
- 前端新增创建 / 详情编辑入口,支持 quota curl 模板、启用开关、自动刷新和账号卡片同款额度展示。
122+
- 修复 Wails 前端模型遗漏 `quotaCurl / quotaEnabled` 导致详情保存时字段被 `createFrom()` 丢弃的问题,并增加回归测试。
123+
- 详情面板新增“测试请求”按钮,直接使用当前草稿调用 `TestCodexAPIKeyQuotaCurl`,不要求先保存即可验证额度 curl。
124+
- 修复 Wails 绑定根层遗漏:Wails 实际绑定的是根包 `main.App`,已在 `app.go / app_types.go / app_mappers.go` 转发 `TestCodexAPIKeyQuotaCurl` 和 quota 字段,避免 `wails dev` 重新生成 bindings 后丢失导出。
125+
- 修复旧半保存数据迁移:若本地 store 已有 `quota-curl` 但缺少 `quota-enabled`,加载时按启用处理;显式写了 `quota-enabled:false` 的配置仍保持禁用。
126+
- curl parser 支持浏览器复制 curl 常见的反斜杠换行格式,避免多行 `curl ... \` + `-H ...` 被解析成错误 URL。
127+
128+
## 验证记录
129+
- `go test ./...`:通过
130+
- `cd frontend && npm run test:unit`:通过,包含生成绑定导出回归
131+
- `cd frontend && npm run typecheck`:通过
132+
- `./scripts/wails-cli.sh dev`:已完成 `Generating bindings` 并输出 Vite `built`,无 `TestCodexAPIKeyQuotaCurl` 导出错误;验证后手动结束常驻 dev 进程
133+
- 真实 quota 接口直连:返回 `plan_type=pro`,primary / secondary window 均有 used/reset 数据
134+
- `docs-linhay/scripts/check-docs.sh`:通过

0 commit comments

Comments
 (0)