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
16 changes: 16 additions & 0 deletions packages/core/src/utils/errorParsing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,20 @@ describe('parseAndFormatApiError', () => {
const expected = '[API Error: An unknown error occurred.]';
expect(parseAndFormatApiError(error)).toBe(expected);
});

it('should decode and format a byte-array encoded error message', () => {
const json = JSON.stringify({
error: {
code: 429,
message: 'No capacity available',
status: 'RESOURCE_EXHAUSTED',
},
});
const byteString = Array.from(json)
.map((c) => c.charCodeAt(0))
.join(',');
const result = parseAndFormatApiError(byteString);
expect(result).toContain('[API Error: No capacity available');
expect(result).toContain('RESOURCE_EXHAUSTED');
});
});
34 changes: 33 additions & 1 deletion packages/core/src/utils/errorParsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
import type { UserTierId } from '../code_assist/types.js';
import { AuthType } from '../core/contentGenerator.js';

function tryDecodeByteArray(s: string): string | null {
if (!/^\d+(,\d+)+$/.test(s)) {
return null;
}
try {
const bytes = new Uint8Array(s.split(',').map(Number));
return new TextDecoder().decode(bytes);
} catch {
return null;
}
}

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 =
Expand Down Expand Up @@ -40,6 +52,16 @@ export function parseAndFormatApiError(
fallbackModel?: string,
): string {
if (isStructuredError(error)) {
const decoded = tryDecodeByteArray(error.message);
if (decoded) {
return parseAndFormatApiError(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed an edge case here.

When the code successfully decodes error.message and recursively calls
parseAndFormatApiError(decoded, ...), it passes only the new string and
leaves behind the original outer object, dropping error.status.

Timeline of what happens if the API returns a 429 Error:

  1. Input at line 47: { message: "78,111...", status: 429 }
  2. Line 55: tryDecodeByteArray decodes the message into the string
    "No capacity available".
  3. Line 57: The recursive call triggers, passing only the new string:
    parseAndFormatApiError("No capacity available", ...)
  4. The function restarts at line 47. Because the input is now a plain string
    instead of an object, status no longer exists.
  5. Line 66 (if (error.status === 429)) is never reached.

Actual Output: [API Error: No capacity available]

Expected Output:
[API Error: No capacity available]
Please wait and try again later. To increase your limits...

We should clone the error object here so we can preserve the original HTTP
status before recursing:

    if (decoded) {
      return parseAndFormatApiError(
        { ...error, message: decoded },
        authType,
        userTier,
        currentModel,
        fallbackModel,
      );

decoded,
authType,
userTier,
currentModel,
fallbackModel,
);
}
let text = `[API Error: ${error.message}]`;
if (error.status === 429) {
text += getRateLimitMessage(authType, fallbackModel);
Expand All @@ -49,9 +71,19 @@ export function parseAndFormatApiError(

// The error message might be a string containing a JSON object.
if (typeof error === 'string') {
const decoded = tryDecodeByteArray(error);
if (decoded) {
return parseAndFormatApiError(
decoded,
authType,
userTier,
currentModel,
fallbackModel,
);
}
const jsonStart = error.indexOf('{');
if (jsonStart === -1) {
return `[API Error: ${error}]`; // Not a JSON error, return as is.
return `[API Error: ${error}]`;
}

const jsonString = error.substring(jsonStart);
Expand Down