Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
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

This commit is contained in:
李建琦
2026-08-21 18:30:13 +08:00
commit 6d655c9903
3584 changed files with 1270640 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
package service
import (
"log/slog"
"sort"
"strings"
"github.com/Wei-Shaw/sub2api/internal/pkg/xai"
)
// Canonical video price family keys used in groups.video_model_prices JSONB.
const (
VideoPriceFamilyGrokImagineVideo = "grok-imagine-video"
VideoPriceFamilyGrokImagineVideo15 = "grok-imagine-video-1.5"
)
// CanonicalGrokImagineVideoPriceFamily normalizes model aliases / preview / legacy
// IDs onto the price-family keys stored in video_model_prices.
func CanonicalGrokImagineVideoPriceFamily(model string) string {
if model == "" {
return ""
}
// Prefer shared xAI helper for known aliases. Keep future native Imagine
// models distinct so operators can assign them independent prices.
if c := xai.CanonicalImagineVideoModel(model); c != "" {
switch c {
case xai.DefaultImagineVideo15Model:
return VideoPriceFamilyGrokImagineVideo15
case xai.DefaultImagineVideoModel:
return VideoPriceFamilyGrokImagineVideo
}
if strings.HasPrefix(c, "grok-imagine-video-") {
return c
}
}
m := strings.ToLower(strings.TrimSpace(model))
for _, prefix := range []string{"xai/", "x-ai/", "grok/"} {
if strings.HasPrefix(m, prefix) {
m = strings.TrimPrefix(m, prefix)
break
}
}
switch {
case m == "grok-imagine-video-1.5" || m == "grok-imagine-video-1.5-preview" ||
m == "grok-video-1.5" || strings.Contains(m, "video-1.5"):
return VideoPriceFamilyGrokImagineVideo15
case m == "grok-imagine-video" || m == "grok-imagine-video-preview" ||
m == "grok-video" || m == "grok-video-latest":
return VideoPriceFamilyGrokImagineVideo
default:
return ""
}
}
// NormalizeVideoModelPrices cleans and canonicalizes a per-model resolution map.
// Keys become price families; tiers use 480p/720p/1080p. Negative prices dropped.
//
// Model keys are walked in sorted order rather than in Go map order: several
// aliases can canonicalize onto the same family, and an unordered walk would
// make the winning price for a conflicting tier vary between processes.
// Unrecognized tiers are dropped with a warning instead of silently collapsing
// into the 480p bucket.
func NormalizeVideoModelPrices(in map[string]map[string]float64) map[string]map[string]float64 {
if len(in) == 0 {
return nil
}
modelKeys := make([]string, 0, len(in))
for modelKey := range in {
modelKeys = append(modelKeys, modelKey)
}
sort.Strings(modelKeys)
out := make(map[string]map[string]float64)
for _, modelKey := range modelKeys {
tierPrices := in[modelKey]
if len(tierPrices) == 0 {
continue
}
family := CanonicalGrokImagineVideoPriceFamily(modelKey)
if family == "" {
key := strings.ToLower(strings.TrimSpace(modelKey))
switch key {
case VideoPriceFamilyGrokImagineVideo, VideoPriceFamilyGrokImagineVideo15:
family = key
default:
if key == "" {
continue
}
family = key
}
}
normalizedTiers := out[family]
if normalizedTiers == nil {
normalizedTiers = make(map[string]float64)
}
tierKeys := make([]string, 0, len(tierPrices))
for tierKey := range tierPrices {
tierKeys = append(tierKeys, tierKey)
}
sort.Strings(tierKeys)
for _, tierKey := range tierKeys {
price := tierPrices[tierKey]
if price < 0 {
continue
}
tier, ok := LookupVideoBillingResolution(tierKey)
if !ok {
slog.Warn("video_model_prices_unknown_resolution_dropped",
"model_key", modelKey,
"family", family,
"resolution", tierKey)
continue
}
if existing, exists := normalizedTiers[tier]; exists && existing != price {
slog.Warn("video_model_prices_conflicting_tier_price",
"model_key", modelKey,
"family", family,
"resolution", tier,
"previous_price", existing,
"price", price)
}
normalizedTiers[tier] = price
}
if len(normalizedTiers) > 0 {
out[family] = normalizedTiers
}
}
if len(out) == 0 {
return nil
}
return out
}
// LookupVideoModelPrice returns a per-second price from a model×resolution map, or nil.
func LookupVideoModelPrice(prices map[string]map[string]float64, model, resolution string) *float64 {
if len(prices) == 0 {
return nil
}
family := CanonicalGrokImagineVideoPriceFamily(model)
if family == "" {
family = strings.ToLower(strings.TrimSpace(model))
}
if family == "" {
return nil
}
tierPrices, ok := prices[family]
if !ok || len(tierPrices) == 0 {
return nil
}
tier := NormalizeVideoBillingResolutionOrDefault(resolution)
if price, ok := tierPrices[tier]; ok {
p := price
return &p
}
return nil
}