mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 13:04:49 -07:00
fix(core): add retry logic for specific fetch errors (#11066)
This commit is contained in:
@@ -304,6 +304,41 @@ describe('retryWithBackoff', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fetch error retries', () => {
|
||||
const fetchErrorMsg = 'exception TypeError: fetch failed sending request';
|
||||
|
||||
it('should retry on specific fetch error when retryFetchErrors is true', async () => {
|
||||
const mockFn = vi.fn();
|
||||
mockFn.mockRejectedValueOnce(new Error(fetchErrorMsg));
|
||||
mockFn.mockResolvedValueOnce('success');
|
||||
|
||||
const promise = retryWithBackoff(mockFn, {
|
||||
retryFetchErrors: true,
|
||||
initialDelayMs: 10,
|
||||
});
|
||||
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
const result = await promise;
|
||||
expect(result).toBe('success');
|
||||
expect(mockFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it.each([false, undefined])(
|
||||
'should not retry on specific fetch error when retryFetchErrors is %s',
|
||||
async (retryFetchErrors) => {
|
||||
const mockFn = vi.fn().mockRejectedValue(new Error(fetchErrorMsg));
|
||||
|
||||
const promise = retryWithBackoff(mockFn, {
|
||||
retryFetchErrors,
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toThrow(fetchErrorMsg);
|
||||
expect(mockFn).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('Flash model fallback for OAuth users', () => {
|
||||
it('should trigger fallback for OAuth personal users on TerminalQuotaError', async () => {
|
||||
const fallbackCallback = vi.fn().mockResolvedValue('gemini-2.5-flash');
|
||||
|
||||
@@ -13,6 +13,9 @@ import {
|
||||
TerminalQuotaError,
|
||||
} from './googleQuotaErrors.js';
|
||||
|
||||
const FETCH_FAILED_MESSAGE =
|
||||
'exception TypeError: fetch failed sending request';
|
||||
|
||||
export interface HttpError extends Error {
|
||||
status?: number;
|
||||
}
|
||||
@@ -21,13 +24,14 @@ export interface RetryOptions {
|
||||
maxAttempts: number;
|
||||
initialDelayMs: number;
|
||||
maxDelayMs: number;
|
||||
shouldRetryOnError: (error: Error) => boolean;
|
||||
shouldRetryOnError: (error: Error, retryFetchErrors?: boolean) => boolean;
|
||||
shouldRetryOnContent?: (content: GenerateContentResponse) => boolean;
|
||||
onPersistent429?: (
|
||||
authType?: string,
|
||||
error?: unknown,
|
||||
) => Promise<string | boolean | null>;
|
||||
authType?: string;
|
||||
retryFetchErrors?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_RETRY_OPTIONS: RetryOptions = {
|
||||
@@ -41,9 +45,21 @@ const DEFAULT_RETRY_OPTIONS: RetryOptions = {
|
||||
* Default predicate function to determine if a retry should be attempted.
|
||||
* Retries on 429 (Too Many Requests) and 5xx server errors.
|
||||
* @param error The error object.
|
||||
* @param retryFetchErrors Whether to retry on specific fetch errors.
|
||||
* @returns True if the error is a transient error, false otherwise.
|
||||
*/
|
||||
function defaultShouldRetry(error: Error | unknown): boolean {
|
||||
function defaultShouldRetry(
|
||||
error: Error | unknown,
|
||||
retryFetchErrors?: boolean,
|
||||
): boolean {
|
||||
if (
|
||||
retryFetchErrors &&
|
||||
error instanceof Error &&
|
||||
error.message.includes(FETCH_FAILED_MESSAGE)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Priority check for ApiError
|
||||
if (error instanceof ApiError) {
|
||||
// Explicitly do not retry 400 (Bad Request)
|
||||
@@ -96,6 +112,7 @@ export async function retryWithBackoff<T>(
|
||||
authType,
|
||||
shouldRetryOnError,
|
||||
shouldRetryOnContent,
|
||||
retryFetchErrors,
|
||||
} = {
|
||||
...DEFAULT_RETRY_OPTIONS,
|
||||
...cleanOptions,
|
||||
@@ -155,7 +172,10 @@ export async function retryWithBackoff<T>(
|
||||
}
|
||||
|
||||
// Generic retry logic for other errors
|
||||
if (attempt >= maxAttempts || !shouldRetryOnError(error as Error)) {
|
||||
if (
|
||||
attempt >= maxAttempts ||
|
||||
!shouldRetryOnError(error as Error, retryFetchErrors)
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user