Skip to content

Commit 43720c5

Browse files
committed
feat: add claude code account list
1 parent 4ee38bc commit 43720c5

42 files changed

Lines changed: 3883 additions & 447 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
name: gettokens-claude-code-account-list
3+
description: GetTokens Claude Code 账号列表:Anthropic 格式账号筛选、请求顺序、路由探测、模型映射、官方默认模型 profile 与 local apply 边界。
4+
---
5+
6+
# GetTokens Claude Code Account List
7+
8+
当任务涉及 Claude Code 账号列表、Claude Code relay 账号请求顺序、Anthropic 格式账号筛选、Claude Code 模型映射、官方默认模型 profile、`~/.claude/settings.json` local apply 或 Claude Code 路由探测时使用本 skill。
9+
10+
## 1. 业务边界
11+
12+
- Claude Code 账号列表不是 `settings.json` 多 key 管理器。
13+
- 它是 GetTokens relay 可供 Claude Code 使用的 Anthropic 格式账号请求工作台。
14+
- P0 账号筛选条件:`AccountRecord.supportedFormats` 包含 `anthropic`
15+
- Claude Code 本地仍只写一个 relay endpoint / relay key;多账号轮换发生在 GetTokens relay 内。
16+
- 不把 provider 名称等于 `claude` 作为筛选条件。
17+
18+
## 2. 账号列表语义
19+
20+
- 候选来源以统一 `AccountRecord` 为入口:
21+
- `supportedFormats` 包含 `anthropic`
22+
- 存在可用于 relay 的凭证或 auth route id
23+
- 请求出口优先 `formatBaseUrls.anthropic`,没有时回退 `baseUrl`
24+
- 请求顺序复用 Codex 账号列表模式:
25+
- 禁用或阻塞账号保留在排序中
26+
- 运行时请求候选只包含当前可请求账号
27+
- 拖拽排序写回 `UpdateAccountPriority`
28+
- 启停写回 `SetAccountDisabled`
29+
- 浏览器 preview 必须在缺少 Wails runtime 时稳定显示 preview 数据。
30+
31+
## 3. 模型映射语义
32+
33+
- UI 展示方向:真实上游模型 `name` -> Claude Code 请求模型 `alias`
34+
- 运行时解析方向:Claude Code 请求模型 alias -> 真实上游模型 name。
35+
- API key / openai-compatible 账号优先复用账号配置内的 `models[]`
36+
- `models[].name` 是真实上游模型
37+
- `models[].alias` 是 Claude Code 请求模型
38+
- OAuth/auth-file 账号优先复用 sidecar `oauth-model-alias`,Claude Code channel 默认为 `claude`
39+
- 默认同名透传,不展示或保存 `model -> model`
40+
- 保存时按 `name + alias` 去重,允许同一个真实模型映射到多个 Claude Code alias。
41+
42+
## 4. 官方默认模型 profile
43+
44+
- 官网给出的默认值就是 `ProviderDefaultModelProfile` 的权威值。
45+
- 官网列出的其他模型只叫“官方可切换模型”,不叫默认候选。
46+
- 本地 `cc-switch` / GetTokens 旧预设只用于迁移差异提示,不参与已有官网来源厂商的默认值决策。
47+
- 远端 `/models` 只能刷新可切换模型集合,不能覆盖官网默认值或用户已保存映射。
48+
- profile 可一键填充 Claude Code local apply 字段,也可生成 relay 映射草稿;保存仍走 `models[]``oauth-model-alias`
49+
- 已保存的用户映射优先级最高;profile 更新只能提示,不能自动覆盖。
50+
- 官方默认值表维护在 `docs-linhay/spaces/20260519-claude-code-account-list/plans/official-model-profiles.md`
51+
52+
## 5. 当前官方校准结论
53+
54+
- DeepSeek:`deepseek-v4-pro[1m]` 为 main/sonnet/opus,`deepseek-v4-flash` 为 haiku;`CLAUDE_CODE_SUBAGENT_MODEL` / `CLAUDE_CODE_EFFORT_LEVEL` 只属于 local apply extra env。
55+
- 百炼:按 Token Plan / Coding Plan / Pay-as-you-go 分 profile,默认模型为 `qwen3.6-plus`,haiku 视官方场景为 `qwen3.6-plus``qwen3.6-flash`
56+
- MiniMax:`MiniMax-M2.7`
57+
- Xiaomi MiMo:默认值为 `mimo-v2.5-pro``mimo-v2.5-pro[1m]` 是官方长上下文变体,`mimo-v2.5` / `mimo-v2.5-tts` 是官方可切换模型。
58+
- Kimi:当前已确认官方 Claude Code env 示例为 `kimi-k2.5`;本地 `kimi-k2.6` 只作旧预设差异提示。
59+
- Doubao:默认按官网 `ark-code-latest` 或用户选择的具体 `Model_Name`;本地 `doubao-seed-2-0-code-preview-latest` 只作迁移提示。
60+
61+
## 6. 验证
62+
63+
- 文档或需求调整后运行:
64+
- `docs-linhay/scripts/check-docs.sh`
65+
- `qmd update`
66+
- `qmd embed`
67+
- 前端实现后至少覆盖:
68+
- Anthropic 格式筛选
69+
- 禁用保留排序但不参与运行候选
70+
- `formatBaseUrls.anthropic` 优先级
71+
- 模型映射同名透传
72+
- 同一真实模型多个 Claude alias
73+
- 官方默认 profile 不覆盖用户映射

