[JUNE 25] Permanent failover to Flash model for OAuth users after persistent 429 errors (#1376)

Co-authored-by: Scott Densmore <scottdensmore@mac.com>
This commit is contained in:
Bryan Morgan
2025-06-24 18:48:55 -04:00
committed by GitHub
parent 4bf18da2b0
commit e356949d3f
16 changed files with 837 additions and 12 deletions

View File

@@ -4,11 +4,15 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { AuthType } from '../core/contentGenerator.js';
export interface RetryOptions {
maxAttempts: number;
initialDelayMs: number;
maxDelayMs: number;
shouldRetry: (error: Error) => boolean;
onPersistent429?: (authType?: string) => Promise<string | null>;
authType?: string;
}
const DEFAULT_RETRY_OPTIONS: RetryOptions = {
@@ -59,29 +63,69 @@ export async function retryWithBackoff<T>(
fn: () => Promise<T>,
options?: Partial<RetryOptions>,
): Promise<T> {
const { maxAttempts, initialDelayMs, maxDelayMs, shouldRetry } = {
const {
maxAttempts,
initialDelayMs,
maxDelayMs,
shouldRetry,
onPersistent429,
authType,
} = {
...DEFAULT_RETRY_OPTIONS,
...options,
};
let attempt = 0;
let currentDelay = initialDelayMs;
let consecutive429Count = 0;
while (attempt < maxAttempts) {
attempt++;
try {
return await fn();
} catch (error) {
const errorStatus = getErrorStatus(error);
// Track consecutive 429 errors
if (errorStatus === 429) {
consecutive429Count++;
} else {
consecutive429Count = 0;
}
// Check if we've exhausted retries or shouldn't retry
if (attempt >= maxAttempts || !shouldRetry(error as Error)) {
// If we have persistent 429s and a fallback callback for OAuth
if (
consecutive429Count >= 3 &&
onPersistent429 &&
(authType === AuthType.LOGIN_WITH_GOOGLE_PERSONAL ||
authType === AuthType.LOGIN_WITH_GOOGLE_ENTERPRISE)
) {
try {
const fallbackModel = await onPersistent429(authType);
if (fallbackModel) {
// Reset attempt counter and try with new model
attempt = 0;
consecutive429Count = 0;
currentDelay = initialDelayMs;
continue;
}
} catch (fallbackError) {
// If fallback fails, continue with original error
console.warn('Fallback to Flash model failed:', fallbackError);
}
}
throw error;
}
const { delayDurationMs, errorStatus } = getDelayDurationAndStatus(error);
const { delayDurationMs, errorStatus: delayErrorStatus } =
getDelayDurationAndStatus(error);
if (delayDurationMs > 0) {
// Respect Retry-After header if present and parsed
console.warn(
`Attempt ${attempt} failed with status ${errorStatus ?? 'unknown'}. Retrying after explicit delay of ${delayDurationMs}ms...`,
`Attempt ${attempt} failed with status ${delayErrorStatus ?? 'unknown'}. Retrying after explicit delay of ${delayDurationMs}ms...`,
error,
);
await delay(delayDurationMs);