| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- package xai
- import (
- "encoding/json"
- "strconv"
- "strings"
- "time"
- )
- // GrokQuotaSignalMaxAge bounds how long a grok-4.5 Responses window can
- // influence SuperGrok vs Heavy inference.
- const GrokQuotaSignalMaxAge = 24 * time.Hour
- const (
- grok45ResponsesModel = "grok-4.5"
- grokHeavyQuotaRequestLimit int64 = 8_300
- grokHeavyQuotaTokenLimit int64 = 53_000_000
- )
- // MapJWTSubscriptionTier maps prod_auth.SubscriptionTier numeric JWT claims
- // to stable snake_case keys used by Grok Build / Mixpanel.
- func MapJWTSubscriptionTier(tier uint64) string {
- switch tier {
- case 0:
- return "free"
- case 1:
- return "supergrok"
- case 2:
- return "x_basic"
- case 3:
- return "x_premium"
- case 4:
- return "x_premium_plus"
- case 5:
- return "supergrok_heavy"
- case 6:
- return "supergrok_lite"
- case 7:
- return "supergrok_plus"
- default:
- return strconv.FormatUint(tier, 10)
- }
- }
- // NormalizeSubscriptionTier canonicalizes display names, /user strings, and
- // JWT-derived keys onto the same snake_case identifiers.
- func NormalizeSubscriptionTier(raw string) string {
- t := strings.ToLower(strings.TrimSpace(raw))
- t = strings.ReplaceAll(t, "-", "_")
- t = strings.Join(strings.Fields(t), "_")
- switch t {
- case "free", "grok_free", "grokfree", "free_tier", "freetier", "grok_basic", "grokbasic":
- return "free"
- case "supergrok", "grokpro":
- return "supergrok"
- case "supergrok_lite", "supergroklite":
- return "supergrok_lite"
- case "supergrok_heavy", "supergrokheavy":
- return "supergrok_heavy"
- case "supergrok_pro", "supergrokpro":
- return "supergrok_pro"
- case "supergrok_plus", "supergrokplus":
- return "supergrok_plus"
- case "x_basic", "xbasic", "basic":
- return "x_basic"
- case "x_premium", "xpremium":
- return "x_premium"
- case "x_premium_plus", "xpremiumplus", "x_premium+":
- return "x_premium_plus"
- default:
- return t
- }
- }
- // SubscriptionTierFromJWT decodes an access token payload (no signature check)
- // and maps the numeric or string `tier` claim.
- func SubscriptionTierFromJWT(jwt string) string {
- claims := DecodeJWTClaims(jwt)
- if claims == nil {
- return ""
- }
- raw, ok := claims["tier"]
- if !ok || raw == nil {
- return ""
- }
- switch v := raw.(type) {
- case float64:
- if v < 0 {
- return ""
- }
- return MapJWTSubscriptionTier(uint64(v))
- case json.Number:
- n, err := v.Int64()
- if err != nil || n < 0 {
- return NormalizeSubscriptionTier(v.String())
- }
- return MapJWTSubscriptionTier(uint64(n))
- case string:
- trimmed := strings.TrimSpace(v)
- if trimmed == "" {
- return ""
- }
- if n, err := strconv.ParseUint(trimmed, 10, 64); err == nil {
- return MapJWTSubscriptionTier(n)
- }
- return NormalizeSubscriptionTier(trimmed)
- default:
- return ""
- }
- }
- // CanonicalGrokPlan resolves SuperGrok vs Heavy when the provider label is
- // ambiguous (SuperGrokPro). JWT numeric claims are applied by the caller first.
- // Monthly $150/$1500 limits still win when present.
- // Rate-limit windows are only used when they came from grok-4.5 Responses.
- func CanonicalGrokPlan(monthlyLimitCents *float64, subscriptionTier string, quota *QuotaSnapshot) string {
- if plan := resolvePlan(monthlyLimitCents); plan != "" {
- return NormalizeSubscriptionTier(plan)
- }
- normalized := NormalizeSubscriptionTier(subscriptionTier)
- switch normalized {
- case "free", "x_basic":
- return "free"
- case "supergrok_heavy":
- return "supergrok_heavy"
- case "supergrok_lite":
- return "supergrok_lite"
- case "supergrok_plus":
- return "supergrok_plus"
- }
- if isAmbiguousGrokPaidPlan(normalized) {
- if hint := Grok45ResponsesPlanHint(quota, time.Time{}); hint != "" {
- return hint
- }
- return "supergrok"
- }
- return ""
- }
- func isAmbiguousGrokPaidPlan(normalized string) bool {
- switch normalized {
- case "supergrok", "supergrok_pro", "paid", "pro":
- return true
- default:
- return false
- }
- }
- // IsGrok45ResponsesQuotaModel reports whether model is the grok-4.5 Responses
- // id (or a dated grok-4.5-* variant). Empty and other families are false.
- func IsGrok45ResponsesQuotaModel(model string) bool {
- m := strings.ToLower(strings.TrimSpace(StripGrokProviderPrefix(model)))
- return m == grok45ResponsesModel || strings.HasPrefix(m, grok45ResponsesModel+"-")
- }
- // Grok45ResponsesPlanHint returns SuperGrok / Heavy inferred from a grok-4.5
- // Responses window. Other models' limits are ignored.
- func Grok45ResponsesPlanHint(quota *QuotaSnapshot, now time.Time) string {
- if quota == nil {
- return ""
- }
- if plan := NormalizeSubscriptionTier(quota.PlanFrom45Responses); plan == "supergrok" || plan == "supergrok_heavy" {
- if isQuotaTimestampFresh(quota.PlanFrom45ResponsesAt, now) {
- return plan
- }
- }
- if !IsGrok45ResponsesQuotaModel(quota.Model) || !IsQuotaSnapshotFresh(quota, now) {
- return ""
- }
- if quotaLooksLikeGrokHeavy(quota) {
- return "supergrok_heavy"
- }
- return ""
- }
- // ApplyGrok45ResponsesPlanSignal records a grok-4.5 Heavy/SuperGrok hint, or
- // copies the previous 4.5 hint when this observation is a different model.
- func (s *QuotaSnapshot) ApplyGrok45ResponsesPlanSignal(prev *QuotaSnapshot) {
- if s == nil {
- return
- }
- observedAt := firstNonEmptyQuotaTime(s.LastHeadersSeenAt, s.UpdatedAt)
- if IsGrok45ResponsesQuotaModel(s.Model) && quotaHasLimitWindow(s) {
- if quotaLooksLikeGrokHeavy(s) {
- s.PlanFrom45Responses = "supergrok_heavy"
- s.PlanFrom45ResponsesAt = observedAt
- return
- }
- s.PlanFrom45Responses = "supergrok"
- s.PlanFrom45ResponsesAt = observedAt
- return
- }
- if prev != nil && strings.TrimSpace(prev.PlanFrom45Responses) != "" {
- s.PlanFrom45Responses = prev.PlanFrom45Responses
- s.PlanFrom45ResponsesAt = prev.PlanFrom45ResponsesAt
- }
- }
- // QuotaSnapshotObservedAt prefers LastHeadersSeenAt over UpdatedAt so a later
- // snapshot rewrite cannot refresh a stale Heavy window.
- func QuotaSnapshotObservedAt(snapshot *QuotaSnapshot) (time.Time, bool) {
- if snapshot == nil {
- return time.Time{}, false
- }
- return parseQuotaTimestamp(firstNonEmptyQuotaTime(snapshot.LastHeadersSeenAt, snapshot.UpdatedAt))
- }
- // IsQuotaSnapshotFresh reports whether a quota signal is recent enough to
- // distinguish SuperGrok from Heavy.
- func IsQuotaSnapshotFresh(snapshot *QuotaSnapshot, now time.Time) bool {
- observedAt, ok := QuotaSnapshotObservedAt(snapshot)
- if !ok {
- return false
- }
- return isTimeFresh(observedAt, now)
- }
- func isQuotaTimestampFresh(raw string, now time.Time) bool {
- parsed, ok := parseQuotaTimestamp(raw)
- if !ok {
- return false
- }
- return isTimeFresh(parsed, now)
- }
- func parseQuotaTimestamp(raw string) (time.Time, bool) {
- raw = strings.TrimSpace(raw)
- if raw == "" {
- return time.Time{}, false
- }
- parsed, err := time.Parse(time.RFC3339, raw)
- if err != nil {
- return time.Time{}, false
- }
- return parsed, true
- }
- func isTimeFresh(observedAt, now time.Time) bool {
- if now.IsZero() {
- now = time.Now()
- }
- age := now.Sub(observedAt)
- return age <= GrokQuotaSignalMaxAge && age >= -5*time.Minute
- }
- func quotaHasLimitWindow(quota *QuotaSnapshot) bool {
- if quota == nil {
- return false
- }
- if quota.Requests != nil && quota.Requests.Limit != nil {
- return true
- }
- return quota.Tokens != nil && quota.Tokens.Limit != nil
- }
- func quotaLooksLikeGrokHeavy(quota *QuotaSnapshot) bool {
- if quota == nil {
- return false
- }
- var requestLimit, tokenLimit int64
- if quota.Requests != nil && quota.Requests.Limit != nil {
- requestLimit = *quota.Requests.Limit
- }
- if quota.Tokens != nil && quota.Tokens.Limit != nil {
- tokenLimit = *quota.Tokens.Limit
- }
- return requestLimit >= grokHeavyQuotaRequestLimit || tokenLimit >= grokHeavyQuotaTokenLimit
- }
- func firstNonEmptyQuotaTime(values ...string) string {
- for _, value := range values {
- if strings.TrimSpace(value) != "" {
- return strings.TrimSpace(value)
- }
- }
- return ""
- }
|