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
349 lines
10 KiB
Go
349 lines
10 KiB
Go
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
|
|
}
|
|
}
|