Skip to content

Commit c7196ba

Browse files
committed
feat(claude): add model alias mapping and improve key normalization
- Introduced model alias mapping for Claude configurations, enabling upstream and client-facing model name associations. - Added `computeClaudeModelsHash` to generate a consistent hash for model aliases. - Implemented `normalizeClaudeKey` function to standardize input API key configuration, including models. - Enhanced executor to resolve model aliases to upstream names dynamically. - Updated documentation and configuration examples to reflect new model alias support.
1 parent 6f9c23a commit c7196ba

File tree

10 files changed

+242
-13
lines changed

10 files changed

+242
-13
lines changed

MANAGEMENT_API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ If a plaintext key is detected in the config at startup, it will be bcrypt‑has
9595
```
9696
- Response:
9797
```json
98-
{"debug":true,"proxy-url":"","api-keys":["1...5","JS...W"],"quota-exceeded":{"switch-project":true,"switch-preview-model":true},"generative-language-api-key":["AI...01","AI...02","AI...03"],"request-log":true,"request-retry":3,"claude-api-key":[{"api-key":"cr...56","base-url":"https://example.com/api","proxy-url":"socks5://proxy.example.com:1080"},{"api-key":"cr...e3","base-url":"http://example.com:3000/api","proxy-url":""},{"api-key":"sk-...q2","base-url":"https://example.com","proxy-url":""}],"codex-api-key":[{"api-key":"sk...01","base-url":"https://example/v1","proxy-url":""}],"openai-compatibility":[{"name":"openrouter","base-url":"https://openrouter.ai/api/v1","api-key-entries":[{"api-key":"sk...01","proxy-url":""}],"models":[{"name":"moonshotai/kimi-k2:free","alias":"kimi-k2"}]},{"name":"iflow","base-url":"https://apis.iflow.cn/v1","api-key-entries":[{"api-key":"sk...7e","proxy-url":"socks5://proxy.example.com:1080"}],"models":[{"name":"deepseek-v3.1","alias":"deepseek-v3.1"},{"name":"glm-4.5","alias":"glm-4.5"},{"name":"kimi-k2","alias":"kimi-k2"}]}]}
98+
{"debug":true,"proxy-url":"","api-keys":["1...5","JS...W"],"quota-exceeded":{"switch-project":true,"switch-preview-model":true},"generative-language-api-key":["AI...01","AI...02","AI...03"],"request-log":true,"request-retry":3,"claude-api-key":[{"api-key":"cr...56","base-url":"https://example.com/api","proxy-url":"socks5://proxy.example.com:1080","models":[{"name":"claude-3-5-sonnet-20241022","alias":"claude-sonnet-latest"}]},{"api-key":"cr...e3","base-url":"http://example.com:3000/api","proxy-url":""},{"api-key":"sk-...q2","base-url":"https://example.com","proxy-url":""}],"codex-api-key":[{"api-key":"sk...01","base-url":"https://example/v1","proxy-url":""}],"openai-compatibility":[{"name":"openrouter","base-url":"https://openrouter.ai/api/v1","api-key-entries":[{"api-key":"sk...01","proxy-url":""}],"models":[{"name":"moonshotai/kimi-k2:free","alias":"kimi-k2"}]},{"name":"iflow","base-url":"https://apis.iflow.cn/v1","api-key-entries":[{"api-key":"sk...7e","proxy-url":"socks5://proxy.example.com:1080"}],"models":[{"name":"deepseek-v3.1","alias":"deepseek-v3.1"},{"name":"glm-4.5","alias":"glm-4.5"},{"name":"kimi-k2","alias":"kimi-k2"}]}]}
9999
```
100100

101101
### Debug

MANAGEMENT_API_CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
```
9696
- 响应:
9797
```json
98-
{"debug":true,"proxy-url":"","api-keys":["1...5","JS...W"],"quota-exceeded":{"switch-project":true,"switch-preview-model":true},"generative-language-api-key":["AI...01","AI...02","AI...03"],"request-log":true,"request-retry":3,"claude-api-key":[{"api-key":"cr...56","base-url":"https://example.com/api","proxy-url":"socks5://proxy.example.com:1080"},{"api-key":"cr...e3","base-url":"http://example.com:3000/api","proxy-url":""},{"api-key":"sk-...q2","base-url":"https://example.com","proxy-url":""}],"codex-api-key":[{"api-key":"sk...01","base-url":"https://example/v1","proxy-url":""}],"openai-compatibility":[{"name":"openrouter","base-url":"https://openrouter.ai/api/v1","api-key-entries":[{"api-key":"sk...01","proxy-url":""}],"models":[{"name":"moonshotai/kimi-k2:free","alias":"kimi-k2"}]},{"name":"iflow","base-url":"https://apis.iflow.cn/v1","api-key-entries":[{"api-key":"sk...7e","proxy-url":"socks5://proxy.example.com:1080"}],"models":[{"name":"deepseek-v3.1","alias":"deepseek-v3.1"},{"name":"glm-4.5","alias":"glm-4.5"},{"name":"kimi-k2","alias":"kimi-k2"}]}]}
98+
{"debug":true,"proxy-url":"","api-keys":["1...5","JS...W"],"quota-exceeded":{"switch-project":true,"switch-preview-model":true},"generative-language-api-key":["AI...01","AI...02","AI...03"],"request-log":true,"request-retry":3,"claude-api-key":[{"api-key":"cr...56","base-url":"https://example.com/api","proxy-url":"socks5://proxy.example.com:1080","models":[{"name":"claude-3-5-sonnet-20241022","alias":"claude-sonnet-latest"}]},{"api-key":"cr...e3","base-url":"http://example.com:3000/api","proxy-url":""},{"api-key":"sk-...q2","base-url":"https://example.com","proxy-url":""}],"codex-api-key":[{"api-key":"sk...01","base-url":"https://example/v1","proxy-url":""}],"openai-compatibility":[{"name":"openrouter","base-url":"https://openrouter.ai/api/v1","api-key-entries":[{"api-key":"sk...01","proxy-url":""}],"models":[{"name":"moonshotai/kimi-k2:free","alias":"kimi-k2"}]},{"name":"iflow","base-url":"https://apis.iflow.cn/v1","api-key-entries":[{"api-key":"sk...7e","proxy-url":"socks5://proxy.example.com:1080"}],"models":[{"name":"deepseek-v3.1","alias":"deepseek-v3.1"},{"name":"glm-4.5","alias":"glm-4.5"},{"name":"kimi-k2","alias":"kimi-k2"}]}]}
9999
```
100100

