feat: launch Gemini 3 Flash in Gemini CLI ️ (#15196)

Co-authored-by: gemini-cli-robot <gemini-cli-robot@google.com>
Co-authored-by: joshualitt <joshualitt@google.com>
Co-authored-by: Sehoon Shon <sshon@google.com>
Co-authored-by: Adam Weidman <65992621+adamfweidman@users.noreply.github.com>
Co-authored-by: Adib234 <30782825+Adib234@users.noreply.github.com>
Co-authored-by: Jenna Inouye <jinouye@google.com>
This commit is contained in:
Tommaso Sciortino
2025-12-17 09:43:21 -08:00
committed by GitHub
parent 18698d6929
commit bf90b59935
65 changed files with 1898 additions and 2060 deletions
+145 -4
View File
@@ -34,6 +34,11 @@ import { logRipgrepFallback } from '../telemetry/loggers.js';
import { RipgrepFallbackEvent } from '../telemetry/types.js';
import { ToolRegistry } from '../tools/tool-registry.js';
import { DEFAULT_MODEL_CONFIGS } from './defaultModelConfigs.js';
import {
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
PREVIEW_GEMINI_MODEL,
} from './models.js';
vi.mock('fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('fs')>();
@@ -177,7 +182,7 @@ vi.mock('../code_assist/codeAssist.js');
vi.mock('../code_assist/experiments/experiments.js');
describe('Server Config (config.ts)', () => {
const MODEL = 'gemini-pro';
const MODEL = DEFAULT_GEMINI_MODEL;
const SANDBOX: SandboxConfig = {
command: 'docker',
image: 'gemini-cli-sandbox',
@@ -769,6 +774,40 @@ describe('Server Config (config.ts)', () => {
});
});
describe('UseWriteTodos Configuration', () => {
it('should default useWriteTodos to true when not provided', () => {
const config = new Config(baseParams);
expect(config.getUseWriteTodos()).toBe(true);
});
it('should set useWriteTodos to false when provided as false', () => {
const params: ConfigParameters = {
...baseParams,
useWriteTodos: false,
};
const config = new Config(params);
expect(config.getUseWriteTodos()).toBe(false);
});
it('should disable useWriteTodos for preview models', () => {
const params: ConfigParameters = {
...baseParams,
model: 'gemini-3-pro-preview',
};
const config = new Config(params);
expect(config.getUseWriteTodos()).toBe(false);
});
it('should NOT disable useWriteTodos for non-preview models', () => {
const params: ConfigParameters = {
...baseParams,
model: 'gemini-2.5-pro',
};
const config = new Config(params);
expect(config.getUseWriteTodos()).toBe(true);
});
});
describe('Shell Tool Inactivity Timeout', () => {
it('should default to 300000ms (300 seconds) when not provided', () => {
const config = new Config(baseParams);
@@ -1703,18 +1742,16 @@ describe('Availability Service Integration', () => {
cwd: '.',
};
it('setActiveModel updates active model and emits event', async () => {
it('setActiveModel updates active model', async () => {
const config = new Config(baseParams);
const model1 = 'model1';
const model2 = 'model2';
config.setActiveModel(model1);
expect(config.getActiveModel()).toBe(model1);
expect(mockCoreEvents.emitModelChanged).toHaveBeenCalledWith(model1);
config.setActiveModel(model2);
expect(config.getActiveModel()).toBe(model2);
expect(mockCoreEvents.emitModelChanged).toHaveBeenCalledWith(model2);
});
it('getActiveModel defaults to configured model if not set', () => {
@@ -1731,3 +1768,107 @@ describe('Availability Service Integration', () => {
expect(spy).toHaveBeenCalled();
});
});
describe('Config Quota & Preview Model Access', () => {
let config: Config;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockCodeAssistServer: any;
const baseParams: ConfigParameters = {
cwd: '/tmp',
targetDir: '/tmp',
debugMode: false,
sessionId: 'test-session',
model: 'gemini-pro',
usageStatisticsEnabled: false,
embeddingModel: 'gemini-embedding', // required in type but not in the original file I copied, adding here
sandbox: {
command: 'docker',
image: 'gemini-cli-sandbox',
},
};
beforeEach(() => {
vi.clearAllMocks();
mockCodeAssistServer = {
projectId: 'test-project',
retrieveUserQuota: vi.fn(),
};
vi.mocked(getCodeAssistServer).mockReturnValue(mockCodeAssistServer);
config = new Config(baseParams);
});
describe('refreshUserQuota', () => {
it('should update hasAccessToPreviewModel to true if quota includes preview model', async () => {
mockCodeAssistServer.retrieveUserQuota.mockResolvedValue({
buckets: [{ modelId: PREVIEW_GEMINI_MODEL }],
});
await config.refreshUserQuota();
expect(config.getHasAccessToPreviewModel()).toBe(true);
});
it('should update hasAccessToPreviewModel to false if quota does not include preview model', async () => {
mockCodeAssistServer.retrieveUserQuota.mockResolvedValue({
buckets: [{ modelId: 'some-other-model' }],
});
await config.refreshUserQuota();
expect(config.getHasAccessToPreviewModel()).toBe(false);
});
it('should update hasAccessToPreviewModel to false if buckets are undefined', async () => {
mockCodeAssistServer.retrieveUserQuota.mockResolvedValue({});
await config.refreshUserQuota();
expect(config.getHasAccessToPreviewModel()).toBe(false);
});
it('should return undefined and not update if codeAssistServer is missing', async () => {
vi.mocked(getCodeAssistServer).mockReturnValue(undefined);
const result = await config.refreshUserQuota();
expect(result).toBeUndefined();
expect(config.getHasAccessToPreviewModel()).toBe(false);
});
it('should return undefined if retrieveUserQuota fails', async () => {
mockCodeAssistServer.retrieveUserQuota.mockRejectedValue(
new Error('Network error'),
);
const result = await config.refreshUserQuota();
expect(result).toBeUndefined();
// Should remain default (false)
expect(config.getHasAccessToPreviewModel()).toBe(false);
});
});
describe('setPreviewFeatures', () => {
it('should reset model to default auto if disabling preview features while using a preview model', () => {
config.setPreviewFeatures(true);
config.setModel(PREVIEW_GEMINI_MODEL);
config.setPreviewFeatures(false);
expect(config.getModel()).toBe(DEFAULT_GEMINI_MODEL_AUTO);
});
it('should NOT reset model if disabling preview features while NOT using a preview model', () => {
config.setPreviewFeatures(true);
const nonPreviewModel = 'gemini-1.5-pro';
config.setModel(nonPreviewModel);
config.setPreviewFeatures(false);
expect(config.getModel()).toBe(nonPreviewModel);
});
it('should NOT reset model if enabling preview features', () => {
config.setPreviewFeatures(false);
config.setModel(PREVIEW_GEMINI_MODEL); // Just pretending it was set somehow
config.setPreviewFeatures(true);
expect(config.getModel()).toBe(PREVIEW_GEMINI_MODEL);
});
});
});
+64 -4
View File
@@ -48,7 +48,10 @@ import { tokenLimit } from '../core/tokenLimits.js';
import {
DEFAULT_GEMINI_EMBEDDING_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
DEFAULT_THINKING_MODE,
isPreviewModel,
PREVIEW_GEMINI_MODEL,
} from './models.js';
import { shouldAttemptBrowserLaunch } from '../utils/browser.js';
import type { MCPOAuthConfig } from '../mcp/oauth-provider.js';
@@ -80,6 +83,7 @@ import { PolicyEngine } from '../policy/policy-engine.js';
import type { PolicyEngineConfig } from '../policy/types.js';
import { HookSystem } from '../hooks/index.js';
import type { UserTierId } from '../code_assist/types.js';
import type { RetrieveUserQuotaResponse } from '../code_assist/types.js';
import { getCodeAssistServer } from '../code_assist/codeAssist.js';
import type { Experiments } from '../code_assist/experiments/experiments.js';
import { AgentRegistry } from '../agents/registry.js';
@@ -379,6 +383,7 @@ export class Config {
private readonly bugCommand: BugCommandSettings | undefined;
private model: string;
private previewFeatures: boolean | undefined;
private hasAccessToPreviewModel: boolean = false;
private readonly noBrowser: boolean;
private readonly folderTrust: boolean;
private ideMode: boolean;
@@ -508,8 +513,7 @@ export class Config {
this.bugCommand = params.bugCommand;
this.model = params.model;
this._activeModel = params.model;
this.enableModelAvailabilityService =
params.enableModelAvailabilityService ?? false;
this.enableModelAvailabilityService = true;
this.enableAgents = params.enableAgents ?? false;
this.experimentalJitContext = params.experimentalJitContext ?? false;
this.modelAvailabilityService = new ModelAvailabilityService();
@@ -551,7 +555,10 @@ export class Config {
params.truncateToolOutputLines ?? DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES;
this.enableToolOutputTruncation = params.enableToolOutputTruncation ?? true;
this.useSmartEdit = params.useSmartEdit ?? true;
this.useWriteTodos = params.useWriteTodos ?? true;
// // TODO(joshualitt): Re-evaluate the todo tool for 3 family.
this.useWriteTodos = isPreviewModel(this.model)
? false
: (params.useWriteTodos ?? true);
this.enableHooks = params.enableHooks ?? false;
this.disabledHooks =
(params.hooks && 'disabled' in params.hooks
@@ -716,6 +723,9 @@ export class Config {
this.geminiClient.stripThoughtsFromHistory();
}
// Reset availability status when switching auth (e.g. from limited key to OAuth)
this.modelAvailabilityService.reset();
const newContentGeneratorConfig = await createContentGeneratorConfig(
this,
authMethod,
@@ -735,6 +745,10 @@ export class Config {
const codeAssistServer = getCodeAssistServer(this);
if (codeAssistServer) {
if (codeAssistServer.projectId) {
await this.refreshUserQuota();
}
this.experimentsPromise = getExperiments(codeAssistServer)
.then((experiments) => {
this.setExperiments(experiments);
@@ -756,8 +770,21 @@ export class Config {
this.experimentsPromise = undefined;
}
const authType = this.contentGeneratorConfig.authType;
if (
authType === AuthType.USE_GEMINI ||
authType === AuthType.USE_VERTEX_AI
) {
this.setHasAccessToPreviewModel(true);
}
// Reset the session flag since we're explicitly changing auth and using default model
this.inFallbackMode = false;
// Update model if user no longer has access to the preview model
if (!this.hasAccessToPreviewModel && isPreviewModel(this.model)) {
this.setModel(DEFAULT_GEMINI_MODEL_AUTO);
}
}
async getExperimentsAsync(): Promise<Experiments | undefined> {
@@ -841,7 +868,6 @@ export class Config {
setActiveModel(model: string): void {
if (this._activeModel !== model) {
this._activeModel = model;
coreEvents.emitModelChanged(model);
}
}
@@ -952,9 +978,43 @@ export class Config {
}
setPreviewFeatures(previewFeatures: boolean) {
// If it's using a preview model and it's turning off previewFeatures,
// switch the model to the default auto mode.
if (this.previewFeatures && !previewFeatures) {
if (isPreviewModel(this.getModel())) {
this.setModel(DEFAULT_GEMINI_MODEL_AUTO);
}
}
this.previewFeatures = previewFeatures;
}
getHasAccessToPreviewModel(): boolean {
return this.hasAccessToPreviewModel;
}
setHasAccessToPreviewModel(hasAccess: boolean): void {
this.hasAccessToPreviewModel = hasAccess;
}
async refreshUserQuota(): Promise<RetrieveUserQuotaResponse | undefined> {
const codeAssistServer = getCodeAssistServer(this);
if (!codeAssistServer || !codeAssistServer.projectId) {
return undefined;
}
try {
const quota = await codeAssistServer.retrieveUserQuota({
project: codeAssistServer.projectId,
});
const hasAccess =
quota.buckets?.some((b) => b.modelId === PREVIEW_GEMINI_MODEL) ?? false;
this.setHasAccessToPreviewModel(hasAccess);
return quota;
} catch (e) {
debugLogger.debug('Failed to retrieve user quota', e);
return undefined;
}
}
getCoreTools(): string[] | undefined {
return this.coreTools;
}
@@ -65,6 +65,12 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
model: 'gemini-3-pro-preview',
},
},
'gemini-3-flash-preview': {
extends: 'chat-base-3',
modelConfig: {
model: 'gemini-3-flash-preview',
},
},
'gemini-2.5-pro': {
extends: 'chat-base-2.5',
modelConfig: {
@@ -188,6 +194,11 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
model: 'gemini-3-pro-preview',
},
},
'chat-compression-3-flash': {
modelConfig: {
model: 'gemini-3-flash-preview',
},
},
'chat-compression-2.5-pro': {
modelConfig: {
model: 'gemini-2.5-pro',
+87 -156
View File
@@ -7,14 +7,19 @@
import { describe, it, expect } from 'vitest';
import {
getEffectiveModel,
resolveClassifierModel,
isGemini2Model,
DEFAULT_GEMINI_MODEL,
PREVIEW_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
supportsMultimodalFunctionResponse,
GEMINI_MODEL_ALIAS_PRO,
GEMINI_MODEL_ALIAS_FLASH,
GEMINI_MODEL_ALIAS_FLASH_LITE,
supportsMultimodalFunctionResponse,
PREVIEW_GEMINI_FLASH_MODEL,
PREVIEW_GEMINI_MODEL_AUTO,
DEFAULT_GEMINI_MODEL_AUTO,
} from './models.js';
describe('supportsMultimodalFunctionResponse', () => {
@@ -34,210 +39,136 @@ describe('supportsMultimodalFunctionResponse', () => {
});
describe('getEffectiveModel', () => {
describe('When NOT in fallback mode', () => {
const isInFallbackMode = false;
describe('delegation to resolveModel', () => {
it('should return the Preview Pro model when auto-gemini-3 is requested', () => {
const model = getEffectiveModel(PREVIEW_GEMINI_MODEL_AUTO, false);
expect(model).toBe(PREVIEW_GEMINI_MODEL);
});
it('should return the Pro model when Pro is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_MODEL,
false,
);
it('should return the Default Pro model when auto-gemini-2.5 is requested', () => {
const model = getEffectiveModel(DEFAULT_GEMINI_MODEL_AUTO, false);
expect(model).toBe(DEFAULT_GEMINI_MODEL);
});
it('should return the Flash model when Flash is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
it('should return the requested model as-is for explicit specific models', () => {
expect(getEffectiveModel(DEFAULT_GEMINI_MODEL, false)).toBe(
DEFAULT_GEMINI_MODEL,
);
expect(getEffectiveModel(DEFAULT_GEMINI_FLASH_MODEL, false)).toBe(
DEFAULT_GEMINI_FLASH_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the Lite model when Lite is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
expect(getEffectiveModel(DEFAULT_GEMINI_FLASH_LITE_MODEL, false)).toBe(
DEFAULT_GEMINI_FLASH_LITE_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return a custom model name when requested', () => {
const customModel = 'custom-model-v1';
const model = getEffectiveModel(isInFallbackMode, customModel, false);
const model = getEffectiveModel(customModel, false);
expect(model).toBe(customModel);
});
describe('with preview features', () => {
it('should return the preview model when pro alias is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_PRO,
true,
);
const model = getEffectiveModel(GEMINI_MODEL_ALIAS_PRO, true);
expect(model).toBe(PREVIEW_GEMINI_MODEL);
});
it('should return the default pro model when pro alias is requested and preview is off', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_PRO,
false,
);
const model = getEffectiveModel(GEMINI_MODEL_ALIAS_PRO, false);
expect(model).toBe(DEFAULT_GEMINI_MODEL);
});
it('should return the flash model when flash is requested and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_FLASH,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
const model = getEffectiveModel(GEMINI_MODEL_ALIAS_FLASH, true);
expect(model).toBe(PREVIEW_GEMINI_FLASH_MODEL);
});
it('should return the flash model when lite is requested and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_FLASH_LITE,
true,
);
const model = getEffectiveModel(GEMINI_MODEL_ALIAS_FLASH_LITE, true);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return the flash model when the flash model name is explicitly requested and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_MODEL,
true,
);
const model = getEffectiveModel(DEFAULT_GEMINI_FLASH_MODEL, true);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the lite model when the lite model name is requested and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
true,
);
const model = getEffectiveModel(DEFAULT_GEMINI_FLASH_LITE_MODEL, true);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return the default gemini model when the model is explicitly set and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_MODEL,
true,
);
const model = getEffectiveModel(DEFAULT_GEMINI_MODEL, true);
expect(model).toBe(DEFAULT_GEMINI_MODEL);
});
});
});
});
describe('When IN fallback mode', () => {
const isInFallbackMode = true;
describe('isGemini2Model', () => {
it('should return true for gemini-2.5-pro', () => {
expect(isGemini2Model('gemini-2.5-pro')).toBe(true);
});
it('should downgrade the Pro model to the Flash model', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return true for gemini-2.5-flash', () => {
expect(isGemini2Model('gemini-2.5-flash')).toBe(true);
});
it('should return the Flash model when Flash is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return true for gemini-2.0-flash', () => {
expect(isGemini2Model('gemini-2.0-flash')).toBe(true);
});
it('should HONOR the Lite model when Lite is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return false for gemini-1.5-pro', () => {
expect(isGemini2Model('gemini-1.5-pro')).toBe(false);
});
it('should HONOR any model with "lite" in its name', () => {
const customLiteModel = 'gemini-2.5-custom-lite-vNext';
const model = getEffectiveModel(isInFallbackMode, customLiteModel, false);
expect(model).toBe(customLiteModel);
});
it('should return false for gemini-3-pro', () => {
expect(isGemini2Model('gemini-3-pro')).toBe(false);
});
it('should downgrade any other custom model to the Flash model', () => {
const customModel = 'custom-model-v1-unlisted';
const model = getEffectiveModel(isInFallbackMode, customModel, false);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
describe('with preview features', () => {
it('should downgrade the Pro alias to the Flash model', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_PRO,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the Flash alias when requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_FLASH,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the Lite alias when requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_FLASH_LITE,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should downgrade the default Gemini model to the Flash model', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the default Flash model when requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the default Lite model when requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should downgrade any other custom model to the Flash model', () => {
const customModel = 'custom-model-v1-unlisted';
const model = getEffectiveModel(isInFallbackMode, customModel, true);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
});
it('should return false for arbitrary strings', () => {
expect(isGemini2Model('gpt-4')).toBe(false);
});
});
describe('resolveClassifierModel', () => {
it('should return flash model when alias is flash', () => {
expect(
resolveClassifierModel(
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_FLASH,
),
).toBe(DEFAULT_GEMINI_FLASH_MODEL);
expect(
resolveClassifierModel(
PREVIEW_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_FLASH,
),
).toBe(PREVIEW_GEMINI_FLASH_MODEL);
});
it('should return pro model when alias is pro', () => {
expect(
resolveClassifierModel(DEFAULT_GEMINI_MODEL_AUTO, GEMINI_MODEL_ALIAS_PRO),
).toBe(DEFAULT_GEMINI_MODEL);
expect(
resolveClassifierModel(PREVIEW_GEMINI_MODEL_AUTO, GEMINI_MODEL_ALIAS_PRO),
).toBe(PREVIEW_GEMINI_MODEL);
});
it('should handle preview features being enabled', () => {
// If preview is enabled, resolving 'flash' without context (fallback) might switch to preview flash,
// but here we test explicit auto models which should stick to their families if possible?
// Actually our logic forces DEFAULT_GEMINI_FLASH_MODEL for DEFAULT_GEMINI_MODEL_AUTO even if preview is on,
// because the USER requested 2.5 explicitly via "auto-gemini-2.5".
expect(
resolveClassifierModel(
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_FLASH,
true,
),
).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
});
+82 -24
View File
@@ -5,20 +5,24 @@
*/
export const PREVIEW_GEMINI_MODEL = 'gemini-3-pro-preview';
export const PREVIEW_GEMINI_FLASH_MODEL = 'gemini-3-flash-preview';
export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro';
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash';
export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite';
export const VALID_GEMINI_MODELS = new Set([
PREVIEW_GEMINI_MODEL,
PREVIEW_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
]);
export const DEFAULT_GEMINI_MODEL_AUTO = 'auto';
export const PREVIEW_GEMINI_MODEL_AUTO = 'auto-gemini-3';
export const DEFAULT_GEMINI_MODEL_AUTO = 'auto-gemini-2.5';
// Model aliases for user convenience.
export const GEMINI_MODEL_ALIAS_AUTO = 'auto';
export const GEMINI_MODEL_ALIAS_PRO = 'pro';
export const GEMINI_MODEL_ALIAS_FLASH = 'flash';
export const GEMINI_MODEL_ALIAS_FLASH_LITE = 'flash-lite';
@@ -38,17 +42,24 @@ export const DEFAULT_THINKING_MODE = 8192;
*/
export function resolveModel(
requestedModel: string,
previewFeaturesEnabled: boolean | undefined,
previewFeaturesEnabled: boolean = false,
): string {
switch (requestedModel) {
case DEFAULT_GEMINI_MODEL_AUTO:
case PREVIEW_GEMINI_MODEL_AUTO: {
return PREVIEW_GEMINI_MODEL;
}
case DEFAULT_GEMINI_MODEL_AUTO: {
return DEFAULT_GEMINI_MODEL;
}
case GEMINI_MODEL_ALIAS_PRO: {
return previewFeaturesEnabled
? PREVIEW_GEMINI_MODEL
: DEFAULT_GEMINI_MODEL;
}
case GEMINI_MODEL_ALIAS_FLASH: {
return DEFAULT_GEMINI_FLASH_MODEL;
return previewFeaturesEnabled
? PREVIEW_GEMINI_FLASH_MODEL
: DEFAULT_GEMINI_FLASH_MODEL;
}
case GEMINI_MODEL_ALIAS_FLASH_LITE: {
return DEFAULT_GEMINI_FLASH_LITE_MODEL;
@@ -60,39 +71,86 @@ export function resolveModel(
}
/**
* Determines the effective model to use, applying fallback logic if necessary.
* Resolves the appropriate model based on the classifier's decision.
*
* When fallback mode is active, this function enforces the use of the standard
* fallback model. However, it makes an exception for "lite" models (any model
* with "lite" in its name), allowing them to be used to preserve cost savings.
* This ensures that "pro" models are always downgraded, while "lite" model
* requests are honored.
* @param requestedModel The current requested model (e.g. auto-gemini-2.5).
* @param modelAlias The alias selected by the classifier ('flash' or 'pro').
* @param previewFeaturesEnabled Whether preview features are enabled.
* @returns The resolved concrete model name.
*/
export function resolveClassifierModel(
requestedModel: string,
modelAlias: string,
previewFeaturesEnabled: boolean = false,
): string {
if (modelAlias === GEMINI_MODEL_ALIAS_FLASH) {
if (
requestedModel === DEFAULT_GEMINI_MODEL_AUTO ||
requestedModel === DEFAULT_GEMINI_MODEL
) {
return DEFAULT_GEMINI_FLASH_MODEL;
}
if (
requestedModel === PREVIEW_GEMINI_MODEL_AUTO ||
requestedModel === PREVIEW_GEMINI_MODEL
) {
return PREVIEW_GEMINI_FLASH_MODEL;
}
return resolveModel(GEMINI_MODEL_ALIAS_FLASH, previewFeaturesEnabled);
}
return resolveModel(requestedModel, previewFeaturesEnabled);
}
/**
* Determines the effective model to use.
*
* @param isInFallbackMode Whether the application is in fallback mode.
* @param requestedModel The model that was originally requested.
* @param previewFeaturesEnabled A boolean indicating if preview features are enabled.
* @returns The effective model name.
*/
export function getEffectiveModel(
isInFallbackMode: boolean,
requestedModel: string,
previewFeaturesEnabled: boolean | undefined,
): string {
const resolvedModel = resolveModel(requestedModel, previewFeaturesEnabled);
return resolveModel(requestedModel, previewFeaturesEnabled);
}
// If we are not in fallback mode, simply use the resolved model.
if (!isInFallbackMode) {
return resolvedModel;
export function getDisplayString(
model: string,
previewFeaturesEnabled: boolean = false,
) {
switch (model) {
case PREVIEW_GEMINI_MODEL_AUTO:
return 'Auto (Gemini 3)';
case DEFAULT_GEMINI_MODEL_AUTO:
return 'Auto (Gemini 2.5)';
case GEMINI_MODEL_ALIAS_PRO:
return `Manual (${
previewFeaturesEnabled ? PREVIEW_GEMINI_MODEL : DEFAULT_GEMINI_MODEL
})`;
case GEMINI_MODEL_ALIAS_FLASH:
return `Manual (${
previewFeaturesEnabled
? PREVIEW_GEMINI_FLASH_MODEL
: DEFAULT_GEMINI_FLASH_MODEL
})`;
default:
return `Manual (${model})`;
}
}
// If a "lite" model is requested, honor it. This allows for variations of
// lite models without needing to list them all as constants.
if (resolvedModel.includes('lite')) {
return resolvedModel;
}
// Default fallback for Gemini CLI.
return DEFAULT_GEMINI_FLASH_MODEL;
/**
* Checks if the model is a preview model.
*
* @param model The model name to check.
* @returns True if the model is a preview model.
*/
export function isPreviewModel(model: string): boolean {
return (
model === PREVIEW_GEMINI_MODEL ||
model === PREVIEW_GEMINI_FLASH_MODEL ||
model === PREVIEW_GEMINI_MODEL_AUTO
);
}
/**