Files
Luis Pater 6914478562 feat(config): add support for disabling model list cloaking in Claude Code
- Introduced `DisableCloakingModelList` in `ClaudeCodeConfig` to control model ID cloaking in Anthropic model list responses.
- Updated relevant APIs and handlers to respect the new configuration.
- Added comprehensive tests for enabling/disabling cloaking behavior and config-driven hot reload scenarios.
- Extended example configuration and documentation to include the new setting.

Closes: #4473
2026-07-27 18:16:41 +08:00

101 lines
2.7 KiB
Go

// Package models builds model catalogs for Anthropic clients.
package models
import (
"sort"
"strings"
)
const claudeDDModelPrefix = "claude-fable-5-dd-"
// BuildResponse builds an Anthropic model response from available models.
func BuildResponse(availableModels []map[string]any, disableCloaking bool) map[string]any {
models := make([]map[string]any, len(availableModels))
for i, model := range availableModels {
models[i] = cloneModel(model)
if id, ok := models[i]["id"].(string); ok && !disableCloaking {
models[i]["id"] = EnsureClaudeModelIDPrefix(id)
}
}
sort.SliceStable(models, func(i, j int) bool {
displayNameI, _ := models[i]["display_name"].(string)
displayNameJ, _ := models[j]["display_name"].(string)
if displayNameI != displayNameJ {
return displayNameI < displayNameJ
}
idI, _ := models[i]["id"].(string)
idJ, _ := models[j]["id"].(string)
return idI < idJ
})
firstID := ""
lastID := ""
if len(models) > 0 {
firstID, _ = models[0]["id"].(string)
lastID, _ = models[len(models)-1]["id"].(string)
}
return map[string]any{
"data": models,
"has_more": false,
"first_id": firstID,
"last_id": lastID,
}
}
// EnsureClaudeModelIDPrefix rewrites model IDs for Anthropic model listings.
// IDs that already start with "claude-" are returned unchanged; all other IDs
// become "claude-fable-5-dd-" plus the original ID with its characters reversed.
func EnsureClaudeModelIDPrefix(id string) string {
if id == "" || strings.HasPrefix(id, "claude-") {
return id
}
return claudeDDModelPrefix + reverseModelID(id)
}
// ResolveClaudeModelIDPrefix reverses EnsureClaudeModelIDPrefix for request routing.
// Optional thinking suffixes in model(value) form are preserved.
func ResolveClaudeModelIDPrefix(id string) string {
if id == "" {
return id
}
base, suffix, hasSuffix := splitModelThinkingSuffix(id)
if !strings.HasPrefix(base, claudeDDModelPrefix) {
return id
}
encoded := base[len(claudeDDModelPrefix):]
if encoded == "" {
return id
}
resolved := reverseModelID(encoded)
if hasSuffix {
return resolved + "(" + suffix + ")"
}
return resolved
}
func cloneModel(model map[string]any) map[string]any {
cloned := make(map[string]any, len(model))
for key, value := range model {
cloned[key] = value
}
return cloned
}
func splitModelThinkingSuffix(model string) (base, suffix string, hasSuffix bool) {
lastOpen := strings.LastIndex(model, "(")
if lastOpen == -1 || !strings.HasSuffix(model, ")") {
return model, "", false
}
return model[:lastOpen], model[lastOpen+1 : len(model)-1], true
}
func reverseModelID(id string) string {
runes := []rune(id)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}