Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
172 lines
4.8 KiB
Go
172 lines
4.8 KiB
Go
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.6-sol"):
|
|
return "gpt-5.6-sol"
|
|
case strings.Contains(normalized, "gpt-5.6-terra"):
|
|
return "gpt-5.6-terra"
|
|
case strings.Contains(normalized, "gpt-5.6-luna"):
|
|
return "gpt-5.6-luna"
|
|
case normalized == "gpt-5.6":
|
|
return "gpt-5.6-sol"
|
|
case strings.HasPrefix(normalized, "gpt-5.6-"):
|
|
suffix := strings.TrimPrefix(normalized, "gpt-5.6-")
|
|
if suffix == "max" || isKnownCodexModelSuffix(suffix) {
|
|
return "gpt-5.6-sol"
|
|
}
|
|
return ""
|
|
case strings.Contains(normalized, "gpt-5.5-pro"):
|
|
return "gpt-5.5-pro"
|
|
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 ""
|
|
}
|
|
}
|
|
|
|
// isOpenAIGPT56Model 判断是否 GPT-5.6 系列模型;入参可为原始模型名
|
|
// (含大小写/路径/后缀变体)或已归一化的基名,两者均能正确识别。
|
|
func isOpenAIGPT56Model(model string) bool {
|
|
normalized := canonicalizeOpenAIModelAliasSpelling(model)
|
|
if normalized == "gpt-5.6" {
|
|
return true
|
|
}
|
|
if suffix, ok := strings.CutPrefix(normalized, "gpt-5.6-"); ok && (suffix == "max" || isKnownCodexModelSuffix(suffix)) {
|
|
return true
|
|
}
|
|
for _, prefix := range []string{"gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"} {
|
|
if normalized == prefix || strings.HasPrefix(normalized, prefix+"-") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
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 ""
|
|
}
|