mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-03 16:34:31 -07:00
feat: try more parsing
This commit is contained in:
committed by
Tommaso Sciortino
parent
a8e3928dd2
commit
c3f6e7132b
@@ -94,7 +94,12 @@ describe('parseGoogleApiError', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(parseGoogleApiError(mockError)).toBeNull();
|
|
||||||
|
expect(parseGoogleApiError(mockError)).toEqual({
|
||||||
|
code: 400,
|
||||||
|
message: 'Bad Request',
|
||||||
|
details: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if there are no valid details', () => {
|
it('should return null if there are no valid details', () => {
|
||||||
@@ -115,7 +120,11 @@ describe('parseGoogleApiError', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(parseGoogleApiError(mockError)).toBeNull();
|
expect(parseGoogleApiError(mockError)).toEqual({
|
||||||
|
code: 400,
|
||||||
|
message: 'Bad Request',
|
||||||
|
details: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should parse a doubly nested error in the message', () => {
|
it('should parse a doubly nested error in the message', () => {
|
||||||
|
|||||||
@@ -190,32 +190,32 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null {
|
|||||||
const message = currentError.message;
|
const message = currentError.message;
|
||||||
const errorDetails = currentError.details;
|
const errorDetails = currentError.details;
|
||||||
|
|
||||||
if (Array.isArray(errorDetails) && code && message) {
|
if (code && message) {
|
||||||
const details: GoogleApiErrorDetail[] = [];
|
const details: GoogleApiErrorDetail[] = [];
|
||||||
for (const detail of errorDetails) {
|
if (Array.isArray(errorDetails)) {
|
||||||
if (detail && typeof detail === 'object') {
|
for (const detail of errorDetails) {
|
||||||
const detailObj = detail as Record<string, unknown>;
|
if (detail && typeof detail === 'object') {
|
||||||
const typeKey = Object.keys(detailObj).find(
|
const detailObj = detail as Record<string, unknown>;
|
||||||
(key) => key.trim() === '@type',
|
const typeKey = Object.keys(detailObj).find(
|
||||||
);
|
(key) => key.trim() === '@type',
|
||||||
if (typeKey) {
|
);
|
||||||
if (typeKey !== '@type') {
|
if (typeKey) {
|
||||||
detailObj['@type'] = detailObj[typeKey];
|
if (typeKey !== '@type') {
|
||||||
delete detailObj[typeKey];
|
detailObj['@type'] = detailObj[typeKey];
|
||||||
|
delete detailObj[typeKey];
|
||||||
|
}
|
||||||
|
// We can just cast it; the consumer will have to switch on @type
|
||||||
|
details.push(detailObj as unknown as GoogleApiErrorDetail);
|
||||||
}
|
}
|
||||||
// We can just cast it; the consumer will have to switch on @type
|
|
||||||
details.push(detailObj as unknown as GoogleApiErrorDetail);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.length > 0) {
|
return {
|
||||||
return {
|
code,
|
||||||
code,
|
message,
|
||||||
message,
|
details,
|
||||||
details,
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -288,6 +288,18 @@ function fromApiError(errorObj: object): ErrorShape | undefined {
|
|||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Not a JSON string, can't parse.
|
// Not a JSON string, can't parse.
|
||||||
|
// Try one more fallback: look for the first '{' and last '}'
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
const firstBrace = data.indexOf('{');
|
||||||
|
const lastBrace = data.lastIndexOf('}');
|
||||||
|
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data.substring(firstBrace, lastBrace + 1));
|
||||||
|
} catch (__) {
|
||||||
|
// Still failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from './googleQuotaErrors.js';
|
} from './googleQuotaErrors.js';
|
||||||
import * as errorParser from './googleErrors.js';
|
import * as errorParser from './googleErrors.js';
|
||||||
import type { GoogleApiError } from './googleErrors.js';
|
import type { GoogleApiError } from './googleErrors.js';
|
||||||
|
import { ModelNotFoundError } from './httpErrors.js';
|
||||||
|
|
||||||
describe('classifyGoogleError', () => {
|
describe('classifyGoogleError', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -341,4 +342,51 @@ describe('classifyGoogleError', () => {
|
|||||||
const result = classifyGoogleError(originalError);
|
const result = classifyGoogleError(originalError);
|
||||||
expect(result).toBe(originalError);
|
expect(result).toBe(originalError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should classify nested JSON string 404 error as ModelNotFoundError', () => {
|
||||||
|
// Mimic the double-wrapped JSON structure seen in the user report
|
||||||
|
const innerError = {
|
||||||
|
error: {
|
||||||
|
code: 404,
|
||||||
|
message:
|
||||||
|
'models/NOT_FOUND is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.',
|
||||||
|
status: 'NOT_FOUND',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const errorString = JSON.stringify(innerError);
|
||||||
|
|
||||||
|
const outerErrorString = JSON.stringify({
|
||||||
|
error: {
|
||||||
|
message: errorString,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const error = new Error(`[API Error: ${outerErrorString}]`);
|
||||||
|
|
||||||
|
const classified = classifyGoogleError(error);
|
||||||
|
expect(classified).toBeInstanceOf(ModelNotFoundError);
|
||||||
|
expect((classified as ModelNotFoundError).code).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to string parsing for retry delays when details array is empty', () => {
|
||||||
|
const errorWithEmptyDetails = {
|
||||||
|
error: {
|
||||||
|
code: 429,
|
||||||
|
message: 'Resource exhausted. Please retry in 5s',
|
||||||
|
details: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = classifyGoogleError(errorWithEmptyDetails);
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(RetryableQuotaError);
|
||||||
|
if (result instanceof RetryableQuotaError) {
|
||||||
|
expect(result.retryDelayMs).toBe(5000);
|
||||||
|
// The cause should be the parsed GoogleApiError
|
||||||
|
expect(result.cause).toEqual({
|
||||||
|
code: 429,
|
||||||
|
message: 'Resource exhausted. Please retry in 5s',
|
||||||
|
details: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -89,9 +89,15 @@ export function classifyGoogleError(error: unknown): unknown {
|
|||||||
return new ModelNotFoundError(message, status);
|
return new ModelNotFoundError(message, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!googleApiError || googleApiError.code !== 429) {
|
if (
|
||||||
|
!googleApiError ||
|
||||||
|
googleApiError.code !== 429 ||
|
||||||
|
googleApiError.details.length === 0
|
||||||
|
) {
|
||||||
// Fallback: try to parse the error message for a retry delay
|
// Fallback: try to parse the error message for a retry delay
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage =
|
||||||
|
googleApiError?.message ||
|
||||||
|
(error instanceof Error ? error.message : String(error));
|
||||||
const match = errorMessage.match(/Please retry in ([0-9.]+(?:ms|s))/);
|
const match = errorMessage.match(/Please retry in ([0-9.]+(?:ms|s))/);
|
||||||
if (match?.[1]) {
|
if (match?.[1]) {
|
||||||
const retryDelaySeconds = parseDurationInSeconds(match[1]);
|
const retryDelaySeconds = parseDurationInSeconds(match[1]);
|
||||||
@@ -108,7 +114,7 @@ export function classifyGoogleError(error: unknown): unknown {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return error; // Not a 429 error we can handle.
|
return error; // Not a 429 error we can handle with structured details or a parsable retry message.
|
||||||
}
|
}
|
||||||
|
|
||||||
const quotaFailure = googleApiError.details.find(
|
const quotaFailure = googleApiError.details.find(
|
||||||
|
|||||||
Reference in New Issue
Block a user