summaryrefslogtreecommitdiff
path: root/packages/core/src
diff options
context:
space:
mode:
authorAdam Weidman <[email protected]>2025-08-13 17:57:11 +0000
committerGitHub <[email protected]>2025-08-13 17:57:11 +0000
commitb61a63aef4bcce9cb56fe46f10f0dc90b8fd6597 (patch)
tree278aa096f82fbad8f0b75cdfefdd108531ccc4a0 /packages/core/src
parent9c7fb870c1a7c80741fafdfc6837d4b92e373b2d (diff)
move errorParsing.ts to core (#6159)
Diffstat (limited to 'packages/core/src')
-rw-r--r--packages/core/src/index.ts1
-rw-r--r--packages/core/src/utils/errorParsing.test.ts377
-rw-r--r--packages/core/src/utils/errorParsing.ts166
-rw-r--r--packages/core/src/utils/quotaErrorDetection.ts7
4 files changed, 546 insertions, 5 deletions
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 791446e3..a24cddbe 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -41,6 +41,7 @@ export * from './utils/systemEncoding.js';
export * from './utils/textUtils.js';
export * from './utils/formatters.js';
export * from './utils/filesearch/fileSearch.js';
+export * from './utils/errorParsing.js';
// Export services
export * from './services/fileDiscoveryService.js';
diff --git a/packages/core/src/utils/errorParsing.test.ts b/packages/core/src/utils/errorParsing.test.ts
new file mode 100644
index 00000000..f2a4709a
--- /dev/null
+++ b/packages/core/src/utils/errorParsing.test.ts
@@ -0,0 +1,377 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { describe, it, expect } from 'vitest';
+import { parseAndFormatApiError } from './errorParsing.js';
+import { isProQuotaExceededError } from './quotaErrorDetection.js';
+import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
+import { UserTierId } from '../code_assist/types.js';
+import { AuthType } from '../core/contentGenerator.js';
+import { StructuredError } from '../core/turn.js';
+
+describe('parseAndFormatApiError', () => {
+ const _enterpriseMessage =
+ 'upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits';
+ const vertexMessage = 'request a quota increase through Vertex';
+ const geminiMessage = 'request a quota increase through AI Studio';
+
+ it('should format a valid API error JSON', () => {
+ const errorMessage =
+ 'got status: 400 Bad Request. {"error":{"code":400,"message":"API key not valid. Please pass a valid API key.","status":"INVALID_ARGUMENT"}}';
+ const expected =
+ '[API Error: API key not valid. Please pass a valid API key. (Status: INVALID_ARGUMENT)]';
+ expect(parseAndFormatApiError(errorMessage)).toBe(expected);
+ });
+
+ it('should format a 429 API error with the default message', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Rate limit exceeded","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ undefined,
+ undefined,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain('[API Error: Rate limit exceeded');
+ expect(result).toContain(
+ 'Possible quota limitations in place or slow response times detected. Switching to the gemini-2.5-flash model',
+ );
+ });
+
+ it('should format a 429 API error with the personal message', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Rate limit exceeded","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ undefined,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain('[API Error: Rate limit exceeded');
+ expect(result).toContain(
+ 'Possible quota limitations in place or slow response times detected. Switching to the gemini-2.5-flash model',
+ );
+ });
+
+ it('should format a 429 API error with the vertex message', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Rate limit exceeded","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(errorMessage, AuthType.USE_VERTEX_AI);
+ expect(result).toContain('[API Error: Rate limit exceeded');
+ expect(result).toContain(vertexMessage);
+ });
+
+ it('should return the original message if it is not a JSON error', () => {
+ const errorMessage = 'This is a plain old error message';
+ expect(parseAndFormatApiError(errorMessage)).toBe(
+ `[API Error: ${errorMessage}]`,
+ );
+ });
+
+ it('should return the original message for malformed JSON', () => {
+ const errorMessage = '[Stream Error: {"error": "malformed}';
+ expect(parseAndFormatApiError(errorMessage)).toBe(
+ `[API Error: ${errorMessage}]`,
+ );
+ });
+
+ it('should handle JSON that does not match the ApiError structure', () => {
+ const errorMessage = '[Stream Error: {"not_an_error": "some other json"}]';
+ expect(parseAndFormatApiError(errorMessage)).toBe(
+ `[API Error: ${errorMessage}]`,
+ );
+ });
+
+ it('should format a nested API error', () => {
+ const nestedErrorMessage = JSON.stringify({
+ error: {
+ code: 429,
+ message:
+ "Gemini 2.5 Pro Preview doesn't have a free quota tier. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits.",
+ status: 'RESOURCE_EXHAUSTED',
+ },
+ });
+
+ const errorMessage = JSON.stringify({
+ error: {
+ code: 429,
+ message: nestedErrorMessage,
+ status: 'Too Many Requests',
+ },
+ });
+
+ const result = parseAndFormatApiError(errorMessage, AuthType.USE_GEMINI);
+ expect(result).toContain('Gemini 2.5 Pro Preview');
+ expect(result).toContain(geminiMessage);
+ });
+
+ it('should format a StructuredError', () => {
+ const error: StructuredError = {
+ message: 'A structured error occurred',
+ status: 500,
+ };
+ const expected = '[API Error: A structured error occurred]';
+ expect(parseAndFormatApiError(error)).toBe(expected);
+ });
+
+ it('should format a 429 StructuredError with the vertex message', () => {
+ const error: StructuredError = {
+ message: 'Rate limit exceeded',
+ status: 429,
+ };
+ const result = parseAndFormatApiError(error, AuthType.USE_VERTEX_AI);
+ expect(result).toContain('[API Error: Rate limit exceeded]');
+ expect(result).toContain(vertexMessage);
+ });
+
+ it('should handle an unknown error type', () => {
+ const error = 12345;
+ const expected = '[API Error: An unknown error occurred.]';
+ expect(parseAndFormatApiError(error)).toBe(expected);
+ });
+
+ it('should format a 429 API error with Pro quota exceeded message for Google auth (Free tier)', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Quota exceeded for quota metric \'Gemini 2.5 Pro Requests\' and limit \'RequestsPerDay\' of service \'generativelanguage.googleapis.com\' for consumer \'project_number:123456789\'.","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ undefined,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain(
+ "[API Error: Quota exceeded for quota metric 'Gemini 2.5 Pro Requests'",
+ );
+ expect(result).toContain(
+ 'You have reached your daily gemini-2.5-pro quota limit',
+ );
+ expect(result).toContain(
+ 'upgrade to a Gemini Code Assist Standard or Enterprise plan',
+ );
+ });
+
+ it('should format a regular 429 API error with standard message for Google auth', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Rate limit exceeded","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ undefined,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain('[API Error: Rate limit exceeded');
+ expect(result).toContain(
+ 'Possible quota limitations in place or slow response times detected. Switching to the gemini-2.5-flash model',
+ );
+ expect(result).not.toContain(
+ 'You have reached your daily gemini-2.5-pro quota limit',
+ );
+ });
+
+ it('should format a 429 API error with generic quota exceeded message for Google auth', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Quota exceeded for quota metric \'GenerationRequests\' and limit \'RequestsPerDay\' of service \'generativelanguage.googleapis.com\' for consumer \'project_number:123456789\'.","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ undefined,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain(
+ "[API Error: Quota exceeded for quota metric 'GenerationRequests'",
+ );
+ expect(result).toContain('You have reached your daily quota limit');
+ expect(result).not.toContain(
+ 'You have reached your daily Gemini 2.5 Pro quota limit',
+ );
+ });
+
+ it('should prioritize Pro quota message over generic quota message for Google auth', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Quota exceeded for quota metric \'Gemini 2.5 Pro Requests\' and limit \'RequestsPerDay\' of service \'generativelanguage.googleapis.com\' for consumer \'project_number:123456789\'.","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ undefined,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain(
+ "[API Error: Quota exceeded for quota metric 'Gemini 2.5 Pro Requests'",
+ );
+ expect(result).toContain(
+ 'You have reached your daily gemini-2.5-pro quota limit',
+ );
+ expect(result).not.toContain('You have reached your daily quota limit');
+ });
+
+ it('should format a 429 API error with Pro quota exceeded message for Google auth (Standard tier)', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Quota exceeded for quota metric \'Gemini 2.5 Pro Requests\' and limit \'RequestsPerDay\' of service \'generativelanguage.googleapis.com\' for consumer \'project_number:123456789\'.","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ UserTierId.STANDARD,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain(
+ "[API Error: Quota exceeded for quota metric 'Gemini 2.5 Pro Requests'",
+ );
+ expect(result).toContain(
+ 'You have reached your daily gemini-2.5-pro quota limit',
+ );
+ expect(result).toContain(
+ 'We appreciate you for choosing Gemini Code Assist and the Gemini CLI',
+ );
+ expect(result).not.toContain(
+ 'upgrade to a Gemini Code Assist Standard or Enterprise plan',
+ );
+ });
+
+ it('should format a 429 API error with Pro quota exceeded message for Google auth (Legacy tier)', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Quota exceeded for quota metric \'Gemini 2.5 Pro Requests\' and limit \'RequestsPerDay\' of service \'generativelanguage.googleapis.com\' for consumer \'project_number:123456789\'.","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ UserTierId.LEGACY,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain(
+ "[API Error: Quota exceeded for quota metric 'Gemini 2.5 Pro Requests'",
+ );
+ expect(result).toContain(
+ 'You have reached your daily gemini-2.5-pro quota limit',
+ );
+ expect(result).toContain(
+ 'We appreciate you for choosing Gemini Code Assist and the Gemini CLI',
+ );
+ expect(result).not.toContain(
+ 'upgrade to a Gemini Code Assist Standard or Enterprise plan',
+ );
+ });
+
+ it('should handle different Gemini 2.5 version strings in Pro quota exceeded errors', () => {
+ const errorMessage25 =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Quota exceeded for quota metric \'Gemini 2.5 Pro Requests\' and limit \'RequestsPerDay\' of service \'generativelanguage.googleapis.com\' for consumer \'project_number:123456789\'.","status":"RESOURCE_EXHAUSTED"}}';
+ const errorMessagePreview =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Quota exceeded for quota metric \'Gemini 2.5-preview Pro Requests\' and limit \'RequestsPerDay\' of service \'generativelanguage.googleapis.com\' for consumer \'project_number:123456789\'.","status":"RESOURCE_EXHAUSTED"}}';
+
+ const result25 = parseAndFormatApiError(
+ errorMessage25,
+ AuthType.LOGIN_WITH_GOOGLE,
+ undefined,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ const resultPreview = parseAndFormatApiError(
+ errorMessagePreview,
+ AuthType.LOGIN_WITH_GOOGLE,
+ undefined,
+ 'gemini-2.5-preview-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+
+ expect(result25).toContain(
+ 'You have reached your daily gemini-2.5-pro quota limit',
+ );
+ expect(resultPreview).toContain(
+ 'You have reached your daily gemini-2.5-preview-pro quota limit',
+ );
+ expect(result25).toContain(
+ 'upgrade to a Gemini Code Assist Standard or Enterprise plan',
+ );
+ expect(resultPreview).toContain(
+ 'upgrade to a Gemini Code Assist Standard or Enterprise plan',
+ );
+ });
+
+ it('should not match non-Pro models with similar version strings', () => {
+ // Test that Flash models with similar version strings don't match
+ expect(
+ isProQuotaExceededError(
+ "Quota exceeded for quota metric 'Gemini 2.5 Flash Requests' and limit",
+ ),
+ ).toBe(false);
+ expect(
+ isProQuotaExceededError(
+ "Quota exceeded for quota metric 'Gemini 2.5-preview Flash Requests' and limit",
+ ),
+ ).toBe(false);
+
+ // Test other model types
+ expect(
+ isProQuotaExceededError(
+ "Quota exceeded for quota metric 'Gemini 2.5 Ultra Requests' and limit",
+ ),
+ ).toBe(false);
+ expect(
+ isProQuotaExceededError(
+ "Quota exceeded for quota metric 'Gemini 2.5 Standard Requests' and limit",
+ ),
+ ).toBe(false);
+
+ // Test generic quota messages
+ expect(
+ isProQuotaExceededError(
+ "Quota exceeded for quota metric 'GenerationRequests' and limit",
+ ),
+ ).toBe(false);
+ expect(
+ isProQuotaExceededError(
+ "Quota exceeded for quota metric 'EmbeddingRequests' and limit",
+ ),
+ ).toBe(false);
+ });
+
+ it('should format a generic quota exceeded message for Google auth (Standard tier)', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Quota exceeded for quota metric \'GenerationRequests\' and limit \'RequestsPerDay\' of service \'generativelanguage.googleapis.com\' for consumer \'project_number:123456789\'.","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ UserTierId.STANDARD,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain(
+ "[API Error: Quota exceeded for quota metric 'GenerationRequests'",
+ );
+ expect(result).toContain('You have reached your daily quota limit');
+ expect(result).toContain(
+ 'We appreciate you for choosing Gemini Code Assist and the Gemini CLI',
+ );
+ expect(result).not.toContain(
+ 'upgrade to a Gemini Code Assist Standard or Enterprise plan',
+ );
+ });
+
+ it('should format a regular 429 API error with standard message for Google auth (Standard tier)', () => {
+ const errorMessage =
+ 'got status: 429 Too Many Requests. {"error":{"code":429,"message":"Rate limit exceeded","status":"RESOURCE_EXHAUSTED"}}';
+ const result = parseAndFormatApiError(
+ errorMessage,
+ AuthType.LOGIN_WITH_GOOGLE,
+ UserTierId.STANDARD,
+ 'gemini-2.5-pro',
+ DEFAULT_GEMINI_FLASH_MODEL,
+ );
+ expect(result).toContain('[API Error: Rate limit exceeded');
+ expect(result).toContain(
+ 'We appreciate you for choosing Gemini Code Assist and the Gemini CLI',
+ );
+ expect(result).not.toContain(
+ 'upgrade to a Gemini Code Assist Standard or Enterprise plan',
+ );
+ });
+});
diff --git a/packages/core/src/utils/errorParsing.ts b/packages/core/src/utils/errorParsing.ts
new file mode 100644
index 00000000..aa15a652
--- /dev/null
+++ b/packages/core/src/utils/errorParsing.ts
@@ -0,0 +1,166 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ isProQuotaExceededError,
+ isGenericQuotaExceededError,
+ isApiError,
+ isStructuredError,
+} from './quotaErrorDetection.js';
+import {
+ DEFAULT_GEMINI_MODEL,
+ DEFAULT_GEMINI_FLASH_MODEL,
+} from '../config/models.js';
+import { UserTierId } from '../code_assist/types.js';
+import { AuthType } from '../core/contentGenerator.js';
+
+// Free Tier message functions
+const getRateLimitErrorMessageGoogleFree = (
+ fallbackModel: string = DEFAULT_GEMINI_FLASH_MODEL,
+) =>
+ `\nPossible quota limitations in place or slow response times detected. Switching to the ${fallbackModel} model for the rest of this session.`;
+
+const getRateLimitErrorMessageGoogleProQuotaFree = (
+ currentModel: string = DEFAULT_GEMINI_MODEL,
+ fallbackModel: string = DEFAULT_GEMINI_FLASH_MODEL,
+) =>
+ `\nYou have reached your daily ${currentModel} quota limit. You will be switched to the ${fallbackModel} model for the rest of this session. To increase your limits, upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https://goo.gle/set-up-gemini-code-assist, or use /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey`;
+
+const getRateLimitErrorMessageGoogleGenericQuotaFree = () =>
+ `\nYou have reached your daily quota limit. To increase your limits, upgrade to a Gemini Code Assist Standard or Enterprise plan with higher limits at https://goo.gle/set-up-gemini-code-assist, or use /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey`;
+
+// Legacy/Standard Tier message functions
+const getRateLimitErrorMessageGooglePaid = (
+ fallbackModel: string = DEFAULT_GEMINI_FLASH_MODEL,
+) =>
+ `\nPossible quota limitations in place or slow response times detected. Switching to the ${fallbackModel} model for the rest of this session. We appreciate you for choosing Gemini Code Assist and the Gemini CLI.`;
+
+const getRateLimitErrorMessageGoogleProQuotaPaid = (
+ currentModel: string = DEFAULT_GEMINI_MODEL,
+ fallbackModel: string = DEFAULT_GEMINI_FLASH_MODEL,
+) =>
+ `\nYou have reached your daily ${currentModel} quota limit. You will be switched to the ${fallbackModel} model for the rest of this session. We appreciate you for choosing Gemini Code Assist and the Gemini CLI. To continue accessing the ${currentModel} model today, consider using /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey`;
+
+const getRateLimitErrorMessageGoogleGenericQuotaPaid = (
+ currentModel: string = DEFAULT_GEMINI_MODEL,
+) =>
+ `\nYou have reached your daily quota limit. We appreciate you for choosing Gemini Code Assist and the Gemini CLI. To continue accessing the ${currentModel} model today, consider using /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey`;
+const RATE_LIMIT_ERROR_MESSAGE_USE_GEMINI =
+ '\nPlease wait and try again later. To increase your limits, request a quota increase through AI Studio, or switch to another /auth method';
+const RATE_LIMIT_ERROR_MESSAGE_VERTEX =
+ '\nPlease wait and try again later. To increase your limits, request a quota increase through Vertex, or switch to another /auth method';
+const getRateLimitErrorMessageDefault = (
+ fallbackModel: string = DEFAULT_GEMINI_FLASH_MODEL,
+) =>
+ `\nPossible quota limitations in place or slow response times detected. Switching to the ${fallbackModel} model for the rest of this session.`;
+
+function getRateLimitMessage(
+ authType?: AuthType,
+ error?: unknown,
+ userTier?: UserTierId,
+ currentModel?: string,
+ fallbackModel?: string,
+): string {
+ switch (authType) {
+ case AuthType.LOGIN_WITH_GOOGLE: {
+ // Determine if user is on a paid tier (Legacy or Standard) - default to FREE if not specified
+ const isPaidTier =
+ userTier === UserTierId.LEGACY || userTier === UserTierId.STANDARD;
+
+ if (isProQuotaExceededError(error)) {
+ return isPaidTier
+ ? getRateLimitErrorMessageGoogleProQuotaPaid(
+ currentModel || DEFAULT_GEMINI_MODEL,
+ fallbackModel,
+ )
+ : getRateLimitErrorMessageGoogleProQuotaFree(
+ currentModel || DEFAULT_GEMINI_MODEL,
+ fallbackModel,
+ );
+ } else if (isGenericQuotaExceededError(error)) {
+ return isPaidTier
+ ? getRateLimitErrorMessageGoogleGenericQuotaPaid(
+ currentModel || DEFAULT_GEMINI_MODEL,
+ )
+ : getRateLimitErrorMessageGoogleGenericQuotaFree();
+ } else {
+ return isPaidTier
+ ? getRateLimitErrorMessageGooglePaid(fallbackModel)
+ : getRateLimitErrorMessageGoogleFree(fallbackModel);
+ }
+ }
+ case AuthType.USE_GEMINI:
+ return RATE_LIMIT_ERROR_MESSAGE_USE_GEMINI;
+ case AuthType.USE_VERTEX_AI:
+ return RATE_LIMIT_ERROR_MESSAGE_VERTEX;
+ default:
+ return getRateLimitErrorMessageDefault(fallbackModel);
+ }
+}
+
+export function parseAndFormatApiError(
+ error: unknown,
+ authType?: AuthType,
+ userTier?: UserTierId,
+ currentModel?: string,
+ fallbackModel?: string,
+): string {
+ if (isStructuredError(error)) {
+ let text = `[API Error: ${error.message}]`;
+ if (error.status === 429) {
+ text += getRateLimitMessage(
+ authType,
+ error,
+ userTier,
+ currentModel,
+ fallbackModel,
+ );
+ }
+ return text;
+ }
+
+ // The error message might be a string containing a JSON object.
+ if (typeof error === 'string') {
+ const jsonStart = error.indexOf('{');
+ if (jsonStart === -1) {
+ return `[API Error: ${error}]`; // Not a JSON error, return as is.
+ }
+
+ const jsonString = error.substring(jsonStart);
+
+ try {
+ const parsedError = JSON.parse(jsonString) as unknown;
+ if (isApiError(parsedError)) {
+ let finalMessage = parsedError.error.message;
+ try {
+ // See if the message is a stringified JSON with another error
+ const nestedError = JSON.parse(finalMessage) as unknown;
+ if (isApiError(nestedError)) {
+ finalMessage = nestedError.error.message;
+ }
+ } catch (_e) {
+ // It's not a nested JSON error, so we just use the message as is.
+ }
+ let text = `[API Error: ${finalMessage} (Status: ${parsedError.error.status})]`;
+ if (parsedError.error.code === 429) {
+ text += getRateLimitMessage(
+ authType,
+ parsedError,
+ userTier,
+ currentModel,
+ fallbackModel,
+ );
+ }
+ return text;
+ }
+ } catch (_e) {
+ // Not a valid JSON, fall through and return the original message.
+ }
+ return `[API Error: ${error}]`;
+ }
+
+ return '[API Error: An unknown error occurred.]';
+}
diff --git a/packages/core/src/utils/quotaErrorDetection.ts b/packages/core/src/utils/quotaErrorDetection.ts
index 6fe9b312..1377b4fa 100644
--- a/packages/core/src/utils/quotaErrorDetection.ts
+++ b/packages/core/src/utils/quotaErrorDetection.ts
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { StructuredError } from '../core/turn.js';
+
export interface ApiError {
error: {
code: number;
@@ -13,11 +15,6 @@ export interface ApiError {
};
}
-interface StructuredError {
- message: string;
- status?: number;
-}
-
export function isApiError(error: unknown): error is ApiError {
return (
typeof error === 'object' &&