package models import ( "regexp" "strings" ) var anthropicToCursor = map[string]string{ "claude-opus-4-6": "opus-4.6", "claude-opus-4.6": "opus-4.6", "claude-sonnet-4-6": "sonnet-4.6", "claude-sonnet-4.6": "sonnet-4.6", "claude-opus-4-5": "opus-4.5", "claude-opus-4.5": "opus-4.5", "claude-sonnet-4-5": "sonnet-4.5", "claude-sonnet-4.5": "sonnet-4.5", "claude-opus-4": "opus-4.6", "claude-sonnet-4": "sonnet-4.6", "claude-haiku-4-5-20251001": "sonnet-4.5", "claude-haiku-4-5": "sonnet-4.5", "claude-haiku-4-6": "sonnet-4.6", "claude-haiku-4": "sonnet-4.5", "claude-opus-4-6-thinking": "opus-4.6-thinking", "claude-sonnet-4-6-thinking": "sonnet-4.6-thinking", "claude-opus-4-5-thinking": "opus-4.5-thinking", "claude-sonnet-4-5-thinking": "sonnet-4.5-thinking", } type ModelAlias struct { CursorID string AnthropicID string Name string } var cursorToAnthropicAlias = []ModelAlias{ {"opus-4.6", "claude-opus-4-6", "Claude 4.6 Opus"}, {"opus-4.6-thinking", "claude-opus-4-6-thinking", "Claude 4.6 Opus (Thinking)"}, {"sonnet-4.6", "claude-sonnet-4-6", "Claude 4.6 Sonnet"}, {"sonnet-4.6-thinking", "claude-sonnet-4-6-thinking", "Claude 4.6 Sonnet (Thinking)"}, {"opus-4.5", "claude-opus-4-5", "Claude 4.5 Opus"}, {"opus-4.5-thinking", "claude-opus-4-5-thinking", "Claude 4.5 Opus (Thinking)"}, {"sonnet-4.5", "claude-sonnet-4-5", "Claude 4.5 Sonnet"}, {"sonnet-4.5-thinking", "claude-sonnet-4-5-thinking", "Claude 4.5 Sonnet (Thinking)"}, } // cursorModelPattern matches cursor model IDs like "opus-4.6", "sonnet-4.7-thinking". var cursorModelPattern = regexp.MustCompile(`^([a-zA-Z]+)-(\d+)\.(\d+)(-thinking)?$`) // reverseDynamicPattern matches dynamically generated anthropic aliases // like "claude-opus-4-7", "claude-sonnet-4-7-thinking". var reverseDynamicPattern = regexp.MustCompile(`^claude-([a-zA-Z]+)-(\d+)-(\d+)(-thinking)?$`) func generateDynamicAlias(cursorID string) (AnthropicAlias, bool) { m := cursorModelPattern.FindStringSubmatch(cursorID) if m == nil { return AnthropicAlias{}, false } family := m[1] major := m[2] minor := m[3] thinking := m[4] anthropicID := "claude-" + family + "-" + major + "-" + minor + thinking capFamily := strings.ToUpper(family[:1]) + family[1:] name := capFamily + " " + major + "." + minor if thinking == "-thinking" { name += " (Thinking)" } return AnthropicAlias{ID: anthropicID, Name: name}, true } func reverseDynamicAlias(anthropicID string) (string, bool) { m := reverseDynamicPattern.FindStringSubmatch(anthropicID) if m == nil { return "", false } return m[1] + "-" + m[2] + "." + m[3] + m[4], true } func ResolveToCursorModel(requested string) string { if strings.TrimSpace(requested) == "" { return "" } key := strings.ToLower(strings.TrimSpace(requested)) if v, ok := anthropicToCursor[key]; ok { return v } if v, ok := reverseDynamicAlias(key); ok { return v } return strings.TrimSpace(requested) } type AnthropicAlias struct { ID string Name string } func GetAnthropicModelAliases(availableCursorIDs []string) []AnthropicAlias { set := make(map[string]bool, len(availableCursorIDs)) for _, id := range availableCursorIDs { set[id] = true } staticSet := make(map[string]bool, len(cursorToAnthropicAlias)) var result []AnthropicAlias for _, a := range cursorToAnthropicAlias { if set[a.CursorID] { staticSet[a.CursorID] = true result = append(result, AnthropicAlias{ID: a.AnthropicID, Name: a.Name}) } } for _, id := range availableCursorIDs { if staticSet[id] { continue } if alias, ok := generateDynamicAlias(id); ok { result = append(result, alias) } } return result }