Skip to content

Commit 996c9f5

Browse files
authored
Revert "fix: handle request retries and model fallback correctly" (#11164)
1 parent bd5c158 commit 996c9f5

13 files changed

+807
-999
lines changed

packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,25 @@ import {
1919
type FallbackModelHandler,
2020
UserTierId,
2121
AuthType,
22-
TerminalQuotaError,
22+
isGenericQuotaExceededError,
23+
isProQuotaExceededError,
2324
makeFakeConfig,
24-
type GoogleApiError,
2525
} from '@google/gemini-cli-core';
2626
import { useQuotaAndFallback } from './useQuotaAndFallback.js';
2727
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
2828
import { AuthState, MessageType } from '../types.js';
2929

30+
// Mock the error checking functions from the core package to control test scenarios
31+
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
32+
const original =
33+
await importOriginal<typeof import('@google/gemini-cli-core')>();
34+
return {
35+
...original,
36+
isGenericQuotaExceededError: vi.fn(),
37+
isProQuotaExceededError: vi.fn(),
38+
};
39+
});
40+
3041
// Use a type alias for SpyInstance as it's not directly exported
3142
type SpyInstance = ReturnType<typeof vi.spyOn>;
3243

@@ -36,15 +47,12 @@ describe('useQuotaAndFallback', () => {
3647
let mockSetAuthState: Mock;
3748
let mockSetModelSwitchedFromQuotaError: Mock;
3849
let setFallbackHandlerSpy: SpyInstance;
39-
let mockGoogleApiError: GoogleApiError;
50+
51+
const mockedIsGenericQuotaExceededError = isGenericQuotaExceededError as Mock;
52+
const mockedIsProQuotaExceededError = isProQuotaExceededError as Mock;
4053

4154
beforeEach(() => {
4255
mockConfig = makeFakeConfig();
43-
mockGoogleApiError = {
44-
code: 429,
45-
message: 'mock error',
46-
details: [],
47-
};
4856

4957
// Spy on the method that requires the private field and mock its return.
5058
// This is cleaner than modifying the config class for tests.
@@ -64,6 +72,9 @@ describe('useQuotaAndFallback', () => {
6472

6573
setFallbackHandlerSpy = vi.spyOn(mockConfig, 'setFallbackModelHandler');
6674
vi.spyOn(mockConfig, 'setQuotaErrorOccurred');
75+
76+
mockedIsGenericQuotaExceededError.mockReturnValue(false);
77+
mockedIsProQuotaExceededError.mockReturnValue(false);
6778
});
6879

6980
afterEach(() => {
@@ -128,6 +139,22 @@ describe('useQuotaAndFallback', () => {
128139

129140
describe('Automatic Fallback Scenarios', () => {
130141
const testCases = [
142+
{
143+
errorType: 'generic',
144+
tier: UserTierId.FREE,
145+
expectedMessageSnippets: [
146+
'Automatically switching from model-A to model-B',
147+
'upgrade to a Gemini Code Assist Standard or Enterprise plan',
148+
],
149+
},
150+
{
151+
errorType: 'generic',
152+
tier: UserTierId.STANDARD, // Paid tier
153+
expectedMessageSnippets: [
154+
'Automatically switching from model-A to model-B',
155+
'switch to using a paid API key from AI Studio',
156+
],
157+
},
131158
{
132159
errorType: 'other',
133160
tier: UserTierId.FREE,
@@ -148,11 +175,15 @@ describe('useQuotaAndFallback', () => {
148175

149176
for (const { errorType, tier, expectedMessageSnippets } of testCases) {
150177
it(`should handle ${errorType} error for ${tier} tier correctly`, async () => {
178+
mockedIsGenericQuotaExceededError.mockReturnValue(
179+
errorType === 'generic',
180+
);
181+
151182
const handler = getRegisteredHandler(tier);
152183
const result = await handler(
153184
'model-A',
154185
'model-B',
155-
new Error('some error'),
186+
new Error('quota exceeded'),
156187
);
157188

158189
// Automatic fallbacks should return 'stop'
@@ -176,6 +207,10 @@ describe('useQuotaAndFallback', () => {
176207
});
177208

178209
describe('Interactive Fallback (Pro Quota Error)', () => {
210+
beforeEach(() => {
211+
mockedIsProQuotaExceededError.mockReturnValue(true);
212+
});
213+
179214
it('should set an interactive request and wait for user choice', async () => {
180215
const { result } = renderHook(() =>
181216
useQuotaAndFallback({
@@ -194,7 +229,7 @@ describe('useQuotaAndFallback', () => {
194229
const promise = handler(
195230
'gemini-pro',
196231
'gemini-flash',
197-
new TerminalQuotaError('pro quota', mockGoogleApiError),
232+
new Error('pro quota'),
198233
);
199234

200235
await act(async () => {});
@@ -233,7 +268,7 @@ describe('useQuotaAndFallback', () => {
233268
const promise1 = handler(
234269
'gemini-pro',
235270
'gemini-flash',
236-
new TerminalQuotaError('pro quota 1', mockGoogleApiError),
271+
new Error('pro quota 1'),
237272
);
238273
await act(async () => {});
239274

@@ -243,7 +278,7 @@ describe('useQuotaAndFallback', () => {
243278
const result2 = await handler(
244279
'gemini-pro',
245280
'gemini-flash',
246-
new TerminalQuotaError('pro quota 2', mockGoogleApiError),
281+
new Error('pro quota 2'),
247282
);
248283

249284
// The lock should have stopped the second request
@@ -262,6 +297,10 @@ describe('useQuotaAndFallback', () => {
262297
});
263298

264299
describe('handleProQuotaChoice', () => {
300+
beforeEach(() => {
301+
mockedIsProQuotaExceededError.mockReturnValue(true);
302+
});
303+
265304
it('should do nothing if there is no pending pro quota request', () => {
266305
const { result } = renderHook(() =>
267306
useQuotaAndFallback({
@@ -297,7 +336,7 @@ describe('useQuotaAndFallback', () => {
297336
const promise = handler(
298337
'gemini-pro',
299338
'gemini-flash',
300-
new TerminalQuotaError('pro quota', mockGoogleApiError),
339+
new Error('pro quota'),
301340
);
302341
await act(async () => {}); // Allow state to update
303342

@@ -328,7 +367,7 @@ describe('useQuotaAndFallback', () => {
328367
const promise = handler(
329368
'gemini-pro',
330369
'gemini-flash',
331-
new TerminalQuotaError('pro quota', mockGoogleApiError),
370+
new Error('pro quota'),
332371
);
333372
await act(async () => {}); // Allow state to update
334373

packages/cli/src/ui/hooks/useQuotaAndFallback.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
type Config,
1010
type FallbackModelHandler,
1111
type FallbackIntent,
12-
TerminalQuotaError,
12+
isGenericQuotaExceededError,
13+
isProQuotaExceededError,
1314
UserTierId,
1415
} from '@google/gemini-cli-core';
1516
import { useCallback, useEffect, useRef, useState } from 'react';
@@ -62,7 +63,7 @@ export function useQuotaAndFallback({
6263

6364
let message: string;
6465

65-
if (error instanceof TerminalQuotaError) {
66+
if (error && isProQuotaExceededError(error)) {
6667
// Pro Quota specific messages (Interactive)
6768
if (isPaidTier) {
6869
message = `⚡ You have reached your daily ${failedModel} quota limit.
@@ -73,6 +74,19 @@ export function useQuotaAndFallback({
7374
⚡ You can choose to authenticate with a paid API key or continue with the fallback model.
7475
⚡ 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
7576
⚡ Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
77+
⚡ You can switch authentication methods by typing /auth`;
78+
}
79+
} else if (error && isGenericQuotaExceededError(error)) {
80+
// Generic Quota (Automatic fallback)
81+
const actionMessage = `⚡ You have reached your daily quota limit.\n⚡ Automatically switching from ${failedModel} to ${fallbackModel} for the remainder of this session.`;
82+
83+
if (isPaidTier) {
84+
message = `${actionMessage}
85+
⚡ To continue accessing the ${failedModel} model today, consider using /auth to switch to using a paid API key from AI Studio at https://aistudio.google.com/apikey`;
86+
} else {
87+
message = `${actionMessage}
88+
⚡ 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
89+
⚡ Or you can utilize a Gemini API Key. See: https://goo.gle/gemini-cli-docs-auth#gemini-api-key
7690
⚡ You can switch authentication methods by typing /auth`;
7791
}
7892
} else {
@@ -105,7 +119,7 @@ export function useQuotaAndFallback({
105119
config.setQuotaErrorOccurred(true);
106120

107121
// Interactive Fallback for Pro quota
108-
if (error instanceof TerminalQuotaError) {
122+
if (error && isProQuotaExceededError(error)) {
109123
if (isDialogPending.current) {
110124
return 'stop'; // A dialog is already active, so just stop this request.
111125
}

packages/core/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,3 @@ export { makeFakeConfig } from './src/test-utils/config.js';
4444
export * from './src/utils/pathReader.js';
4545
export { ClearcutLogger } from './src/telemetry/clearcut-logger/clearcut-logger.js';
4646
export { logModelSlashCommand } from './src/telemetry/loggers.js';
47-
export * from './src/utils/googleQuotaErrors.js';
48-
export type { GoogleApiError } from './src/utils/googleErrors.js';

0 commit comments

Comments
 (0)