mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-01 15:02:58 +08:00
fix(grok): clear golangci-lint findings on complete-integration branch
Check Close/CloseNow errors, drop unused helpers and dead constants, lowercase ST1005 error strings, and stop discarding unwrap status as an unused assignment so CI golangci-lint passes.
This commit is contained in:
@@ -81,7 +81,7 @@ func (h *OpenAIGatewayHandler) GrokRealtime(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.CloseNow()
|
||||
defer func() { _ = conn.CloseNow() }()
|
||||
|
||||
model := c.Query("model")
|
||||
if strings.TrimSpace(model) == "" {
|
||||
|
||||
@@ -1359,26 +1359,6 @@ func (a *Account) GetGrokBaseURLOr(defaultBaseURL string) string {
|
||||
return defaultBaseURL
|
||||
}
|
||||
|
||||
func isOfficialGrokAPIBaseURL(raw string) bool {
|
||||
return isOfficialGrokBaseURL(raw, xai.DefaultBaseURL)
|
||||
}
|
||||
|
||||
func isOfficialGrokBaseURL(raw, expected string) bool {
|
||||
parsed, err := url.Parse(strings.TrimSpace(raw))
|
||||
if err != nil || parsed == nil || parsed.Opaque != "" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" {
|
||||
return false
|
||||
}
|
||||
want, err := url.Parse(expected)
|
||||
if err != nil || !strings.EqualFold(parsed.Scheme, want.Scheme) || !strings.EqualFold(parsed.Hostname(), want.Hostname()) {
|
||||
return false
|
||||
}
|
||||
if port := parsed.Port(); port != "" && port != "443" {
|
||||
return false
|
||||
}
|
||||
path := strings.TrimRight(parsed.Path, "/")
|
||||
return path == "" || path == strings.TrimRight(want.Path, "/")
|
||||
}
|
||||
|
||||
// GetGrokMediaBaseURL selects the upstream used by Grok Imagine APIs.
|
||||
// The subscription CLI gateway enforces a small request-body limit that
|
||||
// rejects large Base64 media payloads, so OAuth media leaves for api.x.ai
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type accountSchedulingThresholdSnapshotCleaner interface {
|
||||
ClearAccountSchedulingThresholdSnapshots(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
func clearAccountSchedulingThresholdSnapshots(ctx context.Context, repo AccountRepository, id int64) error {
|
||||
cleaner, ok := repo.(accountSchedulingThresholdSnapshotCleaner)
|
||||
if !ok {
|
||||
return fmt.Errorf("account repository does not support account scheduling threshold snapshot cleanup")
|
||||
}
|
||||
return cleaner.ClearAccountSchedulingThresholdSnapshots(ctx, id)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
//go:build unit
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type missingThresholdSnapshotCleanerRepo struct {
|
||||
AccountRepository
|
||||
}
|
||||
|
||||
func TestClearAccountSchedulingThresholdSnapshots_RequiresRepositorySupport(t *testing.T) {
|
||||
err := clearAccountSchedulingThresholdSnapshots(context.Background(), missingThresholdSnapshotCleanerRepo{}, 1)
|
||||
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "does not support account scheduling threshold snapshot cleanup")
|
||||
}
|
||||
@@ -903,23 +903,23 @@ func (s *AccountTestService) grokTestAccessToken(ctx context.Context, account *A
|
||||
switch account.Type {
|
||||
case AccountTypeOAuth:
|
||||
if s.grokTokenProvider == nil {
|
||||
return "", fmt.Errorf("Grok token provider not configured")
|
||||
return "", fmt.Errorf("grok token provider not configured")
|
||||
}
|
||||
// Manual tests skip production scheduling eligibility so paused/rate-limited
|
||||
// accounts can still be probed by admins (same as Codex/OpenAI tests).
|
||||
token, err := s.grokTokenProvider.GetAccessTokenForManualTest(ctx, account)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to get Grok access token: %s", err.Error())
|
||||
return "", fmt.Errorf("failed to get grok access token: %s", err.Error())
|
||||
}
|
||||
return token, nil
|
||||
case AccountTypeAPIKey:
|
||||
authToken := strings.TrimSpace(account.GetCredential("api_key"))
|
||||
if authToken == "" {
|
||||
return "", fmt.Errorf("Grok API key is missing")
|
||||
return "", fmt.Errorf("grok api key is missing")
|
||||
}
|
||||
return authToken, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Unsupported Grok account type: %s", account.Type)
|
||||
return "", fmt.Errorf("unsupported grok account type: %s", account.Type)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,7 @@ import (
|
||||
// Soft-gate applies only to *explicit* free OAuth (subscription_tier/plan_type ==
|
||||
// "free"). Media/cache free detection uses isKnownGrokFreeAccount instead.
|
||||
// Admin paths (QueryQuota / import probe) never call this filter.
|
||||
|
||||
const (
|
||||
defaultGrokFreeQuotaTokenLimit int64 = 500_000
|
||||
defaultGrokFreeQuotaSoftGatePercent = 95
|
||||
defaultGrokFreeQuotaWindowHours = 24
|
||||
)
|
||||
// Defaults live on config.Gateway.Grok (see config load defaults / tests).
|
||||
|
||||
type GrokFreeQuotaPolicy struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
@@ -525,18 +525,6 @@ func isOfficialGrokVideoStatusDone(statusBody []byte) bool {
|
||||
return strings.EqualFold(strings.TrimSpace(gjson.GetBytes(statusBody, "status").String()), "done")
|
||||
}
|
||||
|
||||
// extractGrokVideoStatusContentURL returns official video.url when present.
|
||||
// Kept for content/proxy helpers that still accept rewritten proxy paths after rewrite.
|
||||
func extractGrokVideoStatusContentURL(body []byte) string {
|
||||
if len(body) == 0 || !gjson.ValidBytes(body) {
|
||||
return ""
|
||||
}
|
||||
if u := strings.TrimSpace(gjson.GetBytes(body, "video.url").String()); u != "" {
|
||||
return u
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExtractGrokVideoBillingFromStatusBody builds usage units from an official done status.
|
||||
// Field priority (official docs):
|
||||
// - duration: video.duration (seconds)
|
||||
|
||||
@@ -119,7 +119,7 @@ func (s *GrokQuotaService) syncGrokObservedModels(ctx context.Context, account *
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestAccountGrokNeedsReauth(t *testing.T) {
|
||||
}))
|
||||
require.True(t, accountGrokNeedsReauth(&Account{
|
||||
Status: StatusError,
|
||||
ErrorMessage: grokSpendingLimitErrorMessage,
|
||||
ErrorMessage: "Grok spending limit reached; reauthorize or wait for billing reset",
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -10,11 +9,8 @@ import (
|
||||
// Spending-limit is recoverable at the end of the observed billing period.
|
||||
// When no billing snapshot is available, use a short probe rather than
|
||||
// fabricating a 24h boundary from the error arrival time.
|
||||
const grokSpendingLimitCooldown = 24 * time.Hour
|
||||
const grokSpendingLimitProbeCooldown = 10 * time.Minute
|
||||
|
||||
const grokSpendingLimitErrorMessage = "Grok spending limit reached; reauthorize or wait for billing reset"
|
||||
|
||||
func grokSpendingLimitResetAt(account *Account, now time.Time) time.Time {
|
||||
if account != nil {
|
||||
if billing, err := grokBillingSnapshotFromExtra(account.Extra); err == nil && billing != nil {
|
||||
@@ -28,29 +24,6 @@ func grokSpendingLimitResetAt(account *Account, now time.Time) time.Time {
|
||||
return now.Add(grokSpendingLimitProbeCooldown)
|
||||
}
|
||||
|
||||
// markGrokSpendingLimitReauth applies a long temp-unsched cool and durable
|
||||
// SetError so ops sees reauth-required without wiping OAuth credentials.
|
||||
func (s *OpenAIGatewayService) markGrokSpendingLimitReauth(ctx context.Context, account *Account) {
|
||||
if s == nil || account == nil || account.IsPoolMode() {
|
||||
return
|
||||
}
|
||||
s.tempUnscheduleGrok(ctx, account, grokSpendingLimitCooldown, "grok spending limit")
|
||||
if s.accountRepo == nil {
|
||||
return
|
||||
}
|
||||
stateCtx, cancel := openAIAccountStateContext(ctx)
|
||||
defer cancel()
|
||||
if err := s.accountRepo.SetError(stateCtx, account.ID, grokSpendingLimitErrorMessage); err != nil {
|
||||
slog.Warn("grok_spending_limit_set_error_failed", "account_id", account.ID, "error", err)
|
||||
}
|
||||
// Soft flag in extra for UI / usage fetcher without requiring status poll.
|
||||
_ = s.accountRepo.UpdateExtra(stateCtx, account.ID, map[string]any{
|
||||
"grok_needs_reauth": true,
|
||||
"grok_needs_reauth_reason": "spending_limit",
|
||||
"grok_needs_reauth_at": time.Now().UTC().Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
// clearGrokNeedsReauthExtra drops the soft reauth flag after successful refresh
|
||||
// or reauth. Best-effort; never fails the request path.
|
||||
func clearGrokNeedsReauthExtra(ctx context.Context, repo AccountRepository, accountID int64) {
|
||||
|
||||
@@ -50,9 +50,8 @@ type GrokUpstreamFailureDecision struct {
|
||||
}
|
||||
|
||||
var (
|
||||
reGrokTokenPair = regexp.MustCompile(`(?i)tokens?\s*(?:\(actual\s*/\s*limit\))?\s*[:=]?\s*(\d+)\s*/\s*(\d+)`)
|
||||
reGrokModelFor = regexp.MustCompile(`(?i)(?:for\s+model|model|模型)\s*[::]?\s*([a-z0-9][a-z0-9._-]{2,80})`)
|
||||
reGrokResetsWindow = regexp.MustCompile(`(?i)resets?\s+over\s+a\s+rolling\s+(\d+)\s*-\s*hour`)
|
||||
reGrokTokenPair = regexp.MustCompile(`(?i)tokens?\s*(?:\(actual\s*/\s*limit\))?\s*[:=]?\s*(\d+)\s*/\s*(\d+)`)
|
||||
reGrokModelFor = regexp.MustCompile(`(?i)(?:for\s+model|model|模型)\s*[::]?\s*([a-z0-9][a-z0-9._-]{2,80})`)
|
||||
)
|
||||
|
||||
// classifyGrokUpstreamFailure decides cooldown/failover from status + body.
|
||||
@@ -166,11 +165,10 @@ func classifyGrokUpstreamFailure(statusCode int, responseBody []byte, requestedM
|
||||
}
|
||||
|
||||
func grokUpstreamErrorCorpus(statusCode int, responseBody []byte) (text, code, low string) {
|
||||
_ = statusCode // classifier already has the transport status; corpus is body-only
|
||||
raw := strings.TrimSpace(string(responseBody))
|
||||
if unwrappedStatus, unwrappedBody, ok := unwrapGrokUpstreamErrorText(raw); ok {
|
||||
if statusCode <= 0 {
|
||||
statusCode = unwrappedStatus
|
||||
}
|
||||
// Strip "upstream status NNN: ..." prefixes so free-usage / quota language is visible.
|
||||
if _, unwrappedBody, ok := unwrapGrokUpstreamErrorText(raw); ok {
|
||||
raw = unwrappedBody
|
||||
}
|
||||
text = raw
|
||||
|
||||
@@ -14,10 +14,6 @@ import (
|
||||
// tests. Current requests use the pinned default UA from this package.
|
||||
const grokUpstreamUserAgent = "sub2api-grok/1.0"
|
||||
|
||||
// defaultBrowserLikeUpstreamUserAgent remains available for non-Grok OpenAI-like
|
||||
// fingerprint templates that historically shared this constant.
|
||||
const defaultBrowserLikeUpstreamUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
|
||||
|
||||
// Fixed CLI identity aliases — single source of truth is internal/pkg/xai.
|
||||
const (
|
||||
grokClientVersionHeader = xai.CLIStableVersion
|
||||
|
||||
Reference in New Issue
Block a user