vibe-proxy/backend/internal/client/claude/models/models.go
2026-08-24 00:10:41 +02:00

100 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)
}