Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions packages/core/src/utils/googleQuotaErrors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,101 @@ describe('classifyGoogleError', () => {
expect((result as TerminalQuotaError).reason).toBe('RATE_LIMIT_EXCEEDED');
});

it('should return RetryableQuotaError for Cloud Code RATE_LIMIT_EXCEEDED with a zero retry delay', () => {
const apiError: GoogleApiError = {
code: 429,
message: 'Rate limit exceeded',
details: [
{
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
reason: 'RATE_LIMIT_EXCEEDED',
domain: 'cloudcode-pa.googleapis.com',
metadata: {
uiMessage: 'true',
model: 'gemini-2.5-pro',
},
},
{
'@type': 'type.googleapis.com/google.rpc.RetryInfo',
retryDelay: '0s',
},
],
};
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
const result = classifyGoogleError(new Error());
expect(result).toBeInstanceOf(RetryableQuotaError);
});

it('should return RetryableQuotaError for Cloud Code RATE_LIMIT_EXCEEDED with a zero millisecond retry delay', () => {
const apiError: GoogleApiError = {
code: 429,
message: 'Rate limit exceeded',
details: [
{
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
reason: 'RATE_LIMIT_EXCEEDED',
domain: 'cloudcode-pa.googleapis.com',
metadata: {
uiMessage: 'true',
model: 'gemini-2.5-pro',
},
},
{
'@type': 'type.googleapis.com/google.rpc.RetryInfo',
retryDelay: '0ms',
},
],
};
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
const result = classifyGoogleError(new Error());
expect(result).toBeInstanceOf(RetryableQuotaError);
});

it('should return TerminalQuotaError for Cloud Code RATE_LIMIT_EXCEEDED with an unparsable retry delay', () => {
const apiError: GoogleApiError = {
code: 429,
message: 'Rate limit exceeded',
details: [
{
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
reason: 'RATE_LIMIT_EXCEEDED',
domain: 'cloudcode-pa.googleapis.com',
metadata: {
uiMessage: 'true',
model: 'gemini-2.5-pro',
},
},
{
'@type': 'type.googleapis.com/google.rpc.RetryInfo',
retryDelay: 'not-a-duration',
},
],
};
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
const result = classifyGoogleError(new Error());
expect(result).toBeInstanceOf(TerminalQuotaError);
expect((result as TerminalQuotaError).reason).toBe('RATE_LIMIT_EXCEEDED');
});

it('should return RetryableQuotaError for a zero retry delay in RetryInfo', () => {
const apiError: GoogleApiError = {
code: 429,
message: 'Too many requests',
details: [
{
'@type': 'type.googleapis.com/google.rpc.RetryInfo',
retryDelay: '0s',
},
],
};
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
const result = classifyGoogleError(new Error());
expect(result).toBeInstanceOf(RetryableQuotaError);
expect((result as RetryableQuotaError).message).toBe(
'Too many requests\nSuggested retry after 0s.',
);
});

it('should return TerminalQuotaError for Cloud Code QUOTA_EXHAUSTED', () => {
const apiError: GoogleApiError = {
code: 429,
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/utils/googleQuotaErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,9 @@ export function classifyGoogleError(error: unknown): unknown {

if (retryInfo?.retryDelay) {
const parsedDelay = parseDurationInSeconds(retryInfo.retryDelay);
if (parsedDelay) {
// `null` means the duration could not be parsed; `0` is a valid delay
// meaning "retry immediately", so it must not be discarded.
if (parsedDelay !== null) {
delaySeconds = parsedDelay;
}
}
Expand Down Expand Up @@ -408,7 +410,7 @@ export function classifyGoogleError(error: unknown): unknown {
}

// 2. Check for delays in RetryInfo
if (retryInfo?.retryDelay && delaySeconds) {
if (retryInfo?.retryDelay && delaySeconds !== undefined) {
if (delaySeconds > MAX_RETRYABLE_DELAY_SECONDS) {
return new TerminalQuotaError(
`${googleApiError.message}\nSuggested retry after ${retryInfo.retryDelay}.`,
Expand Down
Loading