mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-01 15:34:29 -07:00
fix: update currentSequenceModel when modelChanged (#17051)
This commit is contained in:
@@ -166,11 +166,6 @@ describe('useQuotaAndFallback', () => {
|
|||||||
const intent = await promise!;
|
const intent = await promise!;
|
||||||
expect(intent).toBe('retry_always');
|
expect(intent).toBe('retry_always');
|
||||||
|
|
||||||
// Verify activateFallbackMode was called
|
|
||||||
expect(mockConfig.activateFallbackMode).toHaveBeenCalledWith(
|
|
||||||
'gemini-flash',
|
|
||||||
);
|
|
||||||
|
|
||||||
// The pending request should be cleared from the state
|
// The pending request should be cleared from the state
|
||||||
expect(result.current.proQuotaRequest).toBeNull();
|
expect(result.current.proQuotaRequest).toBeNull();
|
||||||
expect(mockHistoryManager.addItem).toHaveBeenCalledTimes(1);
|
expect(mockHistoryManager.addItem).toHaveBeenCalledTimes(1);
|
||||||
@@ -282,11 +277,6 @@ describe('useQuotaAndFallback', () => {
|
|||||||
const intent = await promise!;
|
const intent = await promise!;
|
||||||
expect(intent).toBe('retry_always');
|
expect(intent).toBe('retry_always');
|
||||||
|
|
||||||
// Verify activateFallbackMode was called
|
|
||||||
expect(mockConfig.activateFallbackMode).toHaveBeenCalledWith(
|
|
||||||
'model-B',
|
|
||||||
);
|
|
||||||
|
|
||||||
// The pending request should be cleared from the state
|
// The pending request should be cleared from the state
|
||||||
expect(result.current.proQuotaRequest).toBeNull();
|
expect(result.current.proQuotaRequest).toBeNull();
|
||||||
expect(mockConfig.setQuotaErrorOccurred).toHaveBeenCalledWith(true);
|
expect(mockConfig.setQuotaErrorOccurred).toHaveBeenCalledWith(true);
|
||||||
@@ -342,11 +332,6 @@ To disable gemini-3-pro-preview, disable "Preview features" in /settings.`,
|
|||||||
const intent = await promise!;
|
const intent = await promise!;
|
||||||
expect(intent).toBe('retry_always');
|
expect(intent).toBe('retry_always');
|
||||||
|
|
||||||
// Verify activateFallbackMode was called
|
|
||||||
expect(mockConfig.activateFallbackMode).toHaveBeenCalledWith(
|
|
||||||
'gemini-2.5-pro',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.current.proQuotaRequest).toBeNull();
|
expect(result.current.proQuotaRequest).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -430,11 +415,6 @@ To disable gemini-3-pro-preview, disable "Preview features" in /settings.`,
|
|||||||
expect(intent).toBe('retry_always');
|
expect(intent).toBe('retry_always');
|
||||||
expect(result.current.proQuotaRequest).toBeNull();
|
expect(result.current.proQuotaRequest).toBeNull();
|
||||||
|
|
||||||
// Verify activateFallbackMode was called
|
|
||||||
expect(mockConfig.activateFallbackMode).toHaveBeenCalledWith(
|
|
||||||
'gemini-flash',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify quota error flags are reset
|
// Verify quota error flags are reset
|
||||||
expect(mockSetModelSwitchedFromQuotaError).toHaveBeenCalledWith(false);
|
expect(mockSetModelSwitchedFromQuotaError).toHaveBeenCalledWith(false);
|
||||||
expect(mockConfig.setQuotaErrorOccurred).toHaveBeenCalledWith(false);
|
expect(mockConfig.setQuotaErrorOccurred).toHaveBeenCalledWith(false);
|
||||||
|
|||||||
@@ -135,10 +135,6 @@ export function useQuotaAndFallback({
|
|||||||
config.setQuotaErrorOccurred(false);
|
config.setQuotaErrorOccurred(false);
|
||||||
|
|
||||||
if (choice === 'retry_always') {
|
if (choice === 'retry_always') {
|
||||||
// Set the model to the fallback model for the current session.
|
|
||||||
// This ensures the Footer updates and future turns use this model.
|
|
||||||
// The change is not persisted, so the original model is restored on restart.
|
|
||||||
config.activateFallbackMode(proQuotaRequest.fallbackModel);
|
|
||||||
historyManager.addItem(
|
historyManager.addItem(
|
||||||
{
|
{
|
||||||
type: MessageType.INFO,
|
type: MessageType.INFO,
|
||||||
|
|||||||
@@ -2037,9 +2037,8 @@ export class Config {
|
|||||||
*/
|
*/
|
||||||
async dispose(): Promise<void> {
|
async dispose(): Promise<void> {
|
||||||
coreEvents.off(CoreEvent.AgentsRefreshed, this.onAgentsRefreshed);
|
coreEvents.off(CoreEvent.AgentsRefreshed, this.onAgentsRefreshed);
|
||||||
if (this.agentRegistry) {
|
this.agentRegistry?.dispose();
|
||||||
this.agentRegistry.dispose();
|
this.geminiClient?.dispose();
|
||||||
}
|
|
||||||
if (this.mcpClientManager) {
|
if (this.mcpClientManager) {
|
||||||
await this.mcpClientManager.stop();
|
await this.mcpClientManager.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import type {
|
|||||||
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
||||||
import * as policyCatalog from '../availability/policyCatalog.js';
|
import * as policyCatalog from '../availability/policyCatalog.js';
|
||||||
import { partToString } from '../utils/partUtils.js';
|
import { partToString } from '../utils/partUtils.js';
|
||||||
|
import { coreEvents } from '../utils/events.js';
|
||||||
|
|
||||||
// Mock fs module to prevent actual file system operations during tests
|
// Mock fs module to prevent actual file system operations during tests
|
||||||
const mockFileSystem = new Map<string, string>();
|
const mockFileSystem = new Map<string, string>();
|
||||||
@@ -281,6 +282,7 @@ describe('Gemini Client (client.ts)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
client.dispose();
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1757,6 +1759,55 @@ ${JSON.stringify(
|
|||||||
expect.any(AbortSignal),
|
expect.any(AbortSignal),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should re-route within the same prompt when the configured model changes', async () => {
|
||||||
|
mockTurnRunFn.mockClear();
|
||||||
|
mockTurnRunFn.mockImplementation(async function* () {
|
||||||
|
yield { type: 'content', value: 'Hello' };
|
||||||
|
});
|
||||||
|
|
||||||
|
mockRouterService.route.mockResolvedValueOnce({
|
||||||
|
model: 'original-model',
|
||||||
|
reason: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
let stream = client.sendMessageStream(
|
||||||
|
[{ text: 'Hi' }],
|
||||||
|
new AbortController().signal,
|
||||||
|
'prompt-1',
|
||||||
|
);
|
||||||
|
await fromAsync(stream);
|
||||||
|
|
||||||
|
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockTurnRunFn).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
{ model: 'original-model' },
|
||||||
|
[{ text: 'Hi' }],
|
||||||
|
expect.any(AbortSignal),
|
||||||
|
);
|
||||||
|
|
||||||
|
mockRouterService.route.mockResolvedValue({
|
||||||
|
model: 'fallback-model',
|
||||||
|
reason: 'test',
|
||||||
|
});
|
||||||
|
vi.mocked(mockConfig.getModel).mockReturnValue('gemini-2.5-flash');
|
||||||
|
coreEvents.emitModelChanged('gemini-2.5-flash');
|
||||||
|
|
||||||
|
stream = client.sendMessageStream(
|
||||||
|
[{ text: 'Continue' }],
|
||||||
|
new AbortController().signal,
|
||||||
|
'prompt-1',
|
||||||
|
);
|
||||||
|
await fromAsync(stream);
|
||||||
|
|
||||||
|
expect(mockRouterService.route).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockTurnRunFn).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
{ model: 'fallback-model' },
|
||||||
|
[{ text: 'Continue' }],
|
||||||
|
expect.any(AbortSignal),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use getGlobalMemory for system instruction when JIT is enabled', async () => {
|
it('should use getGlobalMemory for system instruction when JIT is enabled', async () => {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import {
|
|||||||
import { resolveModel } from '../config/models.js';
|
import { resolveModel } from '../config/models.js';
|
||||||
import type { RetryAvailabilityContext } from '../utils/retry.js';
|
import type { RetryAvailabilityContext } from '../utils/retry.js';
|
||||||
import { partToString } from '../utils/partUtils.js';
|
import { partToString } from '../utils/partUtils.js';
|
||||||
|
import { coreEvents, CoreEvent } from '../utils/events.js';
|
||||||
|
|
||||||
const MAX_TURNS = 100;
|
const MAX_TURNS = 100;
|
||||||
|
|
||||||
@@ -94,8 +95,14 @@ export class GeminiClient {
|
|||||||
this.loopDetector = new LoopDetectionService(config);
|
this.loopDetector = new LoopDetectionService(config);
|
||||||
this.compressionService = new ChatCompressionService();
|
this.compressionService = new ChatCompressionService();
|
||||||
this.lastPromptId = this.config.getSessionId();
|
this.lastPromptId = this.config.getSessionId();
|
||||||
|
|
||||||
|
coreEvents.on(CoreEvent.ModelChanged, this.handleModelChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleModelChanged = () => {
|
||||||
|
this.currentSequenceModel = null;
|
||||||
|
};
|
||||||
|
|
||||||
// Hook state to deduplicate BeforeAgent calls and track response for
|
// Hook state to deduplicate BeforeAgent calls and track response for
|
||||||
// AfterAgent
|
// AfterAgent
|
||||||
private hookStateMap = new Map<
|
private hookStateMap = new Map<
|
||||||
@@ -253,6 +260,10 @@ export class GeminiClient {
|
|||||||
this.updateTelemetryTokenCount();
|
this.updateTelemetryTokenCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
coreEvents.off(CoreEvent.ModelChanged, this.handleModelChanged);
|
||||||
|
}
|
||||||
|
|
||||||
async resumeChat(
|
async resumeChat(
|
||||||
history: Content[],
|
history: Content[],
|
||||||
resumedSessionData?: ResumedSessionData,
|
resumedSessionData?: ResumedSessionData,
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
|||||||
fallbackHandler: undefined,
|
fallbackHandler: undefined,
|
||||||
getFallbackModelHandler: vi.fn(),
|
getFallbackModelHandler: vi.fn(),
|
||||||
setActiveModel: vi.fn(),
|
setActiveModel: vi.fn(),
|
||||||
|
setModel: vi.fn(),
|
||||||
|
activateFallbackMode: vi.fn(),
|
||||||
getModelAvailabilityService: vi.fn(() =>
|
getModelAvailabilityService: vi.fn(() =>
|
||||||
createAvailabilityServiceMock({
|
createAvailabilityServiceMock({
|
||||||
selectedModel: FALLBACK_MODEL,
|
selectedModel: FALLBACK_MODEL,
|
||||||
@@ -198,7 +200,7 @@ describe('handleFallback', () => {
|
|||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
expect(policyConfig.getFallbackModelHandler).not.toHaveBeenCalled();
|
expect(policyConfig.getFallbackModelHandler).not.toHaveBeenCalled();
|
||||||
expect(policyConfig.setActiveModel).toHaveBeenCalledWith(
|
expect(policyConfig.activateFallbackMode).toHaveBeenCalledWith(
|
||||||
DEFAULT_GEMINI_FLASH_MODEL,
|
DEFAULT_GEMINI_FLASH_MODEL,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -273,7 +275,7 @@ describe('handleFallback', () => {
|
|||||||
expect(openBrowserSecurely).toHaveBeenCalledWith(
|
expect(openBrowserSecurely).toHaveBeenCalledWith(
|
||||||
'https://goo.gle/set-up-gemini-code-assist',
|
'https://goo.gle/set-up-gemini-code-assist',
|
||||||
);
|
);
|
||||||
expect(policyConfig.setActiveModel).not.toHaveBeenCalled();
|
expect(policyConfig.activateFallbackMode).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should catch errors from the handler, log an error, and return null', async () => {
|
it('should catch errors from the handler, log an error, and return null', async () => {
|
||||||
@@ -378,7 +380,7 @@ describe('handleFallback', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls setActiveModel and logs telemetry when handler returns "retry_always"', async () => {
|
it('calls activateFallbackMode when handler returns "retry_always"', async () => {
|
||||||
policyHandler.mockResolvedValue('retry_always');
|
policyHandler.mockResolvedValue('retry_always');
|
||||||
vi.mocked(policyConfig.getModel).mockReturnValue(
|
vi.mocked(policyConfig.getModel).mockReturnValue(
|
||||||
DEFAULT_GEMINI_MODEL_AUTO,
|
DEFAULT_GEMINI_MODEL_AUTO,
|
||||||
@@ -391,11 +393,13 @@ describe('handleFallback', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
expect(policyConfig.setActiveModel).toHaveBeenCalledWith(FALLBACK_MODEL);
|
expect(policyConfig.activateFallbackMode).toHaveBeenCalledWith(
|
||||||
|
FALLBACK_MODEL,
|
||||||
|
);
|
||||||
// TODO: add logging expect statement
|
// TODO: add logging expect statement
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does NOT call setActiveModel when handler returns "stop"', async () => {
|
it('does NOT call activateFallbackMode when handler returns "stop"', async () => {
|
||||||
policyHandler.mockResolvedValue('stop');
|
policyHandler.mockResolvedValue('stop');
|
||||||
|
|
||||||
const result = await handleFallback(
|
const result = await handleFallback(
|
||||||
@@ -405,11 +409,11 @@ describe('handleFallback', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
expect(policyConfig.setActiveModel).not.toHaveBeenCalled();
|
expect(policyConfig.activateFallbackMode).not.toHaveBeenCalled();
|
||||||
// TODO: add logging expect statement
|
// TODO: add logging expect statement
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does NOT call setActiveModel when handler returns "retry_once"', async () => {
|
it('does NOT call activateFallbackMode when handler returns "retry_once"', async () => {
|
||||||
policyHandler.mockResolvedValue('retry_once');
|
policyHandler.mockResolvedValue('retry_once');
|
||||||
|
|
||||||
const result = await handleFallback(
|
const result = await handleFallback(
|
||||||
@@ -419,7 +423,7 @@ describe('handleFallback', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
expect(policyConfig.setActiveModel).not.toHaveBeenCalled();
|
expect(policyConfig.activateFallbackMode).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ async function processIntent(
|
|||||||
case 'retry_always':
|
case 'retry_always':
|
||||||
// TODO(telemetry): Implement generic fallback event logging. Existing
|
// TODO(telemetry): Implement generic fallback event logging. Existing
|
||||||
// logFlashFallback is specific to a single Model.
|
// logFlashFallback is specific to a single Model.
|
||||||
config.setActiveModel(fallbackModel);
|
config.activateFallbackMode(fallbackModel);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 'retry_once':
|
case 'retry_once':
|
||||||
|
|||||||
Reference in New Issue
Block a user