mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-11 14:40:52 -07:00
Handle all 429 as retryableQuotaError (#15288)
This commit is contained in:
@@ -325,7 +325,7 @@ describe('classifyGoogleError', () => {
|
|||||||
expect(result).toBeInstanceOf(TerminalQuotaError);
|
expect(result).toBeInstanceOf(TerminalQuotaError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return original error for 429 without specific details', () => {
|
it('should return RetryableQuotaError for any 429', () => {
|
||||||
const apiError: GoogleApiError = {
|
const apiError: GoogleApiError = {
|
||||||
code: 429,
|
code: 429,
|
||||||
message: 'Too many requests',
|
message: 'Too many requests',
|
||||||
@@ -340,7 +340,10 @@ describe('classifyGoogleError', () => {
|
|||||||
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
|
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
|
||||||
const originalError = new Error();
|
const originalError = new Error();
|
||||||
const result = classifyGoogleError(originalError);
|
const result = classifyGoogleError(originalError);
|
||||||
expect(result).toBe(originalError);
|
expect(result).toBeInstanceOf(RetryableQuotaError);
|
||||||
|
if (result instanceof RetryableQuotaError) {
|
||||||
|
expect(result.retryDelayMs).toBe(5000);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should classify nested JSON string 404 error as ModelNotFoundError', () => {
|
it('should classify nested JSON string 404 error as ModelNotFoundError', () => {
|
||||||
@@ -389,4 +392,61 @@ describe('classifyGoogleError', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return RetryableQuotaError with 5s fallback for generic 429 without specific message', () => {
|
||||||
|
const generic429 = {
|
||||||
|
status: 429,
|
||||||
|
message: 'Resource exhausted. No specific retry info.',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = classifyGoogleError(generic429);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(RetryableQuotaError);
|
||||||
|
if (result instanceof RetryableQuotaError) {
|
||||||
|
expect(result.retryDelayMs).toBe(5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return RetryableQuotaError with 5s fallback for 429 with empty details and no regex match', () => {
|
||||||
|
const errorWithEmptyDetails = {
|
||||||
|
error: {
|
||||||
|
code: 429,
|
||||||
|
message: 'A generic 429 error with no retry message.',
|
||||||
|
details: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = classifyGoogleError(errorWithEmptyDetails);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(RetryableQuotaError);
|
||||||
|
if (result instanceof RetryableQuotaError) {
|
||||||
|
expect(result.retryDelayMs).toBe(5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return RetryableQuotaError with 5s fallback for 429 with some detail', () => {
|
||||||
|
const errorWithEmptyDetails = {
|
||||||
|
error: {
|
||||||
|
code: 429,
|
||||||
|
message: 'A generic 429 error with no retry message.',
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
|
||||||
|
reason: 'QUOTA_EXCEEDED',
|
||||||
|
domain: 'googleapis.com',
|
||||||
|
metadata: {
|
||||||
|
quota_limit: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = classifyGoogleError(errorWithEmptyDetails);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(RetryableQuotaError);
|
||||||
|
if (result instanceof RetryableQuotaError) {
|
||||||
|
expect(result.retryDelayMs).toBe(5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import type {
|
|||||||
import { parseGoogleApiError } from './googleErrors.js';
|
import { parseGoogleApiError } from './googleErrors.js';
|
||||||
import { getErrorStatus, ModelNotFoundError } from './httpErrors.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).
|
* A non-retryable error indicating a hard quota limit has been reached (e.g., daily limit).
|
||||||
*/
|
*/
|
||||||
@@ -112,6 +114,18 @@ export function classifyGoogleError(error: unknown): unknown {
|
|||||||
retryDelaySeconds,
|
retryDelaySeconds,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (status === 429) {
|
||||||
|
// Fallback: If it is a 429 but doesn't have a specific "retry in" message,
|
||||||
|
// assume it is a temporary rate limit and retry after 5 sec (same as DEFAULT_RETRY_OPTIONS).
|
||||||
|
return new RetryableQuotaError(
|
||||||
|
errorMessage,
|
||||||
|
googleApiError ?? {
|
||||||
|
code: 429,
|
||||||
|
message: errorMessage,
|
||||||
|
details: [],
|
||||||
|
},
|
||||||
|
DEFAULT_RETRYABLE_DELAY_SECOND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return error; // Not a 429 error we can handle with structured details or a parsable retry message.
|
return error; // Not a 429 error we can handle with structured details or a parsable retry message.
|
||||||
@@ -232,5 +246,21 @@ export function classifyGoogleError(error: unknown): unknown {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we reached this point and the status is still 429, we return retryable.
|
||||||
|
if (status === 429) {
|
||||||
|
const errorMessage =
|
||||||
|
googleApiError?.message ||
|
||||||
|
(error instanceof Error ? error.message : String(error));
|
||||||
|
return new RetryableQuotaError(
|
||||||
|
errorMessage,
|
||||||
|
googleApiError ?? {
|
||||||
|
code: 429,
|
||||||
|
message: errorMessage,
|
||||||
|
details: [],
|
||||||
|
},
|
||||||
|
DEFAULT_RETRYABLE_DELAY_SECOND,
|
||||||
|
);
|
||||||
|
}
|
||||||
return error; // Fallback to original error if no specific classification fits.
|
return error; // Fallback to original error if no specific classification fits.
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user