@google/gemini-cli-core 0.9.0-nightly.20251002.aa8b2abe → 0.9.0-nightly.20251004.7db79e14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +2 -1
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/agents/codebase-investigator.d.ts +36 -1
  6. package/dist/src/agents/codebase-investigator.js +58 -86
  7. package/dist/src/agents/codebase-investigator.js.map +1 -1
  8. package/dist/src/agents/executor.d.ts +7 -11
  9. package/dist/src/agents/executor.js +225 -94
  10. package/dist/src/agents/executor.js.map +1 -1
  11. package/dist/src/agents/executor.test.js +327 -287
  12. package/dist/src/agents/executor.test.js.map +1 -1
  13. package/dist/src/agents/invocation.d.ts +3 -2
  14. package/dist/src/agents/invocation.js +1 -0
  15. package/dist/src/agents/invocation.js.map +1 -1
  16. package/dist/src/agents/invocation.test.js +1 -0
  17. package/dist/src/agents/invocation.test.js.map +1 -1
  18. package/dist/src/agents/registry.d.ts +2 -1
  19. package/dist/src/agents/registry.js +2 -0
  20. package/dist/src/agents/registry.js.map +1 -1
  21. package/dist/src/agents/types.d.ts +28 -6
  22. package/dist/src/agents/types.js +1 -0
  23. package/dist/src/agents/types.js.map +1 -1
  24. package/dist/src/config/config.d.ts +4 -0
  25. package/dist/src/config/config.js +8 -0
  26. package/dist/src/config/config.js.map +1 -1
  27. package/dist/src/core/client.js +2 -2
  28. package/dist/src/core/client.js.map +1 -1
  29. package/dist/src/core/client.test.js +1 -1
  30. package/dist/src/core/client.test.js.map +1 -1
  31. package/dist/src/core/geminiChat.js +1 -14
  32. package/dist/src/core/geminiChat.js.map +1 -1
  33. package/dist/src/core/geminiChat.test.js +14 -18
  34. package/dist/src/core/geminiChat.test.js.map +1 -1
  35. package/dist/src/core/prompts.d.ts +2 -1
  36. package/dist/src/core/prompts.js +81 -8
  37. package/dist/src/core/prompts.js.map +1 -1
  38. package/dist/src/core/prompts.test.js +73 -24
  39. package/dist/src/core/prompts.test.js.map +1 -1
  40. package/dist/src/generated/git-commit.d.ts +2 -2
  41. package/dist/src/generated/git-commit.js +2 -2
  42. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +0 -8
  43. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  44. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +2 -2
  45. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +4 -4
  46. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  47. package/dist/src/telemetry/constants.d.ts +1 -0
  48. package/dist/src/telemetry/constants.js +1 -0
  49. package/dist/src/telemetry/constants.js.map +1 -1
  50. package/dist/src/telemetry/loggers.js +2 -2
  51. package/dist/src/telemetry/loggers.js.map +1 -1
  52. package/dist/src/telemetry/loggers.test.js +2 -2
  53. package/dist/src/telemetry/loggers.test.js.map +1 -1
  54. package/dist/src/tools/mcp-client.d.ts +3 -2
  55. package/dist/src/tools/mcp-client.js +28 -30
  56. package/dist/src/tools/mcp-client.js.map +1 -1
  57. package/dist/src/tools/mcp-client.test.js +168 -5
  58. package/dist/src/tools/mcp-client.test.js.map +1 -1
  59. package/dist/src/utils/errorParsing.d.ts +1 -1
  60. package/dist/src/utils/errorParsing.js +5 -33
  61. package/dist/src/utils/errorParsing.js.map +1 -1
  62. package/dist/src/utils/errorParsing.test.js +0 -88
  63. package/dist/src/utils/errorParsing.test.js.map +1 -1
  64. package/dist/src/utils/flashFallback.test.js +26 -45
  65. package/dist/src/utils/flashFallback.test.js.map +1 -1
  66. package/dist/src/utils/googleErrors.d.ts +104 -0
  67. package/dist/src/utils/googleErrors.js +108 -0
  68. package/dist/src/utils/googleErrors.js.map +1 -0
  69. package/dist/src/utils/googleErrors.test.d.ts +6 -0
  70. package/dist/src/utils/googleErrors.test.js +212 -0
  71. package/dist/src/utils/googleErrors.test.js.map +1 -0
  72. package/dist/src/utils/googleQuotaErrors.d.ts +35 -0
  73. package/dist/src/utils/googleQuotaErrors.js +108 -0
  74. package/dist/src/utils/googleQuotaErrors.js.map +1 -0
  75. package/dist/src/utils/googleQuotaErrors.test.d.ts +6 -0
  76. package/dist/src/utils/googleQuotaErrors.test.js +189 -0
  77. package/dist/src/utils/googleQuotaErrors.test.js.map +1 -0
  78. package/dist/src/utils/memoryDiscovery.d.ts +1 -0
  79. package/dist/src/utils/memoryDiscovery.js +2 -1
  80. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  81. package/dist/src/utils/memoryDiscovery.test.js +99 -21
  82. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  83. package/dist/src/utils/quotaErrorDetection.d.ts +0 -2
  84. package/dist/src/utils/quotaErrorDetection.js +0 -46
  85. package/dist/src/utils/quotaErrorDetection.js.map +1 -1
  86. package/dist/src/utils/retry.js +40 -157
  87. package/dist/src/utils/retry.js.map +1 -1
  88. package/dist/src/utils/retry.test.js +85 -144
  89. package/dist/src/utils/retry.test.js.map +1 -1
  90. package/dist/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +1 -1
@@ -6,14 +6,16 @@
6
6
  import { describe, it, expect, beforeEach, vi } from 'vitest';
7
7
  import { Config } from '../config/config.js';
8
8
  import fs from 'node:fs';
9
- import { setSimulate429, disableSimulationAfterFallback, shouldSimulate429, createSimulated429Error, resetRequestCounter, } from './testUtils.js';
9
+ import { setSimulate429, disableSimulationAfterFallback, shouldSimulate429, resetRequestCounter, } from './testUtils.js';
10
10
  import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
11
11
  import { retryWithBackoff } from './retry.js';
12
12
  import { AuthType } from '../core/contentGenerator.js';
13
+ import { TerminalQuotaError } from './googleQuotaErrors.js';
13
14
  vi.mock('node:fs');
14
15
  // Update the description to reflect that this tests the retry utility's integration
15
16
  describe('Retry Utility Fallback Integration', () => {
16
17
  let config;
18
+ let mockGoogleApiError;
17
19
  beforeEach(() => {
18
20
  vi.mocked(fs.existsSync).mockReturnValue(true);
19
21
  vi.mocked(fs.statSync).mockReturnValue({
@@ -26,6 +28,11 @@ describe('Retry Utility Fallback Integration', () => {
26
28
  cwd: '/test',
27
29
  model: 'gemini-2.5-pro',
28
30
  });
31
+ mockGoogleApiError = {
32
+ code: 429,
33
+ message: 'mock error',
34
+ details: [],
35
+ };
29
36
  // Reset simulation state for each test
30
37
  setSimulate429(false);
31
38
  resetRequestCounter();
@@ -37,75 +44,49 @@ describe('Retry Utility Fallback Integration', () => {
37
44
  // Use the generalized setter
38
45
  config.setFallbackModelHandler(fallbackHandler);
39
46
  // Call the handler directly via the config property
40
- const result = await config.fallbackModelHandler('gemini-2.5-pro', DEFAULT_GEMINI_FLASH_MODEL);
47
+ const result = await config.fallbackModelHandler('gemini-2.5-pro', DEFAULT_GEMINI_FLASH_MODEL, new Error('test'));
41
48
  // Verify it returns the correct intent
42
49
  expect(result).toBe('retry');
43
50
  });
44
51
  // This test validates the retry utility's logic for triggering the callback.
45
- it('should trigger onPersistent429 after 2 consecutive 429 errors for OAuth users', async () => {
52
+ it('should trigger onPersistent429 on TerminalQuotaError for OAuth users', async () => {
46
53
  let fallbackCalled = false;
47
- // Removed fallbackModel variable as it's no longer relevant here.
48
- // Mock function that simulates exactly 2 429 errors, then succeeds after fallback
49
54
  const mockApiCall = vi
50
55
  .fn()
51
- .mockRejectedValueOnce(createSimulated429Error())
52
- .mockRejectedValueOnce(createSimulated429Error())
56
+ .mockRejectedValueOnce(new TerminalQuotaError('Daily limit', mockGoogleApiError))
57
+ .mockRejectedValueOnce(new TerminalQuotaError('Daily limit', mockGoogleApiError))
53
58
  .mockResolvedValueOnce('success after fallback');
54
- // Mock the onPersistent429 callback (this is what client.ts/geminiChat.ts provides)
55
59
  const mockPersistent429Callback = vi.fn(async (_authType) => {
56
60
  fallbackCalled = true;
57
- // Return true to signal retryWithBackoff to reset attempts and continue.
58
61
  return true;
59
62
  });
60
- // Test with OAuth personal auth type, with maxAttempts = 2 to ensure fallback triggers
61
63
  const result = await retryWithBackoff(mockApiCall, {
62
64
  maxAttempts: 2,
63
65
  initialDelayMs: 1,
64
66
  maxDelayMs: 10,
65
- shouldRetryOnError: (error) => {
66
- const status = error.status;
67
- return status === 429;
68
- },
69
67
  onPersistent429: mockPersistent429Callback,
70
68
  authType: AuthType.LOGIN_WITH_GOOGLE,
71
69
  });
72
- // Verify fallback mechanism was triggered
73
70
  expect(fallbackCalled).toBe(true);
74
- expect(mockPersistent429Callback).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE, expect.any(Error));
71
+ expect(mockPersistent429Callback).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE, expect.any(TerminalQuotaError));
75
72
  expect(result).toBe('success after fallback');
76
- // Should have: 2 failures, then fallback triggered, then 1 success after retry reset
77
73
  expect(mockApiCall).toHaveBeenCalledTimes(3);
78
74
  });
79
75
  it('should not trigger onPersistent429 for API key users', async () => {
80
- let fallbackCalled = false;
81
- // Mock function that simulates 429 errors
82
- const mockApiCall = vi.fn().mockRejectedValue(createSimulated429Error());
83
- // Mock the callback
84
- const mockPersistent429Callback = vi.fn(async () => {
85
- fallbackCalled = true;
86
- return true;
76
+ const fallbackCallback = vi.fn();
77
+ const mockApiCall = vi
78
+ .fn()
79
+ .mockRejectedValueOnce(new TerminalQuotaError('Daily limit', mockGoogleApiError));
80
+ const promise = retryWithBackoff(mockApiCall, {
81
+ maxAttempts: 2,
82
+ initialDelayMs: 1,
83
+ maxDelayMs: 10,
84
+ onPersistent429: fallbackCallback,
85
+ authType: AuthType.USE_GEMINI, // API key auth type
87
86
  });
88
- // Test with API key auth type - should not trigger fallback
89
- try {
90
- await retryWithBackoff(mockApiCall, {
91
- maxAttempts: 5,
92
- initialDelayMs: 10,
93
- maxDelayMs: 100,
94
- shouldRetryOnError: (error) => {
95
- const status = error.status;
96
- return status === 429;
97
- },
98
- onPersistent429: mockPersistent429Callback,
99
- authType: AuthType.USE_GEMINI, // API key auth type
100
- });
101
- }
102
- catch (error) {
103
- // Expected to throw after max attempts
104
- expect(error.message).toContain('Rate limit exceeded');
105
- }
106
- // Verify fallback was NOT triggered for API key users
107
- expect(fallbackCalled).toBe(false);
108
- expect(mockPersistent429Callback).not.toHaveBeenCalled();
87
+ await expect(promise).rejects.toThrow('Daily limit');
88
+ expect(fallbackCallback).not.toHaveBeenCalled();
89
+ expect(mockApiCall).toHaveBeenCalledTimes(1);
109
90
  });
110
91
  // This test validates the test utilities themselves.
111
92
  it('should properly disable simulation state after fallback (Test Utility)', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"flashFallback.test.js","sourceRoot":"","sources":["../../../src/utils/flashFallback.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"flashFallback.test.js","sourceRoot":"","sources":["../../../src/utils/flashFallback.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * @fileoverview
8
+ * This file contains types and functions for parsing structured Google API errors.
9
+ */
10
+ /**
11
+ * Based on google/rpc/error_details.proto
12
+ */
13
+ export interface ErrorInfo {
14
+ '@type': 'type.googleapis.com/google.rpc.ErrorInfo';
15
+ reason: string;
16
+ domain: string;
17
+ metadata: {
18
+ [key: string]: string;
19
+ };
20
+ }
21
+ export interface RetryInfo {
22
+ '@type': 'type.googleapis.com/google.rpc.RetryInfo';
23
+ retryDelay: string;
24
+ }
25
+ export interface DebugInfo {
26
+ '@type': 'type.googleapis.com/google.rpc.DebugInfo';
27
+ stackEntries: string[];
28
+ detail: string;
29
+ }
30
+ export interface QuotaFailure {
31
+ '@type': 'type.googleapis.com/google.rpc.QuotaFailure';
32
+ violations: Array<{
33
+ subject: string;
34
+ description: string;
35
+ apiService?: string;
36
+ quotaMetric?: string;
37
+ quotaId?: string;
38
+ quotaDimensions?: {
39
+ [key: string]: string;
40
+ };
41
+ quotaValue?: number;
42
+ futureQuotaValue?: number;
43
+ }>;
44
+ }
45
+ export interface PreconditionFailure {
46
+ '@type': 'type.googleapis.com/google.rpc.PreconditionFailure';
47
+ violations: Array<{
48
+ type: string;
49
+ subject: string;
50
+ description: string;
51
+ }>;
52
+ }
53
+ export interface LocalizedMessage {
54
+ '@type': 'type.googleapis.com/google.rpc.LocalizedMessage';
55
+ locale: string;
56
+ message: string;
57
+ }
58
+ export interface BadRequest {
59
+ '@type': 'type.googleapis.com/google.rpc.BadRequest';
60
+ fieldViolations: Array<{
61
+ field: string;
62
+ description: string;
63
+ reason?: string;
64
+ localizedMessage?: LocalizedMessage;
65
+ }>;
66
+ }
67
+ export interface RequestInfo {
68
+ '@type': 'type.googleapis.com/google.rpc.RequestInfo';
69
+ requestId: string;
70
+ servingData: string;
71
+ }
72
+ export interface ResourceInfo {
73
+ '@type': 'type.googleapis.com/google.rpc.ResourceInfo';
74
+ resourceType: string;
75
+ resourceName: string;
76
+ owner: string;
77
+ description: string;
78
+ }
79
+ export interface Help {
80
+ '@type': 'type.googleapis.com/google.rpc.Help';
81
+ links: Array<{
82
+ description: string;
83
+ url: string;
84
+ }>;
85
+ }
86
+ export type GoogleApiErrorDetail = ErrorInfo | RetryInfo | DebugInfo | QuotaFailure | PreconditionFailure | BadRequest | RequestInfo | ResourceInfo | Help | LocalizedMessage;
87
+ export interface GoogleApiError {
88
+ code: number;
89
+ message: string;
90
+ details: GoogleApiErrorDetail[];
91
+ }
92
+ /**
93
+ * Parses an error object to check if it's a structured Google API error
94
+ * and extracts all details.
95
+ *
96
+ * This function can handle two formats:
97
+ * 1. Standard Google API errors where `details` is a top-level field.
98
+ * 2. Errors where the entire structured error object is stringified inside
99
+ * the `message` field of a wrapper error.
100
+ *
101
+ * @param error The error object to inspect.
102
+ * @returns A GoogleApiError object if the error matches, otherwise null.
103
+ */
104
+ export declare function parseGoogleApiError(error: unknown): GoogleApiError | null;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * Parses an error object to check if it's a structured Google API error
8
+ * and extracts all details.
9
+ *
10
+ * This function can handle two formats:
11
+ * 1. Standard Google API errors where `details` is a top-level field.
12
+ * 2. Errors where the entire structured error object is stringified inside
13
+ * the `message` field of a wrapper error.
14
+ *
15
+ * @param error The error object to inspect.
16
+ * @returns A GoogleApiError object if the error matches, otherwise null.
17
+ */
18
+ export function parseGoogleApiError(error) {
19
+ if (!error) {
20
+ return null;
21
+ }
22
+ let errorObj = error;
23
+ // If error is a string, try to parse it.
24
+ if (typeof errorObj === 'string') {
25
+ try {
26
+ errorObj = JSON.parse(errorObj);
27
+ }
28
+ catch (_) {
29
+ // Not a JSON string, can't parse.
30
+ return null;
31
+ }
32
+ }
33
+ if (typeof errorObj !== 'object' || errorObj === null) {
34
+ return null;
35
+ }
36
+ const gaxiosError = errorObj;
37
+ let outerError;
38
+ if (gaxiosError.response?.data) {
39
+ if (typeof gaxiosError.response.data === 'string') {
40
+ try {
41
+ const parsedData = JSON.parse(gaxiosError.response.data);
42
+ if (parsedData.error) {
43
+ outerError = parsedData.error;
44
+ }
45
+ }
46
+ catch (_) {
47
+ // Not a JSON string, or doesn't contain .error
48
+ }
49
+ }
50
+ else if (typeof gaxiosError.response.data === 'object' &&
51
+ gaxiosError.response.data !== null) {
52
+ outerError = gaxiosError.response.data.error;
53
+ }
54
+ }
55
+ const responseStatus = gaxiosError.response?.status;
56
+ if (!outerError) {
57
+ // If the gaxios structure isn't there, check for a top-level `error` property.
58
+ if (gaxiosError.error) {
59
+ outerError = gaxiosError.error;
60
+ }
61
+ else {
62
+ return null;
63
+ }
64
+ }
65
+ let currentError = outerError;
66
+ let depth = 0;
67
+ const maxDepth = 10;
68
+ // Handle cases where the actual error object is stringified inside the message
69
+ // by drilling down until we find an error that doesn't have a stringified message.
70
+ while (typeof currentError.message === 'string' && depth < maxDepth) {
71
+ try {
72
+ const parsedMessage = JSON.parse(currentError.message);
73
+ if (parsedMessage.error) {
74
+ currentError = parsedMessage.error;
75
+ depth++;
76
+ }
77
+ else {
78
+ // The message is a JSON string, but not a nested error object.
79
+ break;
80
+ }
81
+ }
82
+ catch (_) {
83
+ // It wasn't a JSON string, so we've drilled down as far as we can.
84
+ break;
85
+ }
86
+ }
87
+ const code = responseStatus ?? currentError.code ?? gaxiosError.code;
88
+ const message = currentError.message;
89
+ const errorDetails = currentError.details;
90
+ if (Array.isArray(errorDetails) && code && message) {
91
+ const details = [];
92
+ for (const detail of errorDetails) {
93
+ if (detail && typeof detail === 'object' && '@type' in detail) {
94
+ // We can just cast it; the consumer will have to switch on @type
95
+ details.push(detail);
96
+ }
97
+ }
98
+ if (details.length > 0) {
99
+ return {
100
+ code,
101
+ message,
102
+ details,
103
+ };
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ //# sourceMappingURL=googleErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"googleErrors.js","sourceRoot":"","sources":["../../../src/utils/googleErrors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export {};
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { parseGoogleApiError } from './googleErrors.js';
8
+ describe('parseGoogleApiError', () => {
9
+ it('should return null for non-gaxios errors', () => {
10
+ expect(parseGoogleApiError(new Error('vanilla error'))).toBeNull();
11
+ expect(parseGoogleApiError(null)).toBeNull();
12
+ expect(parseGoogleApiError({})).toBeNull();
13
+ });
14
+ it('should parse a standard gaxios error', () => {
15
+ const mockError = {
16
+ response: {
17
+ status: 429,
18
+ data: {
19
+ error: {
20
+ code: 429,
21
+ message: 'Quota exceeded',
22
+ details: [
23
+ {
24
+ '@type': 'type.googleapis.com/google.rpc.QuotaFailure',
25
+ violations: [{ subject: 'user', description: 'daily limit' }],
26
+ },
27
+ ],
28
+ },
29
+ },
30
+ },
31
+ };
32
+ const parsed = parseGoogleApiError(mockError);
33
+ expect(parsed).not.toBeNull();
34
+ expect(parsed?.code).toBe(429);
35
+ expect(parsed?.message).toBe('Quota exceeded');
36
+ expect(parsed?.details).toHaveLength(1);
37
+ const detail = parsed?.details[0];
38
+ expect(detail['@type']).toBe('type.googleapis.com/google.rpc.QuotaFailure');
39
+ expect(detail.violations[0].description).toBe('daily limit');
40
+ });
41
+ it('should parse an error with details stringified in the message', () => {
42
+ const innerError = {
43
+ error: {
44
+ code: 429,
45
+ message: 'Inner quota message',
46
+ details: [
47
+ {
48
+ '@type': 'type.googleapis.com/google.rpc.RetryInfo',
49
+ retryDelay: '10s',
50
+ },
51
+ ],
52
+ },
53
+ };
54
+ const mockError = {
55
+ response: {
56
+ status: 429,
57
+ data: {
58
+ error: {
59
+ code: 429,
60
+ message: JSON.stringify(innerError),
61
+ details: [], // Top-level details are empty
62
+ },
63
+ },
64
+ },
65
+ };
66
+ const parsed = parseGoogleApiError(mockError);
67
+ expect(parsed).not.toBeNull();
68
+ expect(parsed?.code).toBe(429);
69
+ expect(parsed?.message).toBe('Inner quota message');
70
+ expect(parsed?.details).toHaveLength(1);
71
+ expect(parsed?.details[0]['@type']).toBe('type.googleapis.com/google.rpc.RetryInfo');
72
+ });
73
+ it('should return null if details are not in the expected format', () => {
74
+ const mockError = {
75
+ response: {
76
+ status: 400,
77
+ data: {
78
+ error: {
79
+ code: 400,
80
+ message: 'Bad Request',
81
+ details: 'just a string', // Invalid details format
82
+ },
83
+ },
84
+ },
85
+ };
86
+ expect(parseGoogleApiError(mockError)).toBeNull();
87
+ });
88
+ it('should return null if there are no valid details', () => {
89
+ const mockError = {
90
+ response: {
91
+ status: 400,
92
+ data: {
93
+ error: {
94
+ code: 400,
95
+ message: 'Bad Request',
96
+ details: [
97
+ {
98
+ // missing '@type'
99
+ reason: 'some reason',
100
+ },
101
+ ],
102
+ },
103
+ },
104
+ },
105
+ };
106
+ expect(parseGoogleApiError(mockError)).toBeNull();
107
+ });
108
+ it('should parse a doubly nested error in the message', () => {
109
+ const innerError = {
110
+ error: {
111
+ code: 429,
112
+ message: 'Innermost quota message',
113
+ details: [
114
+ {
115
+ '@type': 'type.googleapis.com/google.rpc.RetryInfo',
116
+ retryDelay: '20s',
117
+ },
118
+ ],
119
+ },
120
+ };
121
+ const middleError = {
122
+ error: {
123
+ code: 429,
124
+ message: JSON.stringify(innerError),
125
+ details: [],
126
+ },
127
+ };
128
+ const mockError = {
129
+ response: {
130
+ status: 429,
131
+ data: {
132
+ error: {
133
+ code: 429,
134
+ message: JSON.stringify(middleError),
135
+ details: [],
136
+ },
137
+ },
138
+ },
139
+ };
140
+ const parsed = parseGoogleApiError(mockError);
141
+ expect(parsed).not.toBeNull();
142
+ expect(parsed?.code).toBe(429);
143
+ expect(parsed?.message).toBe('Innermost quota message');
144
+ expect(parsed?.details).toHaveLength(1);
145
+ expect(parsed?.details[0]['@type']).toBe('type.googleapis.com/google.rpc.RetryInfo');
146
+ });
147
+ it('should parse an error that is not in a response object', () => {
148
+ const innerError = {
149
+ error: {
150
+ code: 429,
151
+ message: 'Innermost quota message',
152
+ details: [
153
+ {
154
+ '@type': 'type.googleapis.com/google.rpc.RetryInfo',
155
+ retryDelay: '20s',
156
+ },
157
+ ],
158
+ },
159
+ };
160
+ const mockError = {
161
+ error: {
162
+ code: 429,
163
+ message: JSON.stringify(innerError),
164
+ details: [],
165
+ },
166
+ };
167
+ const parsed = parseGoogleApiError(mockError);
168
+ expect(parsed).not.toBeNull();
169
+ expect(parsed?.code).toBe(429);
170
+ expect(parsed?.message).toBe('Innermost quota message');
171
+ expect(parsed?.details).toHaveLength(1);
172
+ expect(parsed?.details[0]['@type']).toBe('type.googleapis.com/google.rpc.RetryInfo');
173
+ });
174
+ it('should parse an error that is a JSON string', () => {
175
+ const innerError = {
176
+ error: {
177
+ code: 429,
178
+ message: 'Innermost quota message',
179
+ details: [
180
+ {
181
+ '@type': 'type.googleapis.com/google.rpc.RetryInfo',
182
+ retryDelay: '20s',
183
+ },
184
+ ],
185
+ },
186
+ };
187
+ const mockError = {
188
+ error: {
189
+ code: 429,
190
+ message: JSON.stringify(innerError),
191
+ details: [],
192
+ },
193
+ };
194
+ const parsed = parseGoogleApiError(JSON.stringify(mockError));
195
+ expect(parsed).not.toBeNull();
196
+ expect(parsed?.code).toBe(429);
197
+ expect(parsed?.message).toBe('Innermost quota message');
198
+ expect(parsed?.details).toHaveLength(1);
199
+ expect(parsed?.details[0]['@type']).toBe('type.googleapis.com/google.rpc.RetryInfo');
200
+ });
201
+ it('should parse the user-provided nested error string', () => {
202
+ const userErrorString = '{"error":{"message":"{\\n \\"error\\": {\\n \\"code\\": 429,\\n \\"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.\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_paid_tier_input_token_count, limit: 10000\\\\nPlease retry in 40.025771073s.\\",\\n \\"status\\": \\"RESOURCE_EXHAUSTED\\",\\n \\"details\\": [\\n {\\n \\"@type\\": \\"type.googleapis.com/google.rpc.DebugInfo\\",\\n \\"detail\\": \\"[ORIGINAL ERROR] generic::resource_exhausted: 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.\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_paid_tier_input_token_count, limit: 10000\\\\nPlease retry in 40.025771073s. [google.rpc.error_details_ext] { 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.\\\\\\\\n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_paid_tier_input_token_count, limit: 10000\\\\\\\\nPlease retry in 40.025771073s.\\\\\\" }\\"\\n },\\n {\\n \\"@type\\": \\"type.googleapis.com/google.rpc.QuotaFailure\\",\\n \\"violations\\": [\\n {\\n \\"quotaMetric\\": \\"generativelanguage.googleapis.com/generate_content_paid_tier_input_token_count\\",\\n \\"quotaId\\": \\"GenerateContentPaidTierInputTokensPerModelPerMinute\\",\\n \\"quotaDimensions\\": {\\n \\"location\\": \\"global\\",\\n \\"model\\": \\"gemini-2.5-pro\\"\\n },\\n \\"quotaValue\\": \\"10000\\"\\n }\\n ]\\n },\\n {\\n \\"@type\\": \\"type.googleapis.com/google.rpc.Help\\",\\n \\"links\\": [\\n {\\n \\"description\\": \\"Learn more about Gemini API quotas\\",\\n \\"url\\": \\"https://ai.google.dev/gemini-api/docs/rate-limits\\"\\n }\\n ]\\n },\\n {\\n \\"@type\\": \\"type.googleapis.com/google.rpc.RetryInfo\\",\\n \\"retryDelay\\": \\"40s\\"\\n }\\n ]\\n }\\n}\\n","code":429,"status":"Too Many Requests"}}';
203
+ const parsed = parseGoogleApiError(userErrorString);
204
+ expect(parsed).not.toBeNull();
205
+ expect(parsed?.code).toBe(429);
206
+ expect(parsed?.message).toContain('You exceeded your current quota');
207
+ expect(parsed?.details).toHaveLength(4);
208
+ expect(parsed?.details.some((d) => d['@type'] === 'type.googleapis.com/google.rpc.QuotaFailure')).toBe(true);
209
+ expect(parsed?.details.some((d) => d['@type'] === 'type.googleapis.com/google.rpc.RetryInfo')).toBe(true);
210
+ });
211
+ });
212
+ //# sourceMappingURL=googleErrors.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"googleErrors.test.js","sourceRoot":"","sources":["../../../src/utils/googleErrors.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import type { GoogleApiError } from './googleErrors.js';
7
+ /**
8
+ * A non-retryable error indicating a hard quota limit has been reached (e.g., daily limit).
9
+ */
10
+ export declare class TerminalQuotaError extends Error {
11
+ readonly cause: GoogleApiError;
12
+ constructor(message: string, cause: GoogleApiError);
13
+ }
14
+ /**
15
+ * A retryable error indicating a temporary quota issue (e.g., per-minute limit).
16
+ */
17
+ export declare class RetryableQuotaError extends Error {
18
+ readonly cause: GoogleApiError;
19
+ retryDelayMs: number;
20
+ constructor(message: string, cause: GoogleApiError, retryDelaySeconds: number);
21
+ }
22
+ /**
23
+ * Analyzes a caught error and classifies it as a specific quota-related error if applicable.
24
+ *
25
+ * It decides whether an error is a `TerminalQuotaError` or a `RetryableQuotaError` based on
26
+ * the following logic:
27
+ * - If the error indicates a daily limit, it's a `TerminalQuotaError`.
28
+ * - If the error suggests a retry delay of more than 5 minutes, it's a `TerminalQuotaError`.
29
+ * - If the error suggests a retry delay of 5 minutes or less, it's a `RetryableQuotaError`.
30
+ * - If the error indicates a per-minute limit, it's a `RetryableQuotaError`.
31
+ *
32
+ * @param error The error to classify.
33
+ * @returns A `TerminalQuotaError`, `RetryableQuotaError`, or the original `unknown` error.
34
+ */
35
+ export declare function classifyGoogleError(error: unknown): unknown;