diff --git a/src/plugin.ts b/src/plugin.ts index 4e5d50ab..f65bc6ef 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1608,6 +1608,46 @@ export const createAntigravityPlugin = (providerId: string) => async ( } } + // Handle 403 VALIDATION_REQUIRED - account needs Google verification + // This prevents "Body already used" errors from SDK trying to parse the response twice + if (response.status === 403) { + const cloned403 = response.clone(); + const body403 = await extractRetryInfoFromBody(cloned403); + + if (body403.reason === "VALIDATION_REQUIRED") { + const cooldownMs = 10 * 60 * 1000; // 10 minutes - give user time to verify + accountManager.markAccountCoolingDown(account, cooldownMs, "validation-required"); + accountManager.markRateLimited(account, cooldownMs, family, headerStyle, model); + + const accountLabel = account.email || `Account ${account.index + 1}`; + await showToast( + `${accountLabel} requires Google verification. Visit accounts.google.com to verify, then retry. Switching accounts...`, + "warning" + ); + pushDebug(`validation-required: account=${account.index} cooldown=${cooldownMs}ms`); + + // Record failure for health tracking + getHealthTracker().recordFailure(account.index); + + // Force switch to another account + lastFailure = { + response, + streaming: prepared.streaming, + debugContext, + requestedModel: prepared.requestedModel, + projectId: prepared.projectId, + endpoint: prepared.endpoint, + effectiveModel: prepared.effectiveModel, + sessionId: prepared.sessionId, + toolDebugMissing: prepared.toolDebugMissing, + toolDebugSummary: prepared.toolDebugSummary, + toolDebugPayload: prepared.toolDebugPayload, + }; + shouldSwitchAccount = true; + break; + } + } + // Success - reset rate limit backoff state for this quota const quotaKey = headerStyleToQuotaKey(headerStyle, family); resetRateLimitState(account.index, quotaKey); diff --git a/src/plugin/storage.ts b/src/plugin/storage.ts index c91ef2bc..48c7ccf3 100644 --- a/src/plugin/storage.ts +++ b/src/plugin/storage.ts @@ -173,7 +173,14 @@ export interface AccountStorage { activeIndex: number; } -export type CooldownReason = "auth-failure" | "network-error" | "project-error"; +/** + * Reasons why an account may be cooling down. + * - "auth-failure": Authentication/token refresh failed + * - "network-error": Network connectivity issues + * - "project-error": Project ID resolution failed + * - "validation-required": Google requires account verification (403 PERMISSION_DENIED with VALIDATION_REQUIRED) + */ +export type CooldownReason = "auth-failure" | "network-error" | "project-error" | "validation-required"; export interface AccountMetadataV3 { email?: string; @@ -189,6 +196,8 @@ export interface AccountMetadataV3 { cooldownReason?: CooldownReason; /** Per-account device fingerprint for rate limit mitigation */ fingerprint?: import("./fingerprint").Fingerprint; + /** History of previous fingerprints for this account */ + fingerprintHistory?: import("./fingerprint").FingerprintVersion[]; } export interface AccountStorageV3 {