app.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,40 @@ func (a *App) ProbeCodexAccountRouting(input ProbeCodexAccountRoutingInput) (*Co
189189
}, nil
190190
}
191191

192+
func (a *App) ProbeClaudeCodeAccountRouting(input ProbeClaudeCodeAccountRoutingInput) (*ClaudeCodeAccountRoutingProbeResult, error) {
193+
result, err := a.core.ProbeClaudeCodeAccountRouting(wailsapp.ProbeClaudeCodeAccountRoutingInput{
194+
Model: input.Model,
195+
Attempts: input.Attempts,
196+
AllowAccountIDs: append([]string(nil), input.AllowAccountIDs...),
197+
DenyAccountIDs: append([]string(nil), input.DenyAccountIDs...),
198+
OrderAccountIDs: append([]string(nil), input.OrderAccountIDs...),
199+
AllowFallback: input.AllowFallback,
200+
})
201+
if err != nil {
202+
return nil, err
203+
}
204+
attempts := make([]ClaudeCodeAccountRoutingProbeAttempt, 0, len(result.Attempts))
205+
for _, attempt := range result.Attempts {
206+
attempts = append(attempts, ClaudeCodeAccountRoutingProbeAttempt{
207+
Index: attempt.Index,
208+
Success: attempt.Success,
209+
StatusCode: attempt.StatusCode,
210+
AccountID: attempt.AccountID,
211+
AccountLabel: attempt.AccountLabel,
212+
Provider: attempt.Provider,
213+
Message: attempt.Message,
214+
Evidence: attempt.Evidence,
215+
ResponseBody: attempt.ResponseBody,
216+
StartedAt: attempt.StartedAt,
217+
FinishedAt: attempt.FinishedAt,
218+
})
219+
}
220+
return &ClaudeCodeAccountRoutingProbeResult{
221+
Model: result.Model,
222+
Attempts: attempts,
223+
}, nil
224+
}
225+
192226
func (a *App) UploadAuthFiles(files []UploadFilePayload) error {
193227
payload := make([]wailsapp.UploadFilePayload, 0, len(files))
194228
for _, file := range files {
@@ -956,6 +990,7 @@ func (a *App) CreateCodexAPIKey(input CreateCodexAPIKeyInput) error {
956990
Prefix: input.Prefix,
957991
ProxyURL: input.ProxyURL,
958992
Headers: input.Headers,
993+
Models: mapOpenAICompatibleModelsToWails(input.Models),
959994
ExcludedModels: input.ExcludedModels,
960995
QuotaCurl: input.QuotaCurl,
961996
QuotaEnabled: input.QuotaEnabled,
@@ -978,6 +1013,7 @@ func (a *App) UpdateCodexAPIKeyConfig(input UpdateCodexAPIKeyConfigInput) error
9781013
BaseURL: input.BaseURL,
9791014
Prefix: input.Prefix,
9801015
ProxyURL: input.ProxyURL,
1016+
Models: mapOpenAICompatibleModelsToWails(input.Models),
9811017
QuotaCurl: input.QuotaCurl,
9821018
QuotaEnabled: input.QuotaEnabled,
9831019
BillingCurl: input.BillingCurl,

app_mappers.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
accountsdomain "github.com/linhay/gettokens/internal/accounts"
5+
"github.com/linhay/gettokens/internal/cliproxyapi"
56
wailsapp "github.com/linhay/gettokens/internal/wailsapp"
67
)
78

@@ -38,6 +39,9 @@ func mapAccountRecord(record accountsdomain.AccountRecord) AccountRecord {
3839
PlanType: record.PlanType,
3940
Name: record.Name,
4041
APIKey: record.APIKey,
42+
APIKeys: append([]string(nil), record.APIKeys...),
43+
Headers: cloneStringMap(record.Headers),
44+
Models: mapAccountRecordModels(record.Models),
4145
KeyFingerprint: record.KeyFingerprint,
4246
KeySuffix: record.KeySuffix,
4347
BaseURL: record.BaseURL,
@@ -55,6 +59,28 @@ func mapAccountRecord(record accountsdomain.AccountRecord) AccountRecord {
5559
}
5660
}
5761

62+
func mapAccountRecordModels(items []cliproxyapi.CodexModel) []OpenAICompatibleModel {
63+
out := make([]OpenAICompatibleModel, 0, len(items))
64+
for _, item := range items {
65+
out = append(out, OpenAICompatibleModel{
66+
Name: item.Name,
67+
Alias: item.Alias,
68+
})
69+
}
70+
return out
71+
}
72+
73+
func cloneStringMap(items map[string]string) map[string]string {
74+
if len(items) == 0 {
75+
return nil
76+
}
77+
out := make(map[string]string, len(items))
78+
for key, value := range items {
79+
out[key] = value
80+
}
81+
return out
82+
}
83+
5884
func mapCodexQuotaResponse(result *wailsapp.CodexQuotaResponse) *CodexQuotaResponse {
5985
if result == nil {
6086
return &CodexQuotaResponse{}

app_types.go

Lines changed: 80 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -74,47 +74,51 @@ type CodexQuotaBillingBalanceInfo struct {
7474
}
7575

7676
type AccountRecord struct {
77-
ID string `json:"id"`
78-
Provider string `json:"provider"`
79-
CredentialSource string `json:"credentialSource"`
80-
DisplayName string `json:"displayName"`
81-
Status string `json:"status"`
82-
Priority int `json:"priority,omitempty"`
83-
Disabled bool `json:"disabled,omitempty"`
84-
Email string `json:"email,omitempty"`
85-
PlanType string `json:"planType,omitempty"`
86-
Name string `json:"name,omitempty"`
87-
APIKey string `json:"apiKey,omitempty"`
88-
KeyFingerprint string `json:"keyFingerprint,omitempty"`
89-
KeySuffix string `json:"keySuffix,omitempty"`
90-
BaseURL string `json:"baseUrl,omitempty"`
91-
Prefix string `json:"prefix,omitempty"`
92-
ProxyURL string `json:"proxyUrl,omitempty"`
93-
AuthIndex interface{} `json:"authIndex,omitempty"`
94-
QuotaKey string `json:"quotaKey,omitempty"`
95-
QuotaCurl string `json:"quotaCurl,omitempty"`
96-
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
97-
LocalOnly bool `json:"localOnly,omitempty"`
98-
SupportedFormats []string `json:"supportedFormats,omitempty"`
99-
FormatBaseURLs map[string]string `json:"formatBaseUrls,omitempty"`
100-
BillingCurl string `json:"billingCurl,omitempty"`
101-
BillingEnabled bool `json:"billingEnabled,omitempty"`
77+
ID string `json:"id"`
78+
Provider string `json:"provider"`
79+
CredentialSource string `json:"credentialSource"`
80+
DisplayName string `json:"displayName"`
81+
Status string `json:"status"`
82+
Priority int `json:"priority,omitempty"`
83+
Disabled bool `json:"disabled,omitempty"`
84+
Email string `json:"email,omitempty"`
85+
PlanType string `json:"planType,omitempty"`
86+
Name string `json:"name,omitempty"`
87+
APIKey string `json:"apiKey,omitempty"`
88+
APIKeys []string `json:"apiKeys,omitempty"`
89+
Headers map[string]string `json:"headers,omitempty"`
90+
Models []OpenAICompatibleModel `json:"models,omitempty"`
91+
KeyFingerprint string `json:"keyFingerprint,omitempty"`
92+
KeySuffix string `json:"keySuffix,omitempty"`
93+
BaseURL string `json:"baseUrl,omitempty"`
94+
Prefix string `json:"prefix,omitempty"`
95+
ProxyURL string `json:"proxyUrl,omitempty"`
96+
AuthIndex interface{} `json:"authIndex,omitempty"`
97+
QuotaKey string `json:"quotaKey,omitempty"`
98+
QuotaCurl string `json:"quotaCurl,omitempty"`
99+
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
100+
LocalOnly bool `json:"localOnly,omitempty"`
101+
SupportedFormats []string `json:"supportedFormats,omitempty"`
102+
FormatBaseURLs map[string]string `json:"formatBaseUrls,omitempty"`
103+
BillingCurl string `json:"billingCurl,omitempty"`
104+
BillingEnabled bool `json:"billingEnabled,omitempty"`
102105
}
103106

104107
type CreateCodexAPIKeyInput struct {
105-
APIKey string `json:"apiKey"`
106-
Label string `json:"label,omitempty"`
107-
BaseURL string `json:"baseUrl"`
108-
FormatBaseURLs map[string]string `json:"formatBaseUrls,omitempty"`
109-
Priority int `json:"priority,omitempty"`
110-
Prefix string `json:"prefix,omitempty"`
111-
ProxyURL string `json:"proxyUrl,omitempty"`
112-
Headers map[string]string `json:"headers,omitempty"`
113-
ExcludedModels []string `json:"excludedModels,omitempty"`
114-
QuotaCurl string `json:"quotaCurl,omitempty"`
115-
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
116-
BillingCurl string `json:"billingCurl,omitempty"`
117-
BillingEnabled bool `json:"billingEnabled,omitempty"`
108+
APIKey string `json:"apiKey"`
109+
Label string `json:"label,omitempty"`
110+
BaseURL string `json:"baseUrl"`
111+
FormatBaseURLs map[string]string `json:"formatBaseUrls,omitempty"`
112+
Priority int `json:"priority,omitempty"`
113+
Prefix string `json:"prefix,omitempty"`
114+
ProxyURL string `json:"proxyUrl,omitempty"`
115+
Headers map[string]string `json:"headers,omitempty"`
116+
Models []OpenAICompatibleModel `json:"models,omitempty"`
117+
ExcludedModels []string `json:"excludedModels,omitempty"`
118+
QuotaCurl string `json:"quotaCurl,omitempty"`
119+
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
120+
BillingCurl string `json:"billingCurl,omitempty"`
121+
BillingEnabled bool `json:"billingEnabled,omitempty"`
118122
}
119123

120124
type UpdateCodexAPIKeyPriorityInput struct {
@@ -128,15 +132,16 @@ type UpdateCodexAPIKeyLabelInput struct {
128132
}
129133

130134
type UpdateCodexAPIKeyConfigInput struct {
131-
ID string `json:"id"`
132-
APIKey string `json:"apiKey"`
133-
BaseURL string `json:"baseUrl"`
134-
Prefix string `json:"prefix,omitempty"`
135-
ProxyURL string `json:"proxyUrl,omitempty"`
136-
QuotaCurl string `json:"quotaCurl,omitempty"`
137-
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
138-
BillingCurl string `json:"billingCurl,omitempty"`
139-
BillingEnabled bool `json:"billingEnabled,omitempty"`
135+
ID string `json:"id"`
136+
APIKey string `json:"apiKey"`
137+
BaseURL string `json:"baseUrl"`
138+
Prefix string `json:"prefix,omitempty"`
139+
ProxyURL string `json:"proxyUrl,omitempty"`
140+
Models []OpenAICompatibleModel `json:"models,omitempty"`
141+
QuotaCurl string `json:"quotaCurl,omitempty"`
142+
QuotaEnabled bool `json:"quotaEnabled,omitempty"`
143+
BillingCurl string `json:"billingCurl,omitempty"`
144+
BillingEnabled bool `json:"billingEnabled,omitempty"`
140145
}
141146

142147
type TestCodexAPIKeyQuotaCurlInput struct {
@@ -160,6 +165,15 @@ type ProbeCodexAccountRoutingInput struct {
160165
AllowFallback bool `json:"allowFallback,omitempty"`
161166
}
162167

168+
type ProbeClaudeCodeAccountRoutingInput struct {
169+
Model string `json:"model"`
170+
Attempts int `json:"attempts,omitempty"`
171+
AllowAccountIDs []string `json:"allowAccountIDs,omitempty"`
172+
DenyAccountIDs []string `json:"denyAccountIDs,omitempty"`
173+
OrderAccountIDs []string `json:"orderAccountIDs,omitempty"`
174+
AllowFallback bool `json:"allowFallback,omitempty"`
175+
}
176+
163177
type UpdateOAuthModelAliasesInput struct {
164178
Channel string `json:"channel"`
165179
Models []OpenAICompatibleModel `json:"models,omitempty"`
@@ -184,6 +198,25 @@ type CodexAccountRoutingProbeAttempt struct {
184198
FinishedAt string `json:"finishedAt,omitempty"`
185199
}
186200

201+
type ClaudeCodeAccountRoutingProbeResult struct {
202+
Model string `json:"model"`
203+
Attempts []ClaudeCodeAccountRoutingProbeAttempt `json:"attempts"`
204+
}
205+
206+
type ClaudeCodeAccountRoutingProbeAttempt struct {
207+
Index int `json:"index"`
208+
Success bool `json:"success"`
209+
StatusCode int `json:"statusCode,omitempty"`
210+
AccountID string `json:"accountID,omitempty"`
211+
AccountLabel string `json:"accountLabel,omitempty"`
212+
Provider string `json:"provider,omitempty"`
213+
Message string `json:"message,omitempty"`
214+
Evidence string `json:"evidence,omitempty"`
215+
ResponseBody string `json:"responseBody,omitempty"`
216+
StartedAt string `json:"startedAt,omitempty"`
217+
FinishedAt string `json:"finishedAt,omitempty"`
218+
}
219+
187220
type OpenAICompatibleProvider struct {
188221
Name string `json:"name"`
189222
Priority int `json:"priority,omitempty"`

0 commit comments

Comments
 (0)