| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- package service
- import (
- "encoding/json"
- "fmt"
- "net/http"
- "strings"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/pkg/xai"
- )
- const grokQuotaSnapshotExtraKey = "grok_usage_snapshot"
- type GrokQuotaFetcher struct{}
- func NewGrokQuotaFetcher() *GrokQuotaFetcher {
- return &GrokQuotaFetcher{}
- }
- func (f *GrokQuotaFetcher) BuildUsageInfo(account *Account) *UsageInfo {
- now := time.Now()
- usage := &UsageInfo{
- Source: "passive",
- UpdatedAt: &now,
- GrokFreeTokenLimit: xai.GrokFreeRolling24hTokenLimit,
- }
- if account == nil {
- usage.ErrorCode = "quota_unknown"
- usage.Error = "Grok quota is unknown until billing is probed or an upstream response includes xAI rate-limit headers"
- return usage
- }
- billing, _ := grokBillingSnapshotFromExtra(account.Extra)
- snapshot, err := grokQuotaSnapshotFromExtra(account.Extra)
- activeProbeClearsForbidden := newerSuccessfulGrokActiveProbeClearsBillingForbidden(billing, snapshot)
- if billing != nil {
- usage.GrokBilling = billing
- applyGrokBillingProgressWindows(usage, billing, now)
- if billing.Plan != "" {
- usage.SubscriptionTier = billing.Plan
- usage.SubscriptionTierRaw = billing.Plan
- }
- if parsedAt, parseErr := time.Parse(time.RFC3339, billing.UpdatedAt); parseErr == nil {
- usage.UpdatedAt = &parsedAt
- }
- if billing.FetchedAt != "" {
- usage.GrokLastQuotaProbeAt = billing.FetchedAt
- }
- usage.GrokQuotaSnapshotState = "billing_observed"
- usage.GrokLastStatusCode = billing.StatusCode
- switch billing.StatusCode {
- case 401:
- usage.NeedsReauth = true
- usage.ErrorCode = "unauthenticated"
- case 403:
- usage.IsForbidden = true
- usage.ForbiddenType = "forbidden"
- usage.ErrorCode = "forbidden"
- case 429:
- usage.ErrorCode = "rate_limited"
- }
- // Official weekly/monthly progress clears the "unknown until headers" state.
- if usage.ErrorCode == "quota_unknown" && (usage.SevenDay != nil || usage.ThirtyDay != nil) {
- usage.ErrorCode = ""
- if strings.Contains(strings.ToLower(usage.Error), "unknown until") ||
- strings.Contains(strings.ToLower(usage.Error), "no xai quota headers") {
- usage.Error = ""
- }
- }
- }
- if err != nil || snapshot == nil {
- applyGrokCredentialUsageFallback(usage, account, billing, nil)
- if billing == nil {
- usage.ErrorCode = "quota_unknown"
- usage.Error = "Grok quota is unknown until billing is probed or an upstream response includes xAI rate-limit headers"
- }
- return usage
- }
- if parsedAt, parseErr := time.Parse(time.RFC3339, snapshot.UpdatedAt); parseErr == nil {
- if billing == nil || usage.UpdatedAt == nil || parsedAt.After(*usage.UpdatedAt) {
- usage.UpdatedAt = &parsedAt
- }
- }
- usage.GrokRequestQuota = snapshot.Requests
- usage.GrokTokenQuota = snapshot.Tokens
- usage.GrokRetryAfterSeconds = snapshot.RetryAfterSeconds
- if usage.SubscriptionTier == "" {
- usage.SubscriptionTier = snapshot.SubscriptionTier
- usage.SubscriptionTierRaw = snapshot.SubscriptionTier
- }
- if usage.GrokEntitlementStatus == "" {
- usage.GrokEntitlementStatus = snapshot.EntitlementStatus
- }
- if usage.GrokLastQuotaProbeAt == "" {
- usage.GrokLastQuotaProbeAt = snapshot.LastProbeAt
- }
- usage.GrokLastHeadersSeenAt = snapshot.LastHeadersSeenAt
- if activeProbeClearsForbidden {
- usage.IsForbidden = false
- usage.ForbiddenType = ""
- usage.ErrorCode = ""
- usage.GrokLastQuotaProbeAt = snapshot.LastProbeAt
- usage.GrokLastStatusCode = snapshot.StatusCode
- } else if snapshot.StatusCode >= http.StatusBadRequest || usage.GrokLastStatusCode == 0 {
- usage.GrokLastStatusCode = snapshot.StatusCode
- }
- if snapshot.HasObservedHeaders() {
- if usage.GrokQuotaSnapshotState == "" {
- usage.GrokQuotaSnapshotState = "observed"
- }
- } else if billing == nil {
- usage.GrokQuotaSnapshotState = "no_headers"
- usage.ErrorCode = "quota_unknown"
- usage.Error = "No xAI quota headers observed on the latest Grok probe"
- }
- if usage.ErrorCode == "" {
- switch snapshot.StatusCode {
- case 401:
- usage.NeedsReauth = true
- usage.ErrorCode = "unauthenticated"
- case 403:
- usage.IsForbidden = true
- usage.ForbiddenType = "forbidden"
- usage.ErrorCode = "forbidden"
- if usage.GrokEntitlementStatus == "" {
- usage.GrokEntitlementStatus = "forbidden"
- }
- case 429:
- usage.ErrorCode = "rate_limited"
- }
- }
- if accountGrokNeedsReauth(account) {
- usage.NeedsReauth = true
- if usage.ErrorCode == "" {
- usage.ErrorCode = "spending_limit"
- }
- }
- applyGrokCredentialUsageFallback(usage, account, billing, snapshot)
- if activeProbeClearsForbidden && strings.TrimSpace(snapshot.EntitlementStatus) == "" &&
- strings.EqualFold(strings.TrimSpace(usage.GrokEntitlementStatus), "forbidden") {
- usage.GrokEntitlementStatus = ""
- }
- return usage
- }
- func newerSuccessfulGrokActiveProbeClearsBillingForbidden(billing *xai.BillingSummary, snapshot *xai.QuotaSnapshot) bool {
- if billing == nil || billing.StatusCode != http.StatusForbidden || snapshot == nil ||
- snapshot.StatusCode != http.StatusOK || strings.TrimSpace(snapshot.ObservationSource) != "active_probe" {
- return false
- }
- billingAt, billingOK := firstGrokObservationTime(billing.UpdatedAt, billing.FetchedAt)
- probeAt, probeOK := firstGrokObservationTime(snapshot.LastProbeAt, snapshot.UpdatedAt)
- // Both snapshots use second precision, so a billing request followed by the
- // active probe in the same refresh can legitimately have equal timestamps.
- return billingOK && probeOK && !probeAt.Before(billingAt)
- }
- func firstGrokObservationTime(values ...string) (time.Time, bool) {
- for _, value := range values {
- parsedAt, err := time.Parse(time.RFC3339, strings.TrimSpace(value))
- if err == nil {
- return parsedAt, true
- }
- }
- return time.Time{}, false
- }
- func applyGrokCredentialUsageFallback(usage *UsageInfo, account *Account, billing *xai.BillingSummary, snapshot *xai.QuotaSnapshot) {
- if usage == nil || account == nil {
- return
- }
- if usage.GrokEntitlementStatus == "" {
- usage.GrokEntitlementStatus = strings.TrimSpace(account.GetCredential("entitlement_status"))
- }
- applyGrokResolvedSubscriptionTier(usage, account, billing, snapshot)
- }
- func applyGrokResolvedSubscriptionTier(usage *UsageInfo, account *Account, billing *xai.BillingSummary, snapshot *xai.QuotaSnapshot) {
- if usage == nil || account == nil {
- return
- }
- if jwtTier := xai.SubscriptionTierFromJWT(account.GetCredential("access_token")); jwtTier != "" {
- usage.SubscriptionTier = jwtTier
- usage.SubscriptionTierRaw = jwtTier
- return
- }
- signal := strings.TrimSpace(account.GetCredential("subscription_tier"))
- if signal == "" && snapshot != nil {
- signal = strings.TrimSpace(snapshot.SubscriptionTier)
- }
- if signal == "" && billing != nil {
- signal = strings.TrimSpace(billing.Plan)
- }
- var limit *float64
- if billing != nil {
- limit = billing.MonthlyLimitCents
- }
- if plan := xai.CanonicalGrokPlan(limit, signal, snapshot); plan != "" {
- usage.SubscriptionTier = plan
- if usage.SubscriptionTierRaw == "" {
- usage.SubscriptionTierRaw = firstNonEmpty(signal, plan)
- }
- return
- }
- if usage.SubscriptionTier == "" && signal != "" {
- usage.SubscriptionTier = signal
- usage.SubscriptionTierRaw = signal
- }
- }
- func grokBillingSnapshotFromExtra(extra map[string]any) (*xai.BillingSummary, error) {
- if extra == nil {
- return nil, nil
- }
- raw, ok := extra[grokBillingExtraKey]
- if !ok || raw == nil {
- return nil, nil
- }
- switch snapshot := raw.(type) {
- case *xai.BillingSummary:
- return snapshot, nil
- case xai.BillingSummary:
- return &snapshot, nil
- case map[string]any:
- data, err := json.Marshal(snapshot)
- if err != nil {
- return nil, err
- }
- var out xai.BillingSummary
- if err := json.Unmarshal(data, &out); err != nil {
- return nil, err
- }
- return &out, nil
- default:
- data, err := json.Marshal(raw)
- if err != nil {
- return nil, fmt.Errorf("marshal grok billing snapshot: %w", err)
- }
- var out xai.BillingSummary
- if err := json.Unmarshal(data, &out); err != nil {
- return nil, err
- }
- return &out, nil
- }
- }
- func stampGrokQuotaSnapshotForPlan(account *Account, snapshot *xai.QuotaSnapshot, model string) {
- if snapshot == nil {
- return
- }
- if strings.TrimSpace(snapshot.Model) == "" {
- model = strings.TrimSpace(model)
- if model != "" {
- snapshot.Model = xai.ResolveGrokTextResponsesModelID(model)
- }
- }
- var prev *xai.QuotaSnapshot
- if account != nil {
- prev, _ = grokQuotaSnapshotFromExtra(account.Extra)
- }
- snapshot.ApplyGrok45ResponsesPlanSignal(prev)
- }
- func grokQuotaSnapshotFromExtra(extra map[string]any) (*xai.QuotaSnapshot, error) {
- if extra == nil {
- return nil, nil
- }
- raw, ok := extra[grokQuotaSnapshotExtraKey]
- if !ok || raw == nil {
- return nil, nil
- }
- switch snapshot := raw.(type) {
- case *xai.QuotaSnapshot:
- return snapshot, nil
- case xai.QuotaSnapshot:
- return &snapshot, nil
- case map[string]any:
- data, err := json.Marshal(snapshot)
- if err != nil {
- return nil, err
- }
- var out xai.QuotaSnapshot
- if err := json.Unmarshal(data, &out); err != nil {
- return nil, err
- }
- return &out, nil
- default:
- data, err := json.Marshal(raw)
- if err != nil {
- return nil, fmt.Errorf("marshal grok quota snapshot: %w", err)
- }
- var out xai.QuotaSnapshot
- if err := json.Unmarshal(data, &out); err != nil {
- return nil, err
- }
- return &out, nil
- }
- }
- // applyGrokBillingProgressWindows fills official weekly (seven_day) and monthly
- // (thirty_day) UsageProgress from a billing probe summary.
- func applyGrokBillingProgressWindows(usage *UsageInfo, billing *xai.BillingSummary, now time.Time) {
- if usage == nil || billing == nil {
- return
- }
- if billing.UsagePercent != nil {
- seven := &UsageProgress{Utilization: *billing.UsagePercent}
- if end, err := parseTime(strings.TrimSpace(billing.PeriodEnd)); err == nil {
- seven.ResetsAt = &end
- if sec := int(end.Sub(now).Seconds()); sec > 0 {
- seven.RemainingSeconds = sec
- }
- }
- if usage.SevenDay != nil {
- seven.WindowStats = usage.SevenDay.WindowStats
- }
- usage.SevenDay = seven
- }
- var monthlyUtil *float64
- if billing.UsedPercent != nil {
- monthlyUtil = billing.UsedPercent
- } else if billing.MonthlyLimitCents != nil && *billing.MonthlyLimitCents > 0 && billing.UsedCents != nil {
- v := (*billing.UsedCents / *billing.MonthlyLimitCents) * 100
- monthlyUtil = &v
- }
- if monthlyUtil != nil {
- thirty := &UsageProgress{Utilization: *monthlyUtil}
- endRaw := strings.TrimSpace(billing.BillingPeriodEnd)
- if endRaw == "" && billing.PeriodType == "monthly" {
- endRaw = strings.TrimSpace(billing.PeriodEnd)
- }
- if end, err := parseTime(endRaw); err == nil {
- thirty.ResetsAt = &end
- if sec := int(end.Sub(now).Seconds()); sec > 0 {
- thirty.RemainingSeconds = sec
- }
- }
- if usage.ThirtyDay != nil {
- thirty.WindowStats = usage.ThirtyDay.WindowStats
- }
- usage.ThirtyDay = thirty
- }
- }
|