fix(patch): cherry-pick f51d745 to release/v0.13.0-preview.0-pr-12586 to patch version v0.13.0-preview.0 and create version 0.13.0-preview.1 (#12595)

Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com>
This commit is contained in:
gemini-cli-robot
2025-11-05 09:04:36 -08:00
committed by GitHub
parent 25d7a8037f
commit fd885a3e50
2 changed files with 66 additions and 5 deletions

View File

@@ -25,6 +25,44 @@ describe('classifyGoogleError', () => {
expect(result).toBe(regularError);
});
it('should return RetryableQuotaError when message contains "Please retry in Xs"', () => {
const complexError = {
error: {
message:
'{"error": {"code": 429, "status": 429, "message": "You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/usage?tab=rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 2\nPlease retry in 44.097740004s.", "details": [{"detail": "??? to (unknown) : APP_ERROR(8) You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/usage?tab=rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 2\nPlease retry in 44.097740004s."}]}}',
code: 429,
status: 'Too Many Requests',
},
};
const rawError = new Error(JSON.stringify(complexError));
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(null);
const result = classifyGoogleError(rawError);
expect(result).toBeInstanceOf(RetryableQuotaError);
expect((result as RetryableQuotaError).retryDelayMs).toBe(44097.740004);
expect((result as RetryableQuotaError).message).toBe(rawError.message);
});
it('should return RetryableQuotaError when error is a string and message contains "Please retry in Xms"', () => {
const complexErrorString = JSON.stringify({
error: {
message:
'{"error": {"code": 429, "status": 429, "message": "You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/usage?tab=rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 2\nPlease retry in 900.2ms.", "details": [{"detail": "??? to (unknown) : APP_ERROR(8) You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/usage?tab=rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 2\nPlease retry in 900.2ms."}]}}',
code: 429,
status: 'Too Many Requests',
},
});
const rawError = new Error(complexErrorString);
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(null);
const result = classifyGoogleError(rawError);
expect(result).toBeInstanceOf(RetryableQuotaError);
expect((result as RetryableQuotaError).retryDelayMs).toBeCloseTo(900.2);
expect((result as RetryableQuotaError).message).toBe(rawError.message);
});
it('should return original error if code is not 429', () => {
const apiError: GoogleApiError = {
code: 500,

View File

@@ -43,16 +43,20 @@ export class RetryableQuotaError extends Error {
}
/**
* Parses a duration string (e.g., "34.074824224s", "60s") and returns the time in seconds.
* Parses a duration string (e.g., "34.074824224s", "60s", "900ms") and returns the time in seconds.
* @param duration The duration string to parse.
* @returns The duration in seconds, or null if parsing fails.
*/
function parseDurationInSeconds(duration: string): number | null {
if (!duration.endsWith('s')) {
return null;
if (duration.endsWith('ms')) {
const milliseconds = parseFloat(duration.slice(0, -2));
return isNaN(milliseconds) ? null : milliseconds / 1000;
}
const seconds = parseFloat(duration.slice(0, -1));
return isNaN(seconds) ? null : seconds;
if (duration.endsWith('s')) {
const seconds = parseFloat(duration.slice(0, -1));
return isNaN(seconds) ? null : seconds;
}
return null;
}
/**
@@ -64,6 +68,7 @@ function parseDurationInSeconds(duration: string): number | null {
* - If the error suggests a retry delay of more than 2 minutes, it's a `TerminalQuotaError`.
* - If the error suggests a retry delay of 2 minutes or less, it's a `RetryableQuotaError`.
* - If the error indicates a per-minute limit, it's a `RetryableQuotaError`.
* - If the error message contains the phrase "Please retry in X[s|ms]", it's a `RetryableQuotaError`.
*
* @param error The error to classify.
* @returns A `TerminalQuotaError`, `RetryableQuotaError`, or the original `unknown` error.
@@ -72,6 +77,24 @@ export function classifyGoogleError(error: unknown): unknown {
const googleApiError = parseGoogleApiError(error);
if (!googleApiError || googleApiError.code !== 429) {
// Fallback: try to parse the error message for a retry delay
const errorMessage = error instanceof Error ? error.message : String(error);
const match = errorMessage.match(/Please retry in ([0-9.]+(?:ms|s))/);
if (match?.[1]) {
const retryDelaySeconds = parseDurationInSeconds(match[1]);
if (retryDelaySeconds !== null) {
return new RetryableQuotaError(
errorMessage,
googleApiError ?? {
code: 429,
message: errorMessage,
details: [],
},
retryDelaySeconds,
);
}
}
return error; // Not a 429 error we can handle.
}