package service import "strings" func lastOpenAIModelSegment(model string) string { model = strings.TrimSpace(model) if model == "" { return "" } if strings.Contains(model, "/") { parts := strings.Split(model, "/") model = parts[len(parts)-1] } return strings.TrimSpace(model) } func canonicalizeOpenAIModelAliasSpelling(model string) string { model = strings.ToLower(lastOpenAIModelSegment(model)) if model == "" { return "" } normalized := strings.ReplaceAll(model, "_", "-") normalized = strings.Join(strings.Fields(normalized), "-") for strings.Contains(normalized, "--") { normalized = strings.ReplaceAll(normalized, "--", "-") } if strings.HasPrefix(normalized, "gpt5") { normalized = "gpt-5" + strings.TrimPrefix(normalized, "gpt5") } if !strings.HasPrefix(normalized, "gpt-") && !strings.Contains(normalized, "codex") { return "" } replacements := []struct { from string to string }{ {"gpt-5.4mini", "gpt-5.4-mini"}, {"gpt-5.4nano", "gpt-5.4-nano"}, {"gpt-5.3-codexspark", "gpt-5.3-codex-spark"}, {"gpt-5.3codexspark", "gpt-5.3-codex-spark"}, {"gpt-5.3codex", "gpt-5.3-codex"}, } for _, replacement := range replacements { normalized = strings.ReplaceAll(normalized, replacement.from, replacement.to) } return normalized } func normalizeKnownOpenAICodexModel(model string) string { normalized := canonicalizeOpenAIModelAliasSpelling(model) if normalized == "" { return "" } if mapped := getNormalizedCodexModel(normalized); mapped != "" { return mapped } if strings.HasSuffix(normalized, "-openai-compact") { if mapped := getNormalizedCodexModel(strings.TrimSuffix(normalized, "-openai-compact")); mapped != "" { return mapped } } switch { case strings.Contains(normalized, "gpt-5.5"): return "gpt-5.5" case strings.Contains(normalized, "gpt-5.4-mini"): return "gpt-5.4-mini" case strings.Contains(normalized, "gpt-5.4-nano"): return "gpt-5.4-nano" case strings.Contains(normalized, "gpt-5.4"): return "gpt-5.4" case strings.Contains(normalized, "gpt-5.2"): return "gpt-5.2" case strings.Contains(normalized, "gpt-5.3-codex-spark"): return "gpt-5.3-codex-spark" case strings.Contains(normalized, "gpt-5.3-codex"): return "gpt-5.3-codex" case strings.Contains(normalized, "gpt-5.3"): return "gpt-5.3-codex" case strings.Contains(normalized, "codex"): return "gpt-5.3-codex" case strings.Contains(normalized, "gpt-5"): return "gpt-5.4" default: return "" } } func appendUsageBillingModelCandidate(candidates []string, seen map[string]struct{}, model string) []string { trimmed := strings.TrimSpace(model) if trimmed == "" { return candidates } add := func(candidate string) { candidate = strings.TrimSpace(candidate) if candidate == "" { return } key := strings.ToLower(candidate) if _, ok := seen[key]; ok { return } seen[key] = struct{}{} candidates = append(candidates, candidate) } add(trimmed) if canonical := canonicalizeOpenAIModelAliasSpelling(trimmed); canonical != "" { add(canonical) } if normalized := normalizeKnownOpenAICodexModel(trimmed); normalized != "" { add(normalized) } return candidates } func usageBillingModelCandidates(primary string, alternates ...string) []string { seen := make(map[string]struct{}, 1+len(alternates)) candidates := appendUsageBillingModelCandidate(nil, seen, primary) for _, alternate := range alternates { candidates = appendUsageBillingModelCandidate(candidates, seen, alternate) } return candidates } func firstUsageBillingModel(candidates []string) string { for _, candidate := range candidates { if trimmed := strings.TrimSpace(candidate); trimmed != "" { return trimmed } } return "" }