fix(patch): cherry-pick 07e597d to release/v0.22.4-pr-15684 [CONFLICTS] (#15733)

Co-authored-by: Sehoon Shon <sshon@google.com>
This commit is contained in:
gemini-cli-robot
2025-12-30 13:21:49 -08:00
committed by GitHub
parent 440f7af977
commit 8daf2d34b3
3 changed files with 26 additions and 18 deletions

View File

@@ -342,7 +342,7 @@ describe('classifyGoogleError', () => {
const result = classifyGoogleError(originalError);
expect(result).toBeInstanceOf(RetryableQuotaError);
if (result instanceof RetryableQuotaError) {
expect(result.retryDelayMs).toBe(5000);
expect(result.retryDelayMs).toBeUndefined();
}
});
@@ -393,7 +393,7 @@ describe('classifyGoogleError', () => {
}
});
it('should return RetryableQuotaError with 5s fallback for generic 429 without specific message', () => {
it('should return RetryableQuotaError without delay time for generic 429 without specific message', () => {
const generic429 = {
status: 429,
message: 'Resource exhausted. No specific retry info.',
@@ -403,11 +403,11 @@ describe('classifyGoogleError', () => {
expect(result).toBeInstanceOf(RetryableQuotaError);
if (result instanceof RetryableQuotaError) {
expect(result.retryDelayMs).toBe(5000);
expect(result.retryDelayMs).toBeUndefined();
}
});
it('should return RetryableQuotaError with 5s fallback for 429 with empty details and no regex match', () => {
it('should return RetryableQuotaError without delay time for 429 with empty details and no regex match', () => {
const errorWithEmptyDetails = {
error: {
code: 429,
@@ -420,11 +420,11 @@ describe('classifyGoogleError', () => {
expect(result).toBeInstanceOf(RetryableQuotaError);
if (result instanceof RetryableQuotaError) {
expect(result.retryDelayMs).toBe(5000);
expect(result.retryDelayMs).toBeUndefined();
}
});
it('should return RetryableQuotaError with 5s fallback for 429 with some detail', () => {
it('should return RetryableQuotaError without delay time for 429 with some detail', () => {
const errorWithEmptyDetails = {
error: {
code: 429,
@@ -446,7 +446,7 @@ describe('classifyGoogleError', () => {
expect(result).toBeInstanceOf(RetryableQuotaError);
if (result instanceof RetryableQuotaError) {
expect(result.retryDelayMs).toBe(5000);
expect(result.retryDelayMs).toBeUndefined();
}
});
});

View File

@@ -13,8 +13,6 @@ import type {
import { parseGoogleApiError } from './googleErrors.js';
import { getErrorStatus, ModelNotFoundError } from './httpErrors.js';
const DEFAULT_RETRYABLE_DELAY_SECOND = 5;
/**
* A non-retryable error indicating a hard quota limit has been reached (e.g., daily limit).
*/
@@ -24,11 +22,13 @@ export class TerminalQuotaError extends Error {
constructor(
message: string,
override readonly cause: GoogleApiError,
retryDelayMs?: number,
retryDelaySeconds?: number,
) {
super(message);
this.name = 'TerminalQuotaError';
this.retryDelayMs = retryDelayMs ? retryDelayMs * 1000 : undefined;
this.retryDelayMs = retryDelaySeconds
? retryDelaySeconds * 1000
: undefined;
}
}
@@ -36,16 +36,18 @@ export class TerminalQuotaError extends Error {
* A retryable error indicating a temporary quota issue (e.g., per-minute limit).
*/
export class RetryableQuotaError extends Error {
retryDelayMs: number;
retryDelayMs?: number;
constructor(
message: string,
override readonly cause: GoogleApiError,
retryDelaySeconds: number,
retryDelaySeconds?: number,
) {
super(message);
this.name = 'RetryableQuotaError';
this.retryDelayMs = retryDelaySeconds * 1000;
this.retryDelayMs = retryDelaySeconds
? retryDelaySeconds * 1000
: undefined;
}
}
@@ -124,7 +126,6 @@ export function classifyGoogleError(error: unknown): unknown {
message: errorMessage,
details: [],
},
DEFAULT_RETRYABLE_DELAY_SECOND,
);
}
@@ -259,7 +260,6 @@ export function classifyGoogleError(error: unknown): unknown {
message: errorMessage,
details: [],
},
DEFAULT_RETRYABLE_DELAY_SECOND,
);
}
return error; // Fallback to original error if no specific classification fits.

View File

@@ -220,6 +220,11 @@ export async function retryWithBackoff<T>(
if (classifiedError instanceof RetryableQuotaError || is500) {
if (attempt >= maxAttempts) {
const errorMessage =
classifiedError instanceof Error ? classifiedError.message : '';
debugLogger.warn(
`Attempt ${attempt} failed${errorMessage ? `: ${errorMessage}` : ''}. Max attempts reached`,
);
if (onPersistent429) {
try {
const fallbackModel = await onPersistent429(
@@ -240,8 +245,11 @@ export async function retryWithBackoff<T>(
: error;
}
if (classifiedError instanceof RetryableQuotaError) {
console.warn(
if (
classifiedError instanceof RetryableQuotaError &&
classifiedError.retryDelayMs !== undefined
) {
debugLogger.warn(
`Attempt ${attempt} failed: ${classifiedError.message}. Retrying after ${classifiedError.retryDelayMs}ms...`,
);
await delay(classifiedError.retryDelayMs, signal);