diff --git a/backend/internal/service/antigravity_credits_overages.go b/backend/internal/service/antigravity_credits_overages.go index ec3650859c..3c4b6f2f73 100644 --- a/backend/internal/service/antigravity_credits_overages.go +++ b/backend/internal/service/antigravity_credits_overages.go @@ -141,6 +141,10 @@ func resolveCreditsOveragesModelKey(ctx context.Context, account *Account, upstr } // shouldMarkCreditsExhausted 判断一次 credits 请求失败是否应标记为 credits 耗尽。 +// 此函数在积分注入后失败时调用(预检查注入 + attemptCreditsOveragesRetry 两条路径)。 +// - 429:积分注入后仍被限流 → 直接标记耗尽,不再检查 body 关键词。 +// - 403 等其他 4xx:检查 body 是否包含积分不足的关键词。 +// clearCreditsExhausted 会在后续成功时自动清除。 func shouldMarkCreditsExhausted(resp *http.Response, respBody []byte, reqErr error) bool { if reqErr != nil || resp == nil { return false @@ -148,12 +152,11 @@ func shouldMarkCreditsExhausted(resp *http.Response, respBody []byte, reqErr err if resp.StatusCode >= 500 || resp.StatusCode == http.StatusRequestTimeout { return false } - // 注意:不再检查 isURLLevelRateLimit。此函数仅在积分重试失败后调用, - // 如果注入 enabledCreditTypes 后仍返回 "Resource has been exhausted", - // 说明积分也已耗尽,应该标记。clearCreditsExhausted 会在后续成功时自动清除。 - if info := parseAntigravitySmartRetryInfo(respBody); info != nil { - return false + // 积分注入后仍 429 → 直接标记 + if resp.StatusCode == http.StatusTooManyRequests { + return true } + // 其他 4xx:关键词匹配(如 403 + "Insufficient credits") bodyLower := strings.ToLower(string(respBody)) for _, keyword := range creditsExhaustedKeywords { if strings.Contains(bodyLower, keyword) { diff --git a/backend/internal/service/antigravity_credits_overages_test.go b/backend/internal/service/antigravity_credits_overages_test.go index c3439be5b2..49725fb5e2 100644 --- a/backend/internal/service/antigravity_credits_overages_test.go +++ b/backend/internal/service/antigravity_credits_overages_test.go @@ -418,13 +418,13 @@ func TestShouldMarkCreditsExhausted(t *testing.T) { require.True(t, shouldMarkCreditsExhausted(resp, body, nil)) }) - t.Run("结构化限流不标记", func(t *testing.T) { + t.Run("429 结构化限流也标记(积分注入后仍 429 即为耗尽)", func(t *testing.T) { resp := &http.Response{StatusCode: http.StatusTooManyRequests} body := []byte(`{"error":{"status":"RESOURCE_EXHAUSTED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"RATE_LIMIT_EXCEEDED"},{"@type":"type.googleapis.com/google.rpc.RetryInfo","retryDelay":"0.5s"}]}}`) - require.False(t, shouldMarkCreditsExhausted(resp, body, nil)) + require.True(t, shouldMarkCreditsExhausted(resp, body, nil)) }) - t.Run("含 credits 关键词时标记", func(t *testing.T) { + t.Run("含 credits 关键词时标记(403 场景)", func(t *testing.T) { resp := &http.Response{StatusCode: http.StatusForbidden} for _, keyword := range []string{ "Insufficient GOOGLE_ONE_AI credits",