From 47266e03e63142c4be7f61cd00133a08ada192af Mon Sep 17 00:00:00 2001 From: erio Date: Wed, 1 Apr 2026 02:40:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(antigravity):=20=E7=A7=AF=E5=88=86=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E5=90=8E=E4=BB=8D=20429=20=E7=9B=B4=E6=8E=A5=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=20credits=20exhausted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧逻辑中 shouldMarkCreditsExhausted 因 parseAntigravitySmartRetryInfo 能解析出 retry info 就 return false,导致 free 账号注入积分后上游返回 429 也永远不标记 exhausted,形成无限重试死循环。 修复:429 直接标记 exhausted,其他 4xx 保留关键词匹配(如 403 积分不足)。 --- .../service/antigravity_credits_overages.go | 13 ++++++++----- .../service/antigravity_credits_overages_test.go | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) 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",