feat(cli): merge Auto modes into a single Auto mode (#26714)

This commit is contained in:
David Pierce
2026-05-13 16:55:43 +00:00
committed by GitHub
parent 8cda688fe2
commit 749657cbf9
15 changed files with 370 additions and 257 deletions
+56 -46
View File
@@ -882,9 +882,10 @@ their corresponding top-level category object in your `settings.json` file.
}
},
"auto": {
"displayName": "Auto",
"tier": "auto",
"isPreview": true,
"isVisible": false,
"isVisible": true,
"features": {
"thinking": true,
"multimodalToolUse": false
@@ -918,26 +919,16 @@ their corresponding top-level category object in your `settings.json` file.
}
},
"auto-gemini-3": {
"displayName": "Auto (Gemini 3)",
"tier": "auto",
"family": "gemini-3",
"isPreview": true,
"isVisible": true,
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash",
"features": {
"thinking": true,
"multimodalToolUse": false
}
"isVisible": false
},
"auto-gemini-2.5": {
"displayName": "Auto (Gemini 2.5)",
"tier": "auto",
"family": "gemini-2.5",
"isPreview": false,
"isVisible": true,
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash",
"features": {
"thinking": false,
"multimodalToolUse": false
}
"isVisible": false
}
}
```
@@ -1020,33 +1011,15 @@ their corresponding top-level category object in your `settings.json` file.
}
]
},
"auto-gemini-3": {
"default": "gemini-3-pro-preview",
"contexts": [
{
"condition": {
"hasAccessToPreview": false
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"useGemini3_1": true,
"useCustomTools": true
},
"target": "gemini-3.1-pro-preview-customtools"
},
{
"condition": {
"useGemini3_1": true
},
"target": "gemini-3.1-pro-preview"
}
]
},
"auto": {
"default": "gemini-3-pro-preview",
"contexts": [
{
"condition": {
"releaseChannel": "stable"
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"hasAccessToPreview": false
@@ -1092,9 +1065,6 @@ their corresponding top-level category object in your `settings.json` file.
}
]
},
"auto-gemini-2.5": {
"default": "gemini-2.5-pro"
},
"gemini-3.1-flash-lite-preview": {
"default": "gemini-3.1-flash-lite-preview",
"contexts": [
@@ -1127,6 +1097,33 @@ their corresponding top-level category object in your `settings.json` file.
"target": "gemini-3.1-flash-lite-preview"
}
]
},
"auto-gemini-3": {
"default": "gemini-3-pro-preview",
"contexts": [
{
"condition": {
"hasAccessToPreview": false
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"useGemini3_1": true,
"useCustomTools": true
},
"target": "gemini-3.1-pro-preview-customtools"
},
{
"condition": {
"useGemini3_1": true
},
"target": "gemini-3.1-pro-preview"
}
]
},
"auto-gemini-2.5": {
"default": "gemini-2.5-pro"
}
}
```
@@ -1145,15 +1142,15 @@ their corresponding top-level category object in your `settings.json` file.
"contexts": [
{
"condition": {
"requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"]
"hasAccessToPreview": false
},
"target": "gemini-2.5-flash"
},
{
"condition": {
"requestedModels": ["auto-gemini-3", "gemini-3-pro-preview"]
"requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"]
},
"target": "gemini-3-flash-preview"
"target": "gemini-2.5-flash"
}
]
},
@@ -1162,7 +1159,20 @@ their corresponding top-level category object in your `settings.json` file.
"contexts": [
{
"condition": {
"requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"]
"hasAccessToPreview": false
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"releaseChannel": "stable",
"requestedModels": ["auto"]
},
"target": "gemini-2.5-pro"
},
{
"condition": {
"requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"]
},
"target": "gemini-2.5-pro"
},
@@ -19,6 +19,7 @@ import type * as acp from '@agentclientprotocol/sdk';
import {
AuthType,
type Config,
GEMINI_MODEL_ALIAS_AUTO,
type MessageBus,
type Storage,
} from '@google/gemini-cli-core';
@@ -208,7 +209,7 @@ describe('AcpSessionManager', () => {
expect(response.models?.availableModels).toEqual(
expect.arrayContaining([
expect.objectContaining({
modelId: 'auto-gemini-3',
modelId: GEMINI_MODEL_ALIAS_AUTO,
name: expect.stringContaining('Auto'),
}),
]),
+10 -17
View File
@@ -10,8 +10,7 @@ import {
type ToolCallConfirmationDetails,
Kind,
ApprovalMode,
DEFAULT_GEMINI_MODEL_AUTO,
PREVIEW_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
@@ -23,6 +22,8 @@ import {
getDisplayString,
AuthType,
ToolConfirmationOutcome,
getChannelFromVersion,
getAutoModelDescription,
} from '@google/gemini-cli-core';
import type * as acp from '@agentclientprotocol/sdk';
import { z } from 'zod';
@@ -262,7 +263,7 @@ export function buildAvailableModels(
}>;
currentModelId: string;
} {
const preferredModel = config.getModel() || DEFAULT_GEMINI_MODEL_AUTO;
const preferredModel = config.getModel() || GEMINI_MODEL_ALIAS_AUTO;
const shouldShowPreviewModels = config.getHasAccessToPreviewModel();
const useGemini31 = config.getGemini31LaunchedSync?.() ?? false;
const useGemini31FlashLite =
@@ -271,6 +272,8 @@ export function buildAvailableModels(
const useCustomToolModel =
useGemini31 && selectedAuthType === AuthType.USE_GEMINI;
const releaseChannel = getChannelFromVersion(config.clientVersion);
// --- DYNAMIC PATH ---
if (
config.getExperimentalDynamicModelConfiguration?.() === true &&
@@ -281,6 +284,7 @@ export function buildAvailableModels(
useGemini3_1FlashLite: useGemini31FlashLite,
useCustomTools: useCustomToolModel,
hasAccessToPreview: shouldShowPreviewModels,
releaseChannel,
});
return {
@@ -292,23 +296,12 @@ export function buildAvailableModels(
// --- LEGACY PATH ---
const mainOptions = [
{
value: DEFAULT_GEMINI_MODEL_AUTO,
title: getDisplayString(DEFAULT_GEMINI_MODEL_AUTO),
description:
'Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash',
value: GEMINI_MODEL_ALIAS_AUTO,
title: getDisplayString(GEMINI_MODEL_ALIAS_AUTO),
description: getAutoModelDescription(releaseChannel, useGemini31),
},
];
if (shouldShowPreviewModels) {
mainOptions.unshift({
value: PREVIEW_GEMINI_MODEL_AUTO,
title: getDisplayString(PREVIEW_GEMINI_MODEL_AUTO),
description: useGemini31
? 'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash'
: 'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
});
}
const manualOptions = [
{
value: DEFAULT_GEMINI_MODEL,
+2 -2
View File
@@ -2058,7 +2058,7 @@ describe('loadCliConfig model selection', () => {
argv,
);
expect(config.getModel()).toBe('auto-gemini-3');
expect(config.getModel()).toBe('auto');
});
it('always prefers model from argv', async () => {
@@ -2102,7 +2102,7 @@ describe('loadCliConfig model selection', () => {
argv,
);
expect(config.getModel()).toBe('auto-gemini-3');
expect(config.getModel()).toBe('auto');
});
});
+1 -2
View File
@@ -30,7 +30,6 @@ import {
loadServerHierarchicalMemory,
ASK_USER_TOOL_NAME,
getVersion,
PREVIEW_GEMINI_MODEL_AUTO,
type HierarchicalMemory,
coreEvents,
GEMINI_MODEL_ALIAS_AUTO,
@@ -866,7 +865,7 @@ export async function loadCliConfig(
interactive,
);
const defaultModel = PREVIEW_GEMINI_MODEL_AUTO;
const defaultModel = GEMINI_MODEL_ALIAS_AUTO;
const rawModel =
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
+5 -1
View File
@@ -3471,7 +3471,11 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
family: { type: 'string' },
isPreview: { type: 'boolean' },
isVisible: { type: 'boolean' },
dialogDescription: { type: 'string' },
dialogDescription: {
type: 'string',
description:
"A description of the model to display in the model selection dialog. For the 'auto' alias, this value is dynamically generated and any value provided here will be ignored.",
},
features: {
type: 'object',
properties: {
@@ -12,7 +12,7 @@ import { waitFor } from '../../test-utils/async.js';
import { createMockSettings } from '../../test-utils/settings.js';
import {
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
PREVIEW_GEMINI_MODEL,
@@ -93,7 +93,7 @@ describe('<ModelDialog />', () => {
beforeEach(() => {
vi.resetAllMocks();
mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO);
mockGetModel.mockReturnValue(GEMINI_MODEL_ALIAS_AUTO);
mockGetHasAccessToPreviewModel.mockReturnValue(false);
mockGetGemini31LaunchedSync.mockReturnValue(false);
mockGetGemini31FlashLiteLaunchedSync.mockReturnValue(false);
@@ -102,8 +102,7 @@ describe('<ModelDialog />', () => {
// Default implementation for getDisplayString
mockGetDisplayString.mockImplementation((val: string) => {
if (val === 'auto-gemini-2.5') return 'Auto (Gemini 2.5)';
if (val === 'auto-gemini-3') return 'Auto (Preview)';
if (val === 'auto') return 'Auto';
return val;
});
});
@@ -234,7 +233,7 @@ describe('<ModelDialog />', () => {
await waitFor(() => {
expect(mockSetModel).toHaveBeenCalledWith(
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
true, // Session only by default
);
expect(mockOnClose).toHaveBeenCalled();
@@ -292,7 +291,7 @@ describe('<ModelDialog />', () => {
await waitFor(() => {
expect(mockSetModel).toHaveBeenCalledWith(
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
false, // Persist enabled
);
expect(mockOnClose).toHaveBeenCalled();
@@ -355,7 +354,7 @@ describe('<ModelDialog />', () => {
mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL);
mockGetDisplayString.mockImplementation((val: string) => {
if (val === DEFAULT_GEMINI_MODEL) return 'My Custom Model Display';
if (val === 'auto-gemini-2.5') return 'Auto (Gemini 2.5)';
if (val === 'auto') return 'Auto';
return val;
});
const { lastFrame, unmount } = await renderComponent();
@@ -369,9 +368,9 @@ describe('<ModelDialog />', () => {
mockGetHasAccessToPreviewModel.mockReturnValue(true);
});
it('shows Auto (Preview) in main view when access is granted', async () => {
it('shows Auto in main view when access is granted', async () => {
const { lastFrame, unmount } = await renderComponent();
expect(lastFrame()).toContain('Auto (Preview)');
expect(lastFrame()).toContain('Auto');
unmount();
});
+17 -18
View File
@@ -14,11 +14,10 @@ import {
PREVIEW_GEMINI_3_1_MODEL,
PREVIEW_GEMINI_FLASH_MODEL,
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
PREVIEW_GEMINI_MODEL_AUTO,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
GEMMA_4_31B_IT_MODEL,
GEMMA_4_26B_A4B_IT_MODEL,
ModelSlashCommandEvent,
@@ -27,6 +26,8 @@ import {
AuthType,
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
isProModel,
getChannelFromVersion,
getAutoModelDescription,
} from '@google/gemini-cli-core';
import { useKeypress } from '../hooks/useKeypress.js';
import { theme } from '../semantic-colors.js';
@@ -63,7 +64,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
}, [config]);
// Determine the Preferred Model (read once when the dialog opens).
const preferredModel = config?.getModel() || DEFAULT_GEMINI_MODEL_AUTO;
const preferredModel = config?.getModel() || GEMINI_MODEL_ALIAS_AUTO;
const shouldShowPreviewModels = config?.getHasAccessToPreviewModel();
const useGemini31 = config?.getGemini31LaunchedSync?.() ?? false;
@@ -122,6 +123,11 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
{ isActive: true },
);
const releaseChannel = useMemo(
() => getChannelFromVersion(config?.clientVersion ?? ''),
[config?.clientVersion],
);
const mainOptions = useMemo(() => {
// --- DYNAMIC PATH ---
if (
@@ -136,6 +142,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
useCustomTools: useCustomToolModel,
hasAccessToPreview: shouldShowPreviewModels,
hasAccessToProModel,
releaseChannel,
});
const list = allOptions
@@ -161,11 +168,10 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
// --- LEGACY PATH ---
const list = [
{
value: DEFAULT_GEMINI_MODEL_AUTO,
title: getDisplayString(DEFAULT_GEMINI_MODEL_AUTO),
description:
'Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash',
key: DEFAULT_GEMINI_MODEL_AUTO,
value: GEMINI_MODEL_ALIAS_AUTO,
title: getDisplayString(GEMINI_MODEL_ALIAS_AUTO),
description: getAutoModelDescription(releaseChannel, useGemini31),
key: GEMINI_MODEL_ALIAS_AUTO,
},
{
value: 'Manual',
@@ -177,16 +183,6 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
},
];
if (shouldShowPreviewModels) {
list.unshift({
value: PREVIEW_GEMINI_MODEL_AUTO,
title: getDisplayString(PREVIEW_GEMINI_MODEL_AUTO),
description: useGemini31
? 'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash'
: 'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
key: PREVIEW_GEMINI_MODEL_AUTO,
});
}
return list;
}, [
config,
@@ -196,6 +192,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
useGemini31FlashLite,
useCustomToolModel,
hasAccessToProModel,
releaseChannel,
]);
const manualOptions = useMemo(() => {
@@ -212,6 +209,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
useCustomTools: useCustomToolModel,
hasAccessToPreview: shouldShowPreviewModels,
hasAccessToProModel,
releaseChannel,
});
return allOptions
@@ -304,6 +302,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
useGemini31FlashLite,
useCustomToolModel,
hasAccessToProModel,
releaseChannel,
config,
]);
@@ -37,7 +37,11 @@ const createMockConfig = (overrides: Partial<Config> = {}): Config => {
return useGemini31 && authType === AuthType.USE_GEMINI;
},
getContentGeneratorConfig: () => ({ authType: undefined }),
getHasAccessToPreviewModel: () => true,
getMaxAttemptsPerTurn: () => 3,
getExperimentalDynamicModelConfiguration: () => false,
getReleaseChannel: () => 'preview',
modelConfigService: new ModelConfigService(DEFAULT_MODEL_CONFIGS),
...overrides,
} as unknown as Config;
return config;
@@ -187,6 +191,7 @@ describe('policyHelpers', () => {
const testCases = [
{ name: 'Default Auto', model: DEFAULT_GEMINI_MODEL_AUTO },
{ name: 'Gemini 3 Auto', model: 'auto-gemini-3' },
{ name: 'Unified Auto', model: 'auto' },
{ name: 'Flash Lite', model: DEFAULT_GEMINI_FLASH_LITE_MODEL },
{
name: 'Gemini 3 Auto (3.1 Enabled)',
@@ -215,7 +220,18 @@ describe('policyHelpers', () => {
];
testCases.forEach(
({ name, model, useGemini31, hasAccess, authType, wrapsAround }) => {
({
name,
model,
useGemini31,
hasAccess,
authType,
wrapsAround,
...rest
}) => {
const releaseChannel = (rest as Record<string, unknown>)[
'releaseChannel'
] as string | undefined;
it(`achieves parity for: ${name}`, () => {
const createBaseConfig = (dynamic: boolean) =>
createMockConfig({
@@ -225,6 +241,7 @@ describe('policyHelpers', () => {
getGemini31FlashLiteLaunchedSync: () => false,
getHasAccessToPreviewModel: () => hasAccess ?? true,
getContentGeneratorConfig: () => ({ authType }),
getReleaseChannel: () => releaseChannel ?? 'preview',
modelConfigService: new ModelConfigService(DEFAULT_MODEL_CONFIGS),
});
@@ -86,6 +86,7 @@ export function resolvePolicyChain(
useGemini3_1: useGemini31,
useGemini3_1FlashLite: useGemini31FlashLite,
useCustomTools: useCustomToolModel,
releaseChannel: config.getReleaseChannel?.(),
};
if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) {
+28 -10
View File
@@ -81,14 +81,11 @@ import { tokenLimit } from '../core/tokenLimits.js';
import {
DEFAULT_GEMINI_EMBEDDING_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
isAutoModel,
isPreviewModel,
isGemini2Model,
PREVIEW_GEMINI_FLASH_MODEL,
PREVIEW_GEMINI_MODEL,
PREVIEW_GEMINI_MODEL_AUTO,
resolveModel,
} from './models.js';
import { shouldAttemptBrowserLaunch } from '../utils/browser.js';
@@ -445,6 +442,7 @@ export interface ExtensionInstallMetadata {
allowPreRelease?: boolean;
}
import { getChannelFromVersion } from '../utils/channel.js';
import { DEFAULT_MAX_ATTEMPTS } from '../utils/retry.js';
import {
DEFAULT_FILE_FILTERING_OPTIONS,
@@ -762,7 +760,8 @@ export class Config implements McpContext, AgentLoopContext {
private skillManager!: SkillManager;
private _sessionId: string;
private readonly clientName: string | undefined;
private clientVersion: string;
private _clientVersion: string;
private fileSystemService: FileSystemService;
private trackerService?: TrackerService;
readonly topicState = new TopicState();
@@ -982,8 +981,9 @@ export class Config implements McpContext, AgentLoopContext {
constructor(params: ConfigParameters) {
this._sessionId = params.sessionId;
this.clientName = params.clientName;
this.clientVersion = params.clientVersion ?? 'unknown';
this._clientVersion = params.clientVersion ?? 'unknown';
this.approvedPlanPath = undefined;
this.embeddingModel =
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
this.sandbox = params.sandbox
@@ -2009,14 +2009,21 @@ export class Config implements McpContext, AgentLoopContext {
resetTime?: string;
} {
const model = this.getModel();
if (!isAutoModel(model)) {
if (!isAutoModel(model, this)) {
return {};
}
const isPreview =
model === PREVIEW_GEMINI_MODEL_AUTO ||
isPreviewModel(this.getActiveModel(), this);
const proModel = isPreview ? PREVIEW_GEMINI_MODEL : DEFAULT_GEMINI_MODEL;
const primaryModel = resolveModel(
model,
this.getGemini31LaunchedSync(),
this.getGemini31FlashLiteLaunchedSync(),
this.getUseCustomToolModelSync(),
this.getHasAccessToPreviewModel(),
this,
);
const isPreview = isPreviewModel(primaryModel, this);
const proModel = primaryModel;
const flashModel = isPreview
? PREVIEW_GEMINI_FLASH_MODEL
: DEFAULT_GEMINI_FLASH_MODEL;
@@ -2781,6 +2788,10 @@ export class Config implements McpContext, AgentLoopContext {
return this.dynamicModelConfiguration;
}
getReleaseChannel(): string {
return getChannelFromVersion(this._clientVersion);
}
getPendingIncludeDirectories(): string[] {
return this.pendingIncludeDirectories;
}
@@ -3529,6 +3540,13 @@ export class Config implements McpContext, AgentLoopContext {
);
}
/**
* Returns the client version.
*/
get clientVersion(): string {
return this._clientVersion;
}
private async ensureExperimentsLoaded(): Promise<void> {
if (!this.experimentsPromise) {
return;
+36 -34
View File
@@ -362,9 +362,10 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
// Aliases
auto: {
displayName: 'Auto',
tier: 'auto',
isPreview: true,
isVisible: false,
isVisible: true,
features: { thinking: true, multimodalToolUse: false },
},
pro: {
@@ -386,22 +387,16 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
features: { thinking: false, multimodalToolUse: false },
},
'auto-gemini-3': {
displayName: 'Auto (Gemini 3)',
tier: 'auto',
family: 'gemini-3',
isPreview: true,
isVisible: true,
dialogDescription:
'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
features: { thinking: true, multimodalToolUse: false },
isVisible: false,
},
'auto-gemini-2.5': {
displayName: 'Auto (Gemini 2.5)',
tier: 'auto',
family: 'gemini-2.5',
isPreview: false,
isVisible: true,
dialogDescription:
'Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash',
features: { thinking: false, multimodalToolUse: false },
isVisible: false,
},
},
modelIdResolutions: {
@@ -451,23 +446,10 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
],
},
'auto-gemini-3': {
default: 'gemini-3-pro-preview',
contexts: [
{ condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' },
{
condition: { useGemini3_1: true, useCustomTools: true },
target: 'gemini-3.1-pro-preview-customtools',
},
{
condition: { useGemini3_1: true },
target: 'gemini-3.1-pro-preview',
},
],
},
auto: {
default: 'gemini-3-pro-preview',
contexts: [
{ condition: { releaseChannel: 'stable' }, target: 'gemini-2.5-pro' },
{ condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' },
{
condition: { useGemini3_1: true, useCustomTools: true },
@@ -493,9 +475,6 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
],
},
'auto-gemini-2.5': {
default: 'gemini-2.5-pro',
},
'gemini-3.1-flash-lite-preview': {
default: 'gemini-3.1-flash-lite-preview',
contexts: [
@@ -523,20 +502,35 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
],
},
'auto-gemini-3': {
default: 'gemini-3-pro-preview',
contexts: [
{ condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' },
{
condition: { useGemini3_1: true, useCustomTools: true },
target: 'gemini-3.1-pro-preview-customtools',
},
{
condition: { useGemini3_1: true },
target: 'gemini-3.1-pro-preview',
},
],
},
'auto-gemini-2.5': {
default: 'gemini-2.5-pro',
},
},
classifierIdResolutions: {
flash: {
default: 'gemini-3-flash-preview',
contexts: [
{
condition: { requestedModels: ['auto-gemini-2.5', 'gemini-2.5-pro'] },
condition: { hasAccessToPreview: false },
target: 'gemini-2.5-flash',
},
{
condition: {
requestedModels: ['auto-gemini-3', 'gemini-3-pro-preview'],
},
target: 'gemini-3-flash-preview',
condition: { requestedModels: ['gemini-2.5-pro', 'auto-gemini-2.5'] },
target: 'gemini-2.5-flash',
},
],
},
@@ -544,7 +538,15 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
default: 'gemini-3-pro-preview',
contexts: [
{
condition: { requestedModels: ['auto-gemini-2.5', 'gemini-2.5-pro'] },
condition: { hasAccessToPreview: false },
target: 'gemini-2.5-pro',
},
{
condition: { releaseChannel: 'stable', requestedModels: ['auto'] },
target: 'gemini-2.5-pro',
},
{
condition: { requestedModels: ['gemini-2.5-pro', 'auto-gemini-2.5'] },
target: 'gemini-2.5-pro',
},
{
+47 -7
View File
@@ -10,6 +10,7 @@ export interface ModelResolutionContext {
useCustomTools?: boolean;
hasAccessToPreview?: boolean;
requestedModel?: string;
releaseChannel?: string;
}
/**
@@ -48,6 +49,7 @@ export interface IModelConfigService {
export interface ModelCapabilityContext {
readonly modelConfigService: IModelConfigService;
getExperimentalDynamicModelConfiguration(): boolean;
getReleaseChannel?(): string;
}
export const PREVIEW_GEMINI_MODEL = 'gemini-3-pro-preview';
@@ -78,7 +80,9 @@ export const VALID_GEMINI_MODELS = new Set([
GEMMA_4_26B_A4B_IT_MODEL,
]);
/** @deprecated Use GEMINI_MODEL_ALIAS_AUTO instead. */
export const PREVIEW_GEMINI_MODEL_AUTO = 'auto-gemini-3';
/** @deprecated Use GEMINI_MODEL_ALIAS_AUTO instead. */
export const DEFAULT_GEMINI_MODEL_AUTO = 'auto-gemini-2.5';
// Model aliases for user convenience.
@@ -92,8 +96,22 @@ export const DEFAULT_GEMINI_EMBEDDING_MODEL = 'gemini-embedding-001';
// Cap the thinking at 8192 to prevent run-away thinking loops.
export const DEFAULT_THINKING_MODE = 8192;
export function getAutoModelDescription(
releaseChannel: string = 'stable',
useGemini3_1: boolean = false,
) {
const isPreview = releaseChannel === 'preview';
const proModel = isPreview
? useGemini3_1
? 'gemini-3.1-pro'
: 'gemini-3-pro'
: 'gemini-2.5-pro';
const flashModel = isPreview ? 'gemini-3-flash' : 'gemini-2.5-flash';
return `Let Gemini CLI decide the best model for the task: ${proModel}, ${flashModel}`;
}
/**
* Resolves the requested model alias (e.g., 'auto-gemini-3', 'pro', 'flash', 'flash-lite')
* Resolves the requested model alias (e.g., 'auto', 'pro', 'flash', 'flash-lite')
* to a concrete model name.
*
* @param requestedModel The model alias or concrete model name requested by the user.
@@ -108,6 +126,7 @@ export function resolveModel(
useCustomToolModel: boolean = false,
hasAccessToPreview: boolean = true,
config?: ModelCapabilityContext,
releaseChannel?: string,
): string {
// Defensive check against non-string inputs at runtime
const normalizedModel = Array.isArray(requestedModel)
@@ -116,12 +135,15 @@ export function resolveModel(
? String(requestedModel ?? '').trim() || ''
: requestedModel.trim() || '';
const currentReleaseChannel = releaseChannel ?? config?.getReleaseChannel?.();
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
const resolved = config.modelConfigService.resolveModelId(normalizedModel, {
useGemini3_1,
useGemini3_1FlashLite,
useCustomTools: useCustomToolModel,
hasAccessToPreview,
releaseChannel: currentReleaseChannel,
});
if (!hasAccessToPreview && isPreviewModel(resolved, config)) {
@@ -140,10 +162,16 @@ export function resolveModel(
let resolved: string;
switch (normalizedModel) {
case PREVIEW_GEMINI_MODEL:
case PREVIEW_GEMINI_MODEL_AUTO:
case GEMINI_MODEL_ALIAS_AUTO:
case GEMINI_MODEL_ALIAS_PRO: {
if (currentReleaseChannel === 'stable') {
resolved = DEFAULT_GEMINI_MODEL;
break;
}
// fallthrough
}
case PREVIEW_GEMINI_MODEL:
case PREVIEW_GEMINI_MODEL_AUTO: {
if (useGemini3_1) {
resolved = useCustomToolModel
? PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL
@@ -202,7 +230,7 @@ export function resolveModel(
/**
* Resolves the appropriate model based on the classifier's decision.
*
* @param requestedModel The current requested model (e.g. auto-gemini-2.5).
* @param requestedModel The current requested model (e.g. auto).
* @param modelAlias The alias selected by the classifier ('flash' or 'pro').
* @param useGemini3_1 Whether to use Gemini 3.1 Pro Preview.
* @param useCustomToolModel Whether to use the custom tool model.
@@ -240,17 +268,27 @@ export function resolveClassifierModel(
}
if (
requestedModel === PREVIEW_GEMINI_MODEL_AUTO ||
requestedModel === PREVIEW_GEMINI_MODEL
requestedModel === PREVIEW_GEMINI_MODEL ||
requestedModel === GEMINI_MODEL_ALIAS_AUTO
) {
return PREVIEW_GEMINI_FLASH_MODEL;
return hasAccessToPreview
? PREVIEW_GEMINI_FLASH_MODEL
: DEFAULT_GEMINI_FLASH_MODEL;
}
return resolveModel(GEMINI_MODEL_ALIAS_FLASH);
return resolveModel(
GEMINI_MODEL_ALIAS_FLASH,
false,
false,
false,
hasAccessToPreview,
);
}
return resolveModel(
requestedModel,
useGemini3_1,
useGemini3_1FlashLite,
useCustomToolModel,
hasAccessToPreview,
);
}
@@ -266,6 +304,8 @@ export function getDisplayString(
}
switch (model) {
case GEMINI_MODEL_ALIAS_AUTO:
return 'Auto';
case PREVIEW_GEMINI_MODEL_AUTO:
return 'Auto (Gemini 3)';
case DEFAULT_GEMINI_MODEL_AUTO:
@@ -11,6 +11,7 @@ import {
PREVIEW_GEMINI_3_1_MODEL,
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
isProModel,
getAutoModelDescription,
} from '../config/models.js';
// The primary key for the ModelConfig is the model string. However, we also
@@ -101,6 +102,7 @@ export interface ResolutionContext {
hasAccessToPreview?: boolean;
hasAccessToProModel?: boolean;
requestedModel?: string;
releaseChannel?: string;
}
/** The requirements defined in the registry. */
@@ -111,6 +113,7 @@ export interface ResolutionCondition {
hasAccessToPreview?: boolean;
/** Matches if the current model is in this list. */
requestedModels?: string[];
releaseChannel?: string;
}
export interface ModelConfigServiceConfig {
@@ -156,6 +159,7 @@ export class ModelConfigService {
const shouldShowPreviewModels = context.hasAccessToPreview ?? false;
const useGemini31 = context.useGemini3_1 ?? false;
const useGemini31FlashLite = context.useGemini3_1FlashLite ?? false;
const releaseChannel = context.releaseChannel ?? 'stable';
const mainOptions = Object.entries(definitions)
.filter(([_, m]) => {
@@ -164,18 +168,21 @@ export class ModelConfigService {
if (m.tier !== 'auto') return false;
return true;
})
.map(([id, m]) => ({
modelId: id,
name: m.displayName ?? getDisplayString(id),
description:
id === 'auto-gemini-3' && useGemini31
? (m.dialogDescription ?? '').replace(
'gemini-3-pro',
'gemini-3.1-pro',
)
: (m.dialogDescription ?? ''),
tier: m.tier ?? 'auto',
}));
.map(([id, m]) => {
let description = m.dialogDescription ?? '';
if (id === 'auto') {
description = getAutoModelDescription(releaseChannel, useGemini31);
} else if (id === 'auto-gemini-3' && useGemini31) {
description = description.replace('gemini-3-pro', 'gemini-3.1-pro');
}
return {
modelId: id,
name: m.displayName ?? getDisplayString(id),
description,
tier: m.tier ?? 'auto',
};
});
const manualOptions = Object.entries(definitions)
.filter(([id, m]) => {
@@ -258,6 +265,8 @@ export class ModelConfigService {
!!context.requestedModel &&
value.includes(context.requestedModel)
);
case 'releaseChannel':
return value === context.releaseChannel;
default:
return false;
}
File diff suppressed because one or more lines are too long