101101
### Debug

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,16 +318,21 @@ The server uses a YAML configuration file (`config.yaml`) located in the project
318318
| `claude-api-key.api-key` | string | "" | Claude API key. |
319319
| `claude-api-key.base-url` | string | "" | Custom Claude API endpoint, if you use a third-party API endpoint. |
320320
| `claude-api-key.proxy-url` | string | "" | Proxy URL for this specific API key. Overrides the global proxy-url setting. Supports socks5/http/https protocols. |
321+
| `claude-api-key.models` | object[] | [] | Model alias entries for this key. |
322+
| `claude-api-key.models.*.name` | string | "" | Upstream Claude model name invoked against the API. |
323+
| `claude-api-key.models.*.alias` | string | "" | Client-facing alias that maps to the upstream model name. |
321324
| `openai-compatibility` | object[] | [] | Upstream OpenAI-compatible providers configuration (name, base-url, api-keys, models). |
322325
| `openai-compatibility.*.name` | string | "" | The name of the provider. It will be used in the user agent and other places. |
323326
| `openai-compatibility.*.base-url` | string | "" | The base URL of the provider. |
324327
| `openai-compatibility.*.api-keys` | string[] | [] | (Deprecated) The API keys for the provider. Use api-key-entries instead for per-key proxy support. |
325328
| `openai-compatibility.*.api-key-entries` | object[] | [] | API key entries with optional per-key proxy configuration. Preferred over api-keys. |
326329
| `openai-compatibility.*.api-key-entries.*.api-key` | string | "" | The API key for this entry. |
327330
| `openai-compatibility.*.api-key-entries.*.proxy-url` | string | "" | Proxy URL for this specific API key. Overrides the global proxy-url setting. Supports socks5/http/https protocols. |
328-
| `openai-compatibility.*.models` | object[] | [] | The actual model name. |
329-
| `openai-compatibility.*.models.*.name` | string | "" | The models supported by the provider. |
330-
| `openai-compatibility.*.models.*.alias` | string | "" | The alias used in the API. |
331+
| `openai-compatibility.*.models` | object[] | [] | Model alias definitions routing client aliases to upstream names. |
332+
| `openai-compatibility.*.models.*.name` | string | "" | Upstream model name invoked against the provider. |
333+
| `openai-compatibility.*.models.*.alias` | string | "" | Client alias routed to the upstream model. |
334+
335+
When `claude-api-key.models` is specified, only the provided aliases are registered in the model registry (mirroring OpenAI compatibility behaviour), and the default Claude catalog is suppressed for that credential.
331336

332337
### Example Configuration File
333338

README_CN.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,16 +331,21 @@ console.log(await claudeResponse.json());
331331
| `claude-api-key.api-key` | string | "" | Claude API密钥。 |
332332
| `claude-api-key.base-url` | string | "" | 自定义的Claude API端点,如果您使用第三方的API端点。 |
333333
| `claude-api-key.proxy-url` | string | "" | 针对该API密钥的代理URL。会覆盖全局proxy-url设置。支持socks5/http/https协议。 |
334+
| `claude-api-key.models` | object[] | [] | Model alias entries for this key. |
335+
| `claude-api-key.models.*.name` | string | "" | Upstream Claude model name invoked against the API. |
336+
| `claude-api-key.models.*.alias` | string | "" | Client-facing alias that maps to the upstream model name. |
334337
| `openai-compatibility` | object[] | [] | 上游OpenAI兼容提供商的配置(名称、基础URL、API密钥、模型)。 |
335338
| `openai-compatibility.*.name` | string | "" | 提供商的名称。它将被用于用户代理(User Agent)和其他地方。 |
336339
| `openai-compatibility.*.base-url` | string | "" | 提供商的基础URL。 |
337340
| `openai-compatibility.*.api-keys` | string[] | [] | (已弃用) 提供商的API密钥。建议改用api-key-entries以获得每密钥代理支持。 |
338341
| `openai-compatibility.*.api-key-entries` | object[] | [] | API密钥条目,支持可选的每密钥代理配置。优先于api-keys。 |
339342
| `openai-compatibility.*.api-key-entries.*.api-key` | string | "" | 该条目的API密钥。 |
340343
| `openai-compatibility.*.api-key-entries.*.proxy-url` | string | "" | 针对该API密钥的代理URL。会覆盖全局proxy-url设置。支持socks5/http/https协议。 |
341-
| `openai-compatibility.*.models` | object[] | [] | 实际的模型名称。 |
342-
| `openai-compatibility.*.models.*.name` | string | "" | 提供商支持的模型。 |
343-
| `openai-compatibility.*.models.*.alias` | string | "" | 在API中使用的别名。 |
344+
| `openai-compatibility.*.models` | object[] | [] | Model alias definitions routing client aliases to upstream names. |
345+
| `openai-compatibility.*.models.*.name` | string | "" | Upstream model name invoked against the provider. |
346+
| `openai-compatibility.*.models.*.alias` | string | "" | Client alias routed to the upstream model. |
347+
348+
When `claude-api-key.models` is provided, only the listed aliases are registered for that credential, and the default Claude model catalog is skipped.
344349

345350
### 配置文件示例
346351

config.example.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ ws-auth: false
6565
# - api-key: "sk-atSM..."
6666
# base-url: "https://www.example.com" # use the custom claude API endpoint
6767
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
68+
# models:
69+
# - name: "claude-3-5-sonnet-20241022" # upstream model name
70+
# alias: "claude-sonnet-latest" # client alias mapped to the upstream model
6871

6972
# OpenAI compatibility providers
7073
#openai-compatibility:

internal/api/handlers/management/config_lists.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ func (h *Handler) PutClaudeKeys(c *gin.Context) {
150150
}
151151
arr = obj.Items
152152
}
153+
for i := range arr {
154+
normalizeClaudeKey(&arr[i])
155+
}
153156
h.cfg.ClaudeKey = arr
154157
h.persist(c)
155158
}
@@ -163,6 +166,7 @@ func (h *Handler) PatchClaudeKey(c *gin.Context) {
163166
c.JSON(400, gin.H{"error": "invalid body"})
164167
return
165168
}
169+
normalizeClaudeKey(body.Value)
166170
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.ClaudeKey) {
167171
h.cfg.ClaudeKey[*body.Index] = *body.Value
168172
h.persist(c)
@@ -472,3 +476,26 @@ func normalizedOpenAICompatibilityEntries(entries []config.OpenAICompatibility)
472476
}
473477
return out
474478
}
479+
480+
func normalizeClaudeKey(entry *config.ClaudeKey) {
481+
if entry == nil {
482+
return
483+
}
484+
entry.APIKey = strings.TrimSpace(entry.APIKey)
485+
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
486+
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
487+
if len(entry.Models) == 0 {
488+
return
489+
}
490+
normalized := make([]config.ClaudeModel, 0, len(entry.Models))
491+
for i := range entry.Models {
492+
model := entry.Models[i]
493+
model.Name = strings.TrimSpace(model.Name)
494+
model.Alias = strings.TrimSpace(model.Alias)
495+
if model.Name == "" && model.Alias == "" {
496+
continue
497+
}
498+
normalized = append(normalized, model)
499+
}
500+
entry.Models = normalized
501+
}

internal/config/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ type ClaudeKey struct {
9494

9595
// ProxyURL overrides the global proxy setting for this API key if provided.
9696
ProxyURL string `yaml:"proxy-url" json:"proxy-url"`
97+
98+
// Models defines upstream model names and aliases for request routing.
99+
Models []ClaudeModel `yaml:"models" json:"models"`
100+
}
101+
102+
// ClaudeModel describes a mapping between an alias and the actual upstream model name.
103+
type ClaudeModel struct {
104+
// Name is the upstream model identifier used when issuing requests.
105+
Name string `yaml:"name" json:"name"`
106+
107+
// Alias is the client-facing model name that maps to Name.
108+
Alias string `yaml:"alias" json:"alias"`
97109
}
98110

99111
// CodexKey represents the configuration for a Codex API key,

0 commit comments

Comments
 (0)