fix(antigravity): 积分注入后仍 429 直接标记 credits exhausted

旧逻辑中 shouldMarkCreditsExhausted 因 parseAntigravitySmartRetryInfo
能解析出 retry info 就 return false,导致 free 账号注入积分后上游返回
429 也永远不标记 exhausted,形成无限重试死循环。

修复:429 直接标记 exhausted,其他 4xx 保留关键词匹配(如 403 积分不足)。
This commit is contained in:
erio
2026-04-01 02:40:52 +08:00
parent 9a9eadeba0
commit 47266e03e6
2 changed files with 11 additions and 8 deletions
@@ -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) {
@@ -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",