package service import ( "encoding/json" "net/http" "strings" "github.com/gin-gonic/gin" ) const ( grokMissingUsageErrorCode = "grok_missing_usage" grokMissingUsageMessage = "xAI upstream returned a successful chat completion without billable usage" ) // hasBillableGrokChatUsage stays aligned with the aggregate token buckets used // to account for chat completions. Detail fields alone do not prove that the // successful response can be settled safely. func hasBillableGrokChatUsage(usage OpenAIUsage) bool { return usage.InputTokens > 0 || usage.OutputTokens > 0 || usage.CacheCreationInputTokens > 0 || usage.CacheReadInputTokens > 0 } // requiresBillableGrokChatUsage identifies Grok traffic by both account // platform and model identity. Grok models may be served through generic // OpenAI-compatible accounts, so account.Platform alone is not a safe billing // boundary. func requiresBillableGrokChatUsage(account *Account, models ...string) bool { if account != nil && account.Platform == PlatformGrok { return true } for _, model := range models { normalized := strings.ToLower(strings.TrimSpace(model)) if separator := strings.LastIndex(normalized, "/"); separator >= 0 { normalized = strings.TrimSpace(normalized[separator+1:]) } if normalized == "grok" || strings.HasPrefix(normalized, "grok-") { return true } } return false } func newGrokMissingUsageFailoverError(c *gin.Context, account *Account, upstreamRequestID string) *UpstreamFailoverError { accountID := int64(0) accountName := "" if account != nil { accountID = account.ID accountName = account.Name } setOpsUpstreamError(c, http.StatusBadGateway, grokMissingUsageMessage, "") appendOpsUpstreamError(c, OpsUpstreamErrorEvent{ Platform: PlatformGrok, AccountID: accountID, AccountName: accountName, UpstreamStatusCode: http.StatusBadGateway, UpstreamRequestID: strings.TrimSpace(upstreamRequestID), Kind: "failover", Message: grokMissingUsageMessage, }) body, _ := json.Marshal(gin.H{ "error": gin.H{ "type": "upstream_error", "code": grokMissingUsageErrorCode, "message": grokMissingUsageMessage, }, }) headers := http.Header{} if requestID := strings.TrimSpace(upstreamRequestID); requestID != "" { headers.Set("x-request-id", requestID) } return &UpstreamFailoverError{ StatusCode: http.StatusBadGateway, ResponseBody: body, ResponseHeaders: headers, } }