Skip to content
Merged
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
17 changes: 17 additions & 0 deletions packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@

setFallbackHandlerSpy = vi.spyOn(mockConfig, 'setFallbackModelHandler');
vi.spyOn(mockConfig, 'setQuotaErrorOccurred');
vi.spyOn(mockConfig, 'setModel');
});

afterEach(() => {
Expand Down Expand Up @@ -163,6 +164,9 @@
const intent = await promise!;
expect(intent).toBe('retry_always');

// Verify setModel was called with isFallbackModel=true
expect(mockConfig.setModel).toHaveBeenCalledWith('gemini-flash', true);

// The pending request should be cleared from the state
expect(result.current.proQuotaRequest).toBeNull();
expect(mockHistoryManager.addItem).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -274,6 +278,9 @@
const intent = await promise!;
expect(intent).toBe('retry_always');

// Verify setModel was called with isFallbackModel=true
expect(mockConfig.setModel).toHaveBeenCalledWith('model-B', true);

// The pending request should be cleared from the state
expect(result.current.proQuotaRequest).toBeNull();
expect(mockConfig.setQuotaErrorOccurred).toHaveBeenCalledWith(true);
Expand Down Expand Up @@ -304,21 +311,21 @@
const error = new ModelNotFoundError('model not found', 404);

act(() => {
promise = handler('gemini-3-pro-preview', 'gemini-2.5-pro', error);

Check warning on line 314 in packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Found sensitive keyword "gemini-3". Please make sure this change is appropriate to submit.
});

// The hook should now have a pending request for the UI to handle
const request = result.current.proQuotaRequest;
expect(request).not.toBeNull();
expect(request?.failedModel).toBe('gemini-3-pro-preview');

Check warning on line 320 in packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Found sensitive keyword "gemini-3". Please make sure this change is appropriate to submit.
expect(request?.isTerminalQuotaError).toBe(false);
expect(request?.isModelNotFoundError).toBe(true);

const message = request!.message;
expect(message).toBe(
`It seems like you don't have access to gemini-3-pro-preview.

Check warning on line 326 in packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Found sensitive keyword "gemini-3". Please make sure this change is appropriate to submit.
Learn more at https://goo.gle/enable-preview-features
To disable gemini-3-pro-preview, disable "Preview features" in /settings.`,

Check warning on line 328 in packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Found sensitive keyword "gemini-3". Please make sure this change is appropriate to submit.
);

// Simulate the user choosing to switch
Expand All @@ -328,6 +335,13 @@

const intent = await promise!;
expect(intent).toBe('retry_always');

// Verify setModel was called with isFallbackModel=true
expect(mockConfig.setModel).toHaveBeenCalledWith(
'gemini-2.5-pro',
true,
);

expect(result.current.proQuotaRequest).toBeNull();
});
});
Expand Down Expand Up @@ -411,6 +425,9 @@
expect(intent).toBe('retry_always');
expect(result.current.proQuotaRequest).toBeNull();

// Verify setModel was called with isFallbackModel=true
expect(mockConfig.setModel).toHaveBeenCalledWith('gemini-flash', true);

// Check for the "Switched to fallback model" message
expect(mockHistoryManager.addItem).toHaveBeenCalledTimes(1);
const lastCall = (mockHistoryManager.addItem as Mock).mock.calls[0][0];
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/src/ui/hooks/useQuotaAndFallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ export function useQuotaAndFallback({
isDialogPending.current = false; // Reset the flag here

if (choice === 'retry_always') {
// Explicitly set the model to the fallback model to persist the user's choice.
// Set the model to the fallback model for the current session.
// This ensures the Footer updates and future turns use this model.
config.setModel(proQuotaRequest.fallbackModel);
// The change is not persisted, so the original model is restored on restart.
config.setModel(proQuotaRequest.fallbackModel, true);

historyManager.addItem(
{
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@
it('should disable useWriteTodos for preview models', () => {
const params: ConfigParameters = {
...baseParams,
model: 'gemini-3-pro-preview',

Check warning on line 800 in packages/core/src/config/config.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Found sensitive keyword "gemini-3". Please make sure this change is appropriate to submit.
};
const config = new Config(params);
expect(config.getUseWriteTodos()).toBe(false);
Expand Down Expand Up @@ -1641,6 +1641,18 @@

expect(onModelChange).toHaveBeenCalledWith(DEFAULT_GEMINI_MODEL);
});

it('should NOT call onModelChange when a new model is set as a fallback', () => {
const onModelChange = vi.fn();
const config = new Config({
...baseParams,
onModelChange,
});

config.setModel(DEFAULT_GEMINI_MODEL, true);

expect(onModelChange).not.toHaveBeenCalled();
});
});
});

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,13 +876,13 @@ export class Config {
return this.model;
}

setModel(newModel: string): void {
setModel(newModel: string, isFallbackModel: boolean = false): void {
if (this.model !== newModel || this._activeModel !== newModel) {
this.model = newModel;
// When the user explicitly sets a model, that becomes the active model.
this._activeModel = newModel;
coreEvents.emitModelChanged(newModel);
if (this.onModelChange) {
if (this.onModelChange && !isFallbackModel) {
this.onModelChange(newModel);
}
}
Expand Down