mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-30 11:41:00 -07:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0f32ff7d6 | |||
| 1693afe027 | |||
| d179e3673b | |||
| e4d6a5cc7c | |||
| fc4fdc3cbe | |||
| bdbb996a2e |
@@ -677,6 +677,141 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
used.
|
used.
|
||||||
- **Default:** `[]`
|
- **Default:** `[]`
|
||||||
|
|
||||||
|
- **`modelConfigs.modelDefinitions`** (object):
|
||||||
|
- **Description:** Registry of model metadata, including tier, family, and
|
||||||
|
features.
|
||||||
|
- **Default:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"gemini-3.1-pro-preview": {
|
||||||
|
"tier": "pro",
|
||||||
|
"family": "gemini-3",
|
||||||
|
"isPreview": true,
|
||||||
|
"dialogLocation": "manual",
|
||||||
|
"features": {
|
||||||
|
"thinking": true,
|
||||||
|
"multimodalToolUse": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gemini-3.1-pro-preview-customtools": {
|
||||||
|
"tier": "pro",
|
||||||
|
"family": "gemini-3",
|
||||||
|
"isPreview": true,
|
||||||
|
"features": {
|
||||||
|
"thinking": true,
|
||||||
|
"multimodalToolUse": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gemini-3-pro-preview": {
|
||||||
|
"tier": "pro",
|
||||||
|
"family": "gemini-3",
|
||||||
|
"isPreview": true,
|
||||||
|
"dialogLocation": "manual",
|
||||||
|
"features": {
|
||||||
|
"thinking": true,
|
||||||
|
"multimodalToolUse": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gemini-3-flash-preview": {
|
||||||
|
"tier": "flash",
|
||||||
|
"family": "gemini-3",
|
||||||
|
"isPreview": true,
|
||||||
|
"dialogLocation": "manual",
|
||||||
|
"features": {
|
||||||
|
"thinking": false,
|
||||||
|
"multimodalToolUse": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gemini-2.5-pro": {
|
||||||
|
"tier": "pro",
|
||||||
|
"family": "gemini-2.5",
|
||||||
|
"isPreview": false,
|
||||||
|
"dialogLocation": "manual",
|
||||||
|
"features": {
|
||||||
|
"thinking": false,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gemini-2.5-flash": {
|
||||||
|
"tier": "flash",
|
||||||
|
"family": "gemini-2.5",
|
||||||
|
"isPreview": false,
|
||||||
|
"dialogLocation": "manual",
|
||||||
|
"features": {
|
||||||
|
"thinking": false,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gemini-2.5-flash-lite": {
|
||||||
|
"tier": "flash-lite",
|
||||||
|
"family": "gemini-2.5",
|
||||||
|
"isPreview": false,
|
||||||
|
"dialogLocation": "manual",
|
||||||
|
"features": {
|
||||||
|
"thinking": false,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auto": {
|
||||||
|
"tier": "auto",
|
||||||
|
"isPreview": true,
|
||||||
|
"features": {
|
||||||
|
"thinking": true,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pro": {
|
||||||
|
"tier": "pro",
|
||||||
|
"isPreview": false,
|
||||||
|
"features": {
|
||||||
|
"thinking": true,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
"tier": "flash",
|
||||||
|
"isPreview": false,
|
||||||
|
"features": {
|
||||||
|
"thinking": false,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flash-lite": {
|
||||||
|
"tier": "flash-lite",
|
||||||
|
"isPreview": false,
|
||||||
|
"features": {
|
||||||
|
"thinking": false,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auto-gemini-3": {
|
||||||
|
"displayName": "Auto (Gemini 3)",
|
||||||
|
"tier": "auto",
|
||||||
|
"isPreview": true,
|
||||||
|
"dialogLocation": "main",
|
||||||
|
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash",
|
||||||
|
"features": {
|
||||||
|
"thinking": true,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auto-gemini-2.5": {
|
||||||
|
"displayName": "Auto (Gemini 2.5)",
|
||||||
|
"tier": "auto",
|
||||||
|
"isPreview": false,
|
||||||
|
"dialogLocation": "main",
|
||||||
|
"dialogDescription": "Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash",
|
||||||
|
"features": {
|
||||||
|
"thinking": false,
|
||||||
|
"multimodalToolUse": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
#### `agents`
|
#### `agents`
|
||||||
|
|
||||||
- **`agents.overrides`** (object):
|
- **`agents.overrides`** (object):
|
||||||
@@ -1068,6 +1203,12 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
- **Requires restart:** Yes
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
|
- **`experimental.dynamicModelConfiguration`** (boolean):
|
||||||
|
- **Description:** Enable dynamic model configuration (definitions,
|
||||||
|
resolutions, and chains) via settings.
|
||||||
|
- **Default:** `false`
|
||||||
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
- **`experimental.gemmaModelRouter.enabled`** (boolean):
|
- **`experimental.gemmaModelRouter.enabled`** (boolean):
|
||||||
- **Description:** Enable the Gemma Model Router (experimental). Requires a
|
- **Description:** Enable the Gemma Model Router (experimental). Requires a
|
||||||
local endpoint serving Gemma via the Gemini API using LiteRT-LM shim.
|
local endpoint serving Gemma via the Gemini API using LiteRT-LM shim.
|
||||||
|
|||||||
@@ -857,6 +857,7 @@ export async function loadCliConfig(
|
|||||||
disableLLMCorrection: settings.tools?.disableLLMCorrection,
|
disableLLMCorrection: settings.tools?.disableLLMCorrection,
|
||||||
rawOutput: argv.rawOutput,
|
rawOutput: argv.rawOutput,
|
||||||
acceptRawOutputRisk: argv.acceptRawOutputRisk,
|
acceptRawOutputRisk: argv.acceptRawOutputRisk,
|
||||||
|
dynamicModelConfiguration: settings.experimental?.dynamicModelConfiguration,
|
||||||
modelConfigServiceConfig: settings.modelConfigs,
|
modelConfigServiceConfig: settings.modelConfigs,
|
||||||
// TODO: loading of hooks based on workspace trust
|
// TODO: loading of hooks based on workspace trust
|
||||||
enableHooks: settings.hooksConfig.enabled,
|
enableHooks: settings.hooksConfig.enabled,
|
||||||
|
|||||||
@@ -1039,6 +1039,20 @@ const SETTINGS_SCHEMA = {
|
|||||||
'Apply specific configuration overrides based on matches, with a primary key of model (or alias). The most specific match will be used.',
|
'Apply specific configuration overrides based on matches, with a primary key of model (or alias). The most specific match will be used.',
|
||||||
showInDialog: false,
|
showInDialog: false,
|
||||||
},
|
},
|
||||||
|
modelDefinitions: {
|
||||||
|
type: 'object',
|
||||||
|
label: 'Model Definitions',
|
||||||
|
category: 'Model',
|
||||||
|
requiresRestart: true,
|
||||||
|
default: DEFAULT_MODEL_CONFIGS.modelDefinitions,
|
||||||
|
description:
|
||||||
|
'Registry of model metadata, including tier, family, and features.',
|
||||||
|
showInDialog: false,
|
||||||
|
additionalProperties: {
|
||||||
|
type: 'object',
|
||||||
|
ref: 'ModelDefinition',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1910,6 +1924,16 @@ const SETTINGS_SCHEMA = {
|
|||||||
'Enable web fetch behavior that bypasses LLM summarization.',
|
'Enable web fetch behavior that bypasses LLM summarization.',
|
||||||
showInDialog: true,
|
showInDialog: true,
|
||||||
},
|
},
|
||||||
|
dynamicModelConfiguration: {
|
||||||
|
type: 'boolean',
|
||||||
|
label: 'Dynamic Model Configuration',
|
||||||
|
category: 'Experimental',
|
||||||
|
requiresRestart: true,
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
'Enable dynamic model configuration (definitions, resolutions, and chains) via settings.',
|
||||||
|
showInDialog: false,
|
||||||
|
},
|
||||||
gemmaModelRouter: {
|
gemmaModelRouter: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
label: 'Gemma Model Router',
|
label: 'Gemma Model Router',
|
||||||
@@ -2726,6 +2750,25 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ModelDefinition: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Model metadata registry entry.',
|
||||||
|
properties: {
|
||||||
|
displayName: { type: 'string' },
|
||||||
|
tier: { enum: ['pro', 'flash', 'flash-lite', 'custom', 'auto'] },
|
||||||
|
family: { type: 'string' },
|
||||||
|
isPreview: { type: 'boolean' },
|
||||||
|
dialogLocation: { enum: ['main', 'manual'] },
|
||||||
|
dialogDescription: { type: 'string' },
|
||||||
|
features: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
thinking: { type: 'boolean' },
|
||||||
|
multimodalToolUse: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getSettingsSchema(): SettingsSchemaType {
|
export function getSettingsSchema(): SettingsSchemaType {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
} from '../utils/displayUtils.js';
|
} from '../utils/displayUtils.js';
|
||||||
import { computeSessionStats } from '../utils/computeStats.js';
|
import { computeSessionStats } from '../utils/computeStats.js';
|
||||||
import {
|
import {
|
||||||
|
type Config,
|
||||||
type RetrieveUserQuotaResponse,
|
type RetrieveUserQuotaResponse,
|
||||||
isActiveModel,
|
isActiveModel,
|
||||||
getDisplayString,
|
getDisplayString,
|
||||||
@@ -88,13 +89,16 @@ const Section: React.FC<SectionProps> = ({ title, children }) => (
|
|||||||
// Logic for building the unified list of table rows
|
// Logic for building the unified list of table rows
|
||||||
const buildModelRows = (
|
const buildModelRows = (
|
||||||
models: Record<string, ModelMetrics>,
|
models: Record<string, ModelMetrics>,
|
||||||
|
config: Config,
|
||||||
quotas?: RetrieveUserQuotaResponse,
|
quotas?: RetrieveUserQuotaResponse,
|
||||||
useGemini3_1 = false,
|
useGemini3_1 = false,
|
||||||
useCustomToolModel = false,
|
useCustomToolModel = false,
|
||||||
) => {
|
) => {
|
||||||
const getBaseModelName = (name: string) => name.replace('-001', '');
|
const getBaseModelName = (name: string) => name.replace('-001', '');
|
||||||
const usedModelNames = new Set(
|
const usedModelNames = new Set(
|
||||||
Object.keys(models).map(getBaseModelName).map(getDisplayString),
|
Object.keys(models)
|
||||||
|
.map(getBaseModelName)
|
||||||
|
.map((name) => getDisplayString(name, config)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 1. Models with active usage
|
// 1. Models with active usage
|
||||||
@@ -104,7 +108,7 @@ const buildModelRows = (
|
|||||||
const inputTokens = metrics.tokens.input;
|
const inputTokens = metrics.tokens.input;
|
||||||
return {
|
return {
|
||||||
key: name,
|
key: name,
|
||||||
modelName: getDisplayString(modelName),
|
modelName: getDisplayString(modelName, config),
|
||||||
requests: metrics.api.totalRequests,
|
requests: metrics.api.totalRequests,
|
||||||
cachedTokens: cachedTokens.toLocaleString(),
|
cachedTokens: cachedTokens.toLocaleString(),
|
||||||
inputTokens: inputTokens.toLocaleString(),
|
inputTokens: inputTokens.toLocaleString(),
|
||||||
@@ -121,11 +125,11 @@ const buildModelRows = (
|
|||||||
(b) =>
|
(b) =>
|
||||||
b.modelId &&
|
b.modelId &&
|
||||||
isActiveModel(b.modelId, useGemini3_1, useCustomToolModel) &&
|
isActiveModel(b.modelId, useGemini3_1, useCustomToolModel) &&
|
||||||
!usedModelNames.has(getDisplayString(b.modelId)),
|
!usedModelNames.has(getDisplayString(b.modelId, config)),
|
||||||
)
|
)
|
||||||
.map((bucket) => ({
|
.map((bucket) => ({
|
||||||
key: bucket.modelId!,
|
key: bucket.modelId!,
|
||||||
modelName: getDisplayString(bucket.modelId!),
|
modelName: getDisplayString(bucket.modelId!, config),
|
||||||
requests: '-',
|
requests: '-',
|
||||||
cachedTokens: '-',
|
cachedTokens: '-',
|
||||||
inputTokens: '-',
|
inputTokens: '-',
|
||||||
@@ -139,6 +143,7 @@ const buildModelRows = (
|
|||||||
|
|
||||||
const ModelUsageTable: React.FC<{
|
const ModelUsageTable: React.FC<{
|
||||||
models: Record<string, ModelMetrics>;
|
models: Record<string, ModelMetrics>;
|
||||||
|
config: Config;
|
||||||
quotas?: RetrieveUserQuotaResponse;
|
quotas?: RetrieveUserQuotaResponse;
|
||||||
cacheEfficiency: number;
|
cacheEfficiency: number;
|
||||||
totalCachedTokens: number;
|
totalCachedTokens: number;
|
||||||
@@ -150,6 +155,7 @@ const ModelUsageTable: React.FC<{
|
|||||||
useCustomToolModel?: boolean;
|
useCustomToolModel?: boolean;
|
||||||
}> = ({
|
}> = ({
|
||||||
models,
|
models,
|
||||||
|
config,
|
||||||
quotas,
|
quotas,
|
||||||
cacheEfficiency,
|
cacheEfficiency,
|
||||||
totalCachedTokens,
|
totalCachedTokens,
|
||||||
@@ -162,7 +168,13 @@ const ModelUsageTable: React.FC<{
|
|||||||
}) => {
|
}) => {
|
||||||
const { stdout } = useStdout();
|
const { stdout } = useStdout();
|
||||||
const terminalWidth = stdout?.columns ?? 84;
|
const terminalWidth = stdout?.columns ?? 84;
|
||||||
const rows = buildModelRows(models, quotas, useGemini3_1, useCustomToolModel);
|
const rows = buildModelRows(
|
||||||
|
models,
|
||||||
|
config,
|
||||||
|
quotas,
|
||||||
|
useGemini3_1,
|
||||||
|
useCustomToolModel,
|
||||||
|
);
|
||||||
|
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -676,6 +688,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
|||||||
</Section>
|
</Section>
|
||||||
<ModelUsageTable
|
<ModelUsageTable
|
||||||
models={models}
|
models={models}
|
||||||
|
config={config}
|
||||||
quotas={quotas}
|
quotas={quotas}
|
||||||
cacheEfficiency={computed.cacheEfficiency}
|
cacheEfficiency={computed.cacheEfficiency}
|
||||||
totalCachedTokens={computed.totalCachedTokens}
|
totalCachedTokens={computed.totalCachedTokens}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export const BrowserAgentDefinition = (
|
|||||||
): LocalAgentDefinition<typeof BrowserTaskResultSchema> => {
|
): LocalAgentDefinition<typeof BrowserTaskResultSchema> => {
|
||||||
// Use Preview Flash model if the main model is any of the preview models.
|
// Use Preview Flash model if the main model is any of the preview models.
|
||||||
// If the main model is not a preview model, use the default flash model.
|
// If the main model is not a preview model, use the default flash model.
|
||||||
const model = isPreviewModel(config.getModel())
|
const model = isPreviewModel(config.getModel(), config)
|
||||||
? PREVIEW_GEMINI_FLASH_MODEL
|
? PREVIEW_GEMINI_FLASH_MODEL
|
||||||
: DEFAULT_GEMINI_FLASH_MODEL;
|
: DEFAULT_GEMINI_FLASH_MODEL;
|
||||||
|
|
||||||
|
|||||||
@@ -54,19 +54,21 @@ export function resolvePolicyChain(
|
|||||||
useCustomToolModel,
|
useCustomToolModel,
|
||||||
hasAccessToPreview,
|
hasAccessToPreview,
|
||||||
);
|
);
|
||||||
const isAutoPreferred = preferredModel ? isAutoModel(preferredModel) : false;
|
const isAutoPreferred = preferredModel
|
||||||
const isAutoConfigured = isAutoModel(configuredModel);
|
? isAutoModel(preferredModel, config)
|
||||||
|
: false;
|
||||||
|
const isAutoConfigured = isAutoModel(configuredModel, config);
|
||||||
|
|
||||||
if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) {
|
if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) {
|
||||||
chain = getFlashLitePolicyChain();
|
chain = getFlashLitePolicyChain();
|
||||||
} else if (
|
} else if (
|
||||||
isGemini3Model(resolvedModel) ||
|
isGemini3Model(resolvedModel, config) ||
|
||||||
isAutoPreferred ||
|
isAutoPreferred ||
|
||||||
isAutoConfigured
|
isAutoConfigured
|
||||||
) {
|
) {
|
||||||
if (hasAccessToPreview) {
|
if (hasAccessToPreview) {
|
||||||
const previewEnabled =
|
const previewEnabled =
|
||||||
isGemini3Model(resolvedModel) ||
|
isGemini3Model(resolvedModel, config) ||
|
||||||
preferredModel === PREVIEW_GEMINI_MODEL_AUTO ||
|
preferredModel === PREVIEW_GEMINI_MODEL_AUTO ||
|
||||||
configuredModel === PREVIEW_GEMINI_MODEL_AUTO;
|
configuredModel === PREVIEW_GEMINI_MODEL_AUTO;
|
||||||
chain = getModelPolicyChain({
|
chain = getModelPolicyChain({
|
||||||
|
|||||||
@@ -601,6 +601,7 @@ export interface ConfigParameters {
|
|||||||
disableYoloMode?: boolean;
|
disableYoloMode?: boolean;
|
||||||
rawOutput?: boolean;
|
rawOutput?: boolean;
|
||||||
acceptRawOutputRisk?: boolean;
|
acceptRawOutputRisk?: boolean;
|
||||||
|
dynamicModelConfiguration?: boolean;
|
||||||
modelConfigServiceConfig?: ModelConfigServiceConfig;
|
modelConfigServiceConfig?: ModelConfigServiceConfig;
|
||||||
enableHooks?: boolean;
|
enableHooks?: boolean;
|
||||||
enableHooksUI?: boolean;
|
enableHooksUI?: boolean;
|
||||||
@@ -799,6 +800,7 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
private readonly disableYoloMode: boolean;
|
private readonly disableYoloMode: boolean;
|
||||||
private readonly rawOutput: boolean;
|
private readonly rawOutput: boolean;
|
||||||
private readonly acceptRawOutputRisk: boolean;
|
private readonly acceptRawOutputRisk: boolean;
|
||||||
|
private readonly dynamicModelConfiguration: boolean;
|
||||||
private pendingIncludeDirectories: string[];
|
private pendingIncludeDirectories: string[];
|
||||||
private readonly enableHooks: boolean;
|
private readonly enableHooks: boolean;
|
||||||
private readonly enableHooksUI: boolean;
|
private readonly enableHooksUI: boolean;
|
||||||
@@ -987,7 +989,7 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
this.truncateToolOutputThreshold =
|
this.truncateToolOutputThreshold =
|
||||||
params.truncateToolOutputThreshold ??
|
params.truncateToolOutputThreshold ??
|
||||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD;
|
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD;
|
||||||
this.useWriteTodos = isPreviewModel(this.model)
|
this.useWriteTodos = isPreviewModel(this.model, this)
|
||||||
? false
|
? false
|
||||||
: (params.useWriteTodos ?? true);
|
: (params.useWriteTodos ?? true);
|
||||||
this.workspacePoliciesDir = params.workspacePoliciesDir;
|
this.workspacePoliciesDir = params.workspacePoliciesDir;
|
||||||
@@ -1062,6 +1064,7 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
this.disableYoloMode = params.disableYoloMode ?? false;
|
this.disableYoloMode = params.disableYoloMode ?? false;
|
||||||
this.rawOutput = params.rawOutput ?? false;
|
this.rawOutput = params.rawOutput ?? false;
|
||||||
this.acceptRawOutputRisk = params.acceptRawOutputRisk ?? false;
|
this.acceptRawOutputRisk = params.acceptRawOutputRisk ?? false;
|
||||||
|
this.dynamicModelConfiguration = params.dynamicModelConfiguration ?? false;
|
||||||
|
|
||||||
if (params.hooks) {
|
if (params.hooks) {
|
||||||
this.hooks = params.hooks;
|
this.hooks = params.hooks;
|
||||||
@@ -1111,18 +1114,23 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
// remove this hack.
|
// remove this hack.
|
||||||
let modelConfigServiceConfig = params.modelConfigServiceConfig;
|
let modelConfigServiceConfig = params.modelConfigServiceConfig;
|
||||||
if (modelConfigServiceConfig) {
|
if (modelConfigServiceConfig) {
|
||||||
if (!modelConfigServiceConfig.aliases) {
|
// Ensure user-defined model definitions augment, not replace, the defaults.
|
||||||
modelConfigServiceConfig = {
|
const mergedModelDefinitions = {
|
||||||
...modelConfigServiceConfig,
|
...DEFAULT_MODEL_CONFIGS.modelDefinitions,
|
||||||
aliases: DEFAULT_MODEL_CONFIGS.aliases,
|
...modelConfigServiceConfig.modelDefinitions,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
if (!modelConfigServiceConfig.overrides) {
|
modelConfigServiceConfig = {
|
||||||
modelConfigServiceConfig = {
|
// Preserve other user settings like customAliases
|
||||||
...modelConfigServiceConfig,
|
...modelConfigServiceConfig,
|
||||||
overrides: DEFAULT_MODEL_CONFIGS.overrides,
|
// Apply defaults for aliases and overrides if they are not provided
|
||||||
};
|
aliases:
|
||||||
}
|
modelConfigServiceConfig.aliases ?? DEFAULT_MODEL_CONFIGS.aliases,
|
||||||
|
overrides:
|
||||||
|
modelConfigServiceConfig.overrides ?? DEFAULT_MODEL_CONFIGS.overrides,
|
||||||
|
// Use the merged model definitions
|
||||||
|
modelDefinitions: mergedModelDefinitions,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modelConfigService = new ModelConfigService(
|
this.modelConfigService = new ModelConfigService(
|
||||||
@@ -1325,7 +1333,10 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
|
|
||||||
// Only reset when we have explicit "no access" (hasAccessToPreviewModel === false).
|
// Only reset when we have explicit "no access" (hasAccessToPreviewModel === false).
|
||||||
// When null (quota not fetched) or true, we preserve the saved model.
|
// When null (quota not fetched) or true, we preserve the saved model.
|
||||||
if (isPreviewModel(this.model) && this.hasAccessToPreviewModel === false) {
|
if (
|
||||||
|
isPreviewModel(this.model, this) &&
|
||||||
|
this.hasAccessToPreviewModel === false
|
||||||
|
) {
|
||||||
this.setModel(DEFAULT_GEMINI_MODEL_AUTO);
|
this.setModel(DEFAULT_GEMINI_MODEL_AUTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1593,7 +1604,7 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
|
|
||||||
const isPreview =
|
const isPreview =
|
||||||
model === PREVIEW_GEMINI_MODEL_AUTO ||
|
model === PREVIEW_GEMINI_MODEL_AUTO ||
|
||||||
isPreviewModel(this.getActiveModel());
|
isPreviewModel(this.getActiveModel(), this);
|
||||||
const proModel = isPreview ? PREVIEW_GEMINI_MODEL : DEFAULT_GEMINI_MODEL;
|
const proModel = isPreview ? PREVIEW_GEMINI_MODEL : DEFAULT_GEMINI_MODEL;
|
||||||
const flashModel = isPreview
|
const flashModel = isPreview
|
||||||
? PREVIEW_GEMINI_FLASH_MODEL
|
? PREVIEW_GEMINI_FLASH_MODEL
|
||||||
@@ -1791,8 +1802,9 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasAccess =
|
const hasAccess =
|
||||||
quota.buckets?.some((b) => b.modelId && isPreviewModel(b.modelId)) ??
|
quota.buckets?.some(
|
||||||
false;
|
(b) => b.modelId && isPreviewModel(b.modelId, this),
|
||||||
|
) ?? false;
|
||||||
this.setHasAccessToPreviewModel(hasAccess);
|
this.setHasAccessToPreviewModel(hasAccess);
|
||||||
return quota;
|
return quota;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -2184,6 +2196,10 @@ export class Config implements McpContext, AgentLoopContext {
|
|||||||
return this.acceptRawOutputRisk;
|
return this.acceptRawOutputRisk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getExperimentalDynamicModelConfiguration(): boolean {
|
||||||
|
return this.dynamicModelConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
getPendingIncludeDirectories(): string[] {
|
getPendingIncludeDirectories(): string[] {
|
||||||
return this.pendingIncludeDirectories;
|
return this.pendingIncludeDirectories;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,4 +249,94 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
modelDefinitions: {
|
||||||
|
// Concrete Models
|
||||||
|
'gemini-3.1-pro-preview': {
|
||||||
|
tier: 'pro',
|
||||||
|
family: 'gemini-3',
|
||||||
|
isPreview: true,
|
||||||
|
dialogLocation: 'manual',
|
||||||
|
features: { thinking: true, multimodalToolUse: true },
|
||||||
|
},
|
||||||
|
'gemini-3.1-pro-preview-customtools': {
|
||||||
|
tier: 'pro',
|
||||||
|
family: 'gemini-3',
|
||||||
|
isPreview: true,
|
||||||
|
features: { thinking: true, multimodalToolUse: true },
|
||||||
|
},
|
||||||
|
'gemini-3-pro-preview': {
|
||||||
|
tier: 'pro',
|
||||||
|
family: 'gemini-3',
|
||||||
|
isPreview: true,
|
||||||
|
dialogLocation: 'manual',
|
||||||
|
features: { thinking: true, multimodalToolUse: true },
|
||||||
|
},
|
||||||
|
'gemini-3-flash-preview': {
|
||||||
|
tier: 'flash',
|
||||||
|
family: 'gemini-3',
|
||||||
|
isPreview: true,
|
||||||
|
dialogLocation: 'manual',
|
||||||
|
features: { thinking: false, multimodalToolUse: true },
|
||||||
|
},
|
||||||
|
'gemini-2.5-pro': {
|
||||||
|
tier: 'pro',
|
||||||
|
family: 'gemini-2.5',
|
||||||
|
isPreview: false,
|
||||||
|
dialogLocation: 'manual',
|
||||||
|
features: { thinking: false, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
'gemini-2.5-flash': {
|
||||||
|
tier: 'flash',
|
||||||
|
family: 'gemini-2.5',
|
||||||
|
isPreview: false,
|
||||||
|
dialogLocation: 'manual',
|
||||||
|
features: { thinking: false, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
'gemini-2.5-flash-lite': {
|
||||||
|
tier: 'flash-lite',
|
||||||
|
family: 'gemini-2.5',
|
||||||
|
isPreview: false,
|
||||||
|
dialogLocation: 'manual',
|
||||||
|
features: { thinking: false, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
// Aliases
|
||||||
|
auto: {
|
||||||
|
tier: 'auto',
|
||||||
|
isPreview: true,
|
||||||
|
features: { thinking: true, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
pro: {
|
||||||
|
tier: 'pro',
|
||||||
|
isPreview: false,
|
||||||
|
features: { thinking: true, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
flash: {
|
||||||
|
tier: 'flash',
|
||||||
|
isPreview: false,
|
||||||
|
features: { thinking: false, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
'flash-lite': {
|
||||||
|
tier: 'flash-lite',
|
||||||
|
isPreview: false,
|
||||||
|
features: { thinking: false, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
'auto-gemini-3': {
|
||||||
|
displayName: 'Auto (Gemini 3)',
|
||||||
|
tier: 'auto',
|
||||||
|
isPreview: true,
|
||||||
|
dialogLocation: 'main',
|
||||||
|
dialogDescription:
|
||||||
|
'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash',
|
||||||
|
features: { thinking: true, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
'auto-gemini-2.5': {
|
||||||
|
displayName: 'Auto (Gemini 2.5)',
|
||||||
|
tier: 'auto',
|
||||||
|
isPreview: false,
|
||||||
|
dialogLocation: 'main',
|
||||||
|
dialogDescription:
|
||||||
|
'Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash',
|
||||||
|
features: { thinking: false, multimodalToolUse: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,121 @@ import {
|
|||||||
VALID_GEMINI_MODELS,
|
VALID_GEMINI_MODELS,
|
||||||
VALID_ALIASES,
|
VALID_ALIASES,
|
||||||
} from './models.js';
|
} from './models.js';
|
||||||
|
import type { Config } from './config.js';
|
||||||
|
import { ModelConfigService } from '../services/modelConfigService.js';
|
||||||
|
import { DEFAULT_MODEL_CONFIGS } from './defaultModelConfigs.js';
|
||||||
|
|
||||||
|
const modelConfigService = new ModelConfigService(DEFAULT_MODEL_CONFIGS);
|
||||||
|
|
||||||
|
const dynamicConfig = {
|
||||||
|
getExperimentalDynamicModelConfiguration: () => true,
|
||||||
|
modelConfigService,
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
const legacyConfig = {
|
||||||
|
getExperimentalDynamicModelConfiguration: () => false,
|
||||||
|
modelConfigService,
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
describe('Dynamic Configuration Parity', () => {
|
||||||
|
const modelsToTest = [
|
||||||
|
GEMINI_MODEL_ALIAS_AUTO,
|
||||||
|
GEMINI_MODEL_ALIAS_PRO,
|
||||||
|
GEMINI_MODEL_ALIAS_FLASH,
|
||||||
|
GEMINI_MODEL_ALIAS_FLASH_LITE,
|
||||||
|
PREVIEW_GEMINI_MODEL_AUTO,
|
||||||
|
DEFAULT_GEMINI_MODEL_AUTO,
|
||||||
|
PREVIEW_GEMINI_MODEL,
|
||||||
|
DEFAULT_GEMINI_MODEL,
|
||||||
|
'custom-model',
|
||||||
|
];
|
||||||
|
|
||||||
|
it('getDisplayString should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = getDisplayString(model, legacyConfig);
|
||||||
|
const dynamic = getDisplayString(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isPreviewModel should match legacy behavior', () => {
|
||||||
|
const allModels = [
|
||||||
|
...modelsToTest,
|
||||||
|
PREVIEW_GEMINI_3_1_MODEL,
|
||||||
|
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
|
||||||
|
PREVIEW_GEMINI_FLASH_MODEL,
|
||||||
|
];
|
||||||
|
for (const model of allModels) {
|
||||||
|
const legacy = isPreviewModel(model, legacyConfig);
|
||||||
|
const dynamic = isPreviewModel(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isProModel should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = isProModel(model, legacyConfig);
|
||||||
|
const dynamic = isProModel(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isGemini3Model should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = isGemini3Model(model, legacyConfig);
|
||||||
|
const dynamic = isGemini3Model(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isGemini2Model should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = isGemini2Model(model, legacyConfig);
|
||||||
|
const dynamic = isGemini2Model(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isCustomModel should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = isCustomModel(model, legacyConfig);
|
||||||
|
const dynamic = isCustomModel(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supportsModernFeatures should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = supportsModernFeatures(model, legacyConfig);
|
||||||
|
const dynamic = supportsModernFeatures(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isActiveModel should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = isActiveModel(model, false, false, legacyConfig);
|
||||||
|
const dynamic = isActiveModel(model, false, false, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isValidModelOrAlias should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = isValidModelOrAlias(model, legacyConfig);
|
||||||
|
const dynamic = isValidModelOrAlias(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supportsMultimodalFunctionResponse should match legacy behavior', () => {
|
||||||
|
for (const model of modelsToTest) {
|
||||||
|
const legacy = supportsMultimodalFunctionResponse(model, legacyConfig);
|
||||||
|
const dynamic = supportsMultimodalFunctionResponse(model, dynamicConfig);
|
||||||
|
expect(dynamic).toBe(legacy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('isPreviewModel', () => {
|
describe('isPreviewModel', () => {
|
||||||
it('should return true for preview models', () => {
|
it('should return true for preview models', () => {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { ModelConfigService } from '../services/modelConfigService.js';
|
||||||
|
|
||||||
export const PREVIEW_GEMINI_MODEL = 'gemini-3-pro-preview';
|
export const PREVIEW_GEMINI_MODEL = 'gemini-3-pro-preview';
|
||||||
export const PREVIEW_GEMINI_3_1_MODEL = 'gemini-3.1-pro-preview';
|
export const PREVIEW_GEMINI_3_1_MODEL = 'gemini-3.1-pro-preview';
|
||||||
export const PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL =
|
export const PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL =
|
||||||
@@ -46,6 +48,15 @@ export const DEFAULT_GEMINI_EMBEDDING_MODEL = 'gemini-embedding-001';
|
|||||||
// Cap the thinking at 8192 to prevent run-away thinking loops.
|
// Cap the thinking at 8192 to prevent run-away thinking loops.
|
||||||
export const DEFAULT_THINKING_MODE = 8192;
|
export const DEFAULT_THINKING_MODE = 8192;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the minimal configuration needed to resolve models dynamically.
|
||||||
|
* This breaks circular dependencies by avoiding an import of the full Config class.
|
||||||
|
*/
|
||||||
|
export interface ModelResolutionContext {
|
||||||
|
getExperimentalDynamicModelConfiguration?: () => boolean;
|
||||||
|
modelConfigService: ModelConfigService;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the requested model alias (e.g., 'auto-gemini-3', 'pro', 'flash', 'flash-lite')
|
* Resolves the requested model alias (e.g., 'auto-gemini-3', 'pro', 'flash', 'flash-lite')
|
||||||
* to a concrete model name.
|
* to a concrete model name.
|
||||||
@@ -148,7 +159,17 @@ export function resolveClassifierModel(
|
|||||||
}
|
}
|
||||||
return resolveModel(requestedModel, useGemini3_1, useCustomToolModel);
|
return resolveModel(requestedModel, useGemini3_1, useCustomToolModel);
|
||||||
}
|
}
|
||||||
export function getDisplayString(model: string) {
|
export function getDisplayString(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
) {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
const definition = config.modelConfigService.getModelDefinition(model);
|
||||||
|
if (definition?.displayName) {
|
||||||
|
return definition.displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (model) {
|
switch (model) {
|
||||||
case PREVIEW_GEMINI_MODEL_AUTO:
|
case PREVIEW_GEMINI_MODEL_AUTO:
|
||||||
return 'Auto (Gemini 3)';
|
return 'Auto (Gemini 3)';
|
||||||
@@ -169,9 +190,19 @@ export function getDisplayString(model: string) {
|
|||||||
* Checks if the model is a preview model.
|
* Checks if the model is a preview model.
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model is a preview model.
|
* @returns True if the model is a preview model.
|
||||||
*/
|
*/
|
||||||
export function isPreviewModel(model: string): boolean {
|
export function isPreviewModel(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
return (
|
||||||
|
config.modelConfigService.getModelDefinition(model)?.isPreview === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
model === PREVIEW_GEMINI_MODEL ||
|
model === PREVIEW_GEMINI_MODEL ||
|
||||||
model === PREVIEW_GEMINI_3_1_MODEL ||
|
model === PREVIEW_GEMINI_3_1_MODEL ||
|
||||||
@@ -186,9 +217,16 @@ export function isPreviewModel(model: string): boolean {
|
|||||||
* Checks if the model is a Pro model.
|
* Checks if the model is a Pro model.
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model is a Pro model.
|
* @returns True if the model is a Pro model.
|
||||||
*/
|
*/
|
||||||
export function isProModel(model: string): boolean {
|
export function isProModel(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
return config.modelConfigService.getModelDefinition(model)?.tier === 'pro';
|
||||||
|
}
|
||||||
return model.toLowerCase().includes('pro');
|
return model.toLowerCase().includes('pro');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,9 +234,22 @@ export function isProModel(model: string): boolean {
|
|||||||
* Checks if the model is a Gemini 3 model.
|
* Checks if the model is a Gemini 3 model.
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model is a Gemini 3 model.
|
* @returns True if the model is a Gemini 3 model.
|
||||||
*/
|
*/
|
||||||
export function isGemini3Model(model: string): boolean {
|
export function isGemini3Model(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
// Legacy behavior resolves the model first.
|
||||||
|
const resolved = resolveModel(model);
|
||||||
|
return (
|
||||||
|
config.modelConfigService.getModelDefinition(resolved)?.family ===
|
||||||
|
'gemini-3'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const resolved = resolveModel(model);
|
const resolved = resolveModel(model);
|
||||||
return /^gemini-3(\.|-|$)/.test(resolved);
|
return /^gemini-3(\.|-|$)/.test(resolved);
|
||||||
}
|
}
|
||||||
@@ -207,9 +258,20 @@ export function isGemini3Model(model: string): boolean {
|
|||||||
* Checks if the model is a Gemini 2.x model.
|
* Checks if the model is a Gemini 2.x model.
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model is a Gemini-2.x model.
|
* @returns True if the model is a Gemini-2.x model.
|
||||||
*/
|
*/
|
||||||
export function isGemini2Model(model: string): boolean {
|
export function isGemini2Model(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
// Legacy behavior does NOT resolve the model first for Gemini 2 check.
|
||||||
|
return (
|
||||||
|
config.modelConfigService.getModelDefinition(model)?.family ===
|
||||||
|
'gemini-2.5'
|
||||||
|
);
|
||||||
|
}
|
||||||
return /^gemini-2(\.|$)/.test(model);
|
return /^gemini-2(\.|$)/.test(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,9 +279,20 @@ export function isGemini2Model(model: string): boolean {
|
|||||||
* Checks if the model is a "custom" model (not Gemini branded).
|
* Checks if the model is a "custom" model (not Gemini branded).
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model is not a Gemini branded model.
|
* @returns True if the model is not a Gemini branded model.
|
||||||
*/
|
*/
|
||||||
export function isCustomModel(model: string): boolean {
|
export function isCustomModel(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
const resolved = resolveModel(model);
|
||||||
|
return (
|
||||||
|
config.modelConfigService.getModelDefinition(resolved)?.tier ===
|
||||||
|
'custom' || !resolved.startsWith('gemini-')
|
||||||
|
);
|
||||||
|
}
|
||||||
const resolved = resolveModel(model);
|
const resolved = resolveModel(model);
|
||||||
return !resolved.startsWith('gemini-');
|
return !resolved.startsWith('gemini-');
|
||||||
}
|
}
|
||||||
@@ -229,20 +302,31 @@ export function isCustomModel(model: string): boolean {
|
|||||||
* This includes Gemini 3 models and any custom models.
|
* This includes Gemini 3 models and any custom models.
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model supports modern features like thoughts.
|
* @returns True if the model supports modern features like thoughts.
|
||||||
*/
|
*/
|
||||||
export function supportsModernFeatures(model: string): boolean {
|
export function supportsModernFeatures(
|
||||||
if (isGemini3Model(model)) return true;
|
model: string,
|
||||||
return isCustomModel(model);
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (isGemini3Model(model, config)) return true;
|
||||||
|
return isCustomModel(model, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the model is an auto model.
|
* Checks if the model is an auto model.
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model is an auto model.
|
* @returns True if the model is an auto model.
|
||||||
*/
|
*/
|
||||||
export function isAutoModel(model: string): boolean {
|
export function isAutoModel(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
return config.modelConfigService.getModelDefinition(model)?.tier === 'auto';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
model === GEMINI_MODEL_ALIAS_AUTO ||
|
model === GEMINI_MODEL_ALIAS_AUTO ||
|
||||||
model === PREVIEW_GEMINI_MODEL_AUTO ||
|
model === PREVIEW_GEMINI_MODEL_AUTO ||
|
||||||
@@ -255,9 +339,23 @@ export function isAutoModel(model: string): boolean {
|
|||||||
* This is supported in Gemini 3.
|
* This is supported in Gemini 3.
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model supports multimodal function responses.
|
* @returns True if the model supports multimodal function responses.
|
||||||
*/
|
*/
|
||||||
export function supportsMultimodalFunctionResponse(model: string): boolean {
|
export function supportsMultimodalFunctionResponse(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
if (VALID_ALIASES.has(model)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const resolved = resolveModel(model);
|
||||||
|
return (
|
||||||
|
config.modelConfigService.getModelDefinition(resolved)?.features
|
||||||
|
?.multimodalToolUse === true
|
||||||
|
);
|
||||||
|
}
|
||||||
return model.startsWith('gemini-3-');
|
return model.startsWith('gemini-3-');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,16 +364,32 @@ export function supportsMultimodalFunctionResponse(model: string): boolean {
|
|||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
* @param useGemini3_1 Whether Gemini 3.1 Pro Preview is enabled.
|
* @param useGemini3_1 Whether Gemini 3.1 Pro Preview is enabled.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model is active.
|
* @returns True if the model is active.
|
||||||
*/
|
*/
|
||||||
export function isActiveModel(
|
export function isActiveModel(
|
||||||
model: string,
|
model: string,
|
||||||
useGemini3_1: boolean = false,
|
useGemini3_1: boolean = false,
|
||||||
useCustomToolModel: boolean = false,
|
useCustomToolModel: boolean = false,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!VALID_GEMINI_MODELS.has(model)) {
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
if (VALID_ALIASES.has(model)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const definition = config.modelConfigService.getModelDefinition(model);
|
||||||
|
if (!definition) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy logic only considers explicit Gemini models as "active".
|
||||||
|
if (definition.tier === 'custom' && !model.startsWith('gemini-')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!VALID_GEMINI_MODELS.has(model)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useGemini3_1) {
|
if (useGemini3_1) {
|
||||||
if (model === PREVIEW_GEMINI_MODEL) {
|
if (model === PREVIEW_GEMINI_MODEL) {
|
||||||
return false;
|
return false;
|
||||||
@@ -297,9 +411,19 @@ export function isActiveModel(
|
|||||||
* Checks if the model name is valid (either a valid model or a valid alias).
|
* Checks if the model name is valid (either a valid model or a valid alias).
|
||||||
*
|
*
|
||||||
* @param model The model name to check.
|
* @param model The model name to check.
|
||||||
|
* @param config Optional config object for dynamic model configuration.
|
||||||
* @returns True if the model is valid.
|
* @returns True if the model is valid.
|
||||||
*/
|
*/
|
||||||
export function isValidModelOrAlias(model: string): boolean {
|
export function isValidModelOrAlias(
|
||||||
|
model: string,
|
||||||
|
config?: ModelResolutionContext,
|
||||||
|
): boolean {
|
||||||
|
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
|
||||||
|
if (config.modelConfigService.getModelDefinition(model)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if it's a valid alias
|
// Check if it's a valid alias
|
||||||
if (VALID_ALIASES.has(model)) {
|
if (VALID_ALIASES.has(model)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ export class GeminiChat {
|
|||||||
: getRetryErrorType(error);
|
: getRetryErrorType(error);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(isContentError && isGemini2Model(model)) ||
|
(isContentError && isGemini2Model(model, this.context.config)) ||
|
||||||
(isRetryable && !signal.aborted)
|
(isRetryable && !signal.aborted)
|
||||||
) {
|
) {
|
||||||
// The issue requests exactly 3 retries (4 attempts) for API errors during stream iteration.
|
// The issue requests exactly 3 retries (4 attempts) for API errors during stream iteration.
|
||||||
@@ -551,7 +551,10 @@ export class GeminiChat {
|
|||||||
abortSignal,
|
abortSignal,
|
||||||
};
|
};
|
||||||
|
|
||||||
let contentsToUse: Content[] = supportsModernFeatures(modelToUse)
|
let contentsToUse: Content[] = supportsModernFeatures(
|
||||||
|
modelToUse,
|
||||||
|
this.context.config,
|
||||||
|
)
|
||||||
? [...contentsForPreviewModel]
|
? [...contentsForPreviewModel]
|
||||||
: [...requestContents];
|
: [...requestContents];
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export class PromptProvider {
|
|||||||
context.config.getActiveModel(),
|
context.config.getActiveModel(),
|
||||||
context.config.getGemini31LaunchedSync?.() ?? false,
|
context.config.getGemini31LaunchedSync?.() ?? false,
|
||||||
);
|
);
|
||||||
const isModernModel = supportsModernFeatures(desiredModel);
|
const isModernModel = supportsModernFeatures(desiredModel, context.config);
|
||||||
const activeSnippets = isModernModel ? snippets : legacySnippets;
|
const activeSnippets = isModernModel ? snippets : legacySnippets;
|
||||||
const contextFilenames = getAllGeminiMdFilenames();
|
const contextFilenames = getAllGeminiMdFilenames();
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ export class PromptProvider {
|
|||||||
context.config.getActiveModel(),
|
context.config.getActiveModel(),
|
||||||
context.config.getGemini31LaunchedSync?.() ?? false,
|
context.config.getGemini31LaunchedSync?.() ?? false,
|
||||||
);
|
);
|
||||||
const isModernModel = supportsModernFeatures(desiredModel);
|
const isModernModel = supportsModernFeatures(desiredModel, context.config);
|
||||||
const activeSnippets = isModernModel ? snippets : legacySnippets;
|
const activeSnippets = isModernModel ? snippets : legacySnippets;
|
||||||
return activeSnippets.getCompressionPrompt(
|
return activeSnippets.getCompressionPrompt(
|
||||||
context.config.getApprovedPlanPath(),
|
context.config.getApprovedPlanPath(),
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class ApprovalModeStrategy implements RoutingStrategy {
|
|||||||
const model = context.requestedModel ?? config.getModel();
|
const model = context.requestedModel ?? config.getModel();
|
||||||
|
|
||||||
// This strategy only applies to "auto" models.
|
// This strategy only applies to "auto" models.
|
||||||
if (!isAutoModel(model)) {
|
if (!isAutoModel(model, config)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export class ClassifierStrategy implements RoutingStrategy {
|
|||||||
const model = context.requestedModel ?? config.getModel();
|
const model = context.requestedModel ?? config.getModel();
|
||||||
if (
|
if (
|
||||||
(await config.getNumericalRoutingEnabled()) &&
|
(await config.getNumericalRoutingEnabled()) &&
|
||||||
isGemini3Model(model)
|
isGemini3Model(model, config)
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export class NumericalClassifierStrategy implements RoutingStrategy {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isGemini3Model(model)) {
|
if (!isGemini3Model(model, config)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class OverrideStrategy implements RoutingStrategy {
|
|||||||
const overrideModel = context.requestedModel ?? config.getModel();
|
const overrideModel = context.requestedModel ?? config.getModel();
|
||||||
|
|
||||||
// If the model is 'auto' we should pass to the next strategy.
|
// If the model is 'auto' we should pass to the next strategy.
|
||||||
if (isAutoModel(overrideModel)) {
|
if (isAutoModel(overrideModel, config)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,34 @@ export interface ModelConfigAlias {
|
|||||||
modelConfig: ModelConfig;
|
modelConfig: ModelConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A model definition is a mapping from a model name to a list of features
|
||||||
|
// that the model supports. Model names can be either direct model IDs
|
||||||
|
// (gemini-2.5-pro) or aliases (auto).
|
||||||
|
export interface ModelDefinition {
|
||||||
|
displayName?: string;
|
||||||
|
tier?: string; // 'pro' | 'flash' | 'flash-lite' | 'custom' | 'auto'
|
||||||
|
family?: string; // The gemini family, e.g. 'gemini-3' | 'gemini-2'
|
||||||
|
isPreview?: boolean;
|
||||||
|
// Specifies which view the model should appear in. If unset, the model will
|
||||||
|
// not appear in the dialog.
|
||||||
|
dialogLocation?: 'main' | 'manual';
|
||||||
|
/** A short description of the model for the dialog. */
|
||||||
|
dialogDescription?: string;
|
||||||
|
features?: {
|
||||||
|
// Whether the model supports thinking.
|
||||||
|
thinking?: boolean;
|
||||||
|
// Whether the model supports mutlimodal function responses. This is
|
||||||
|
// supported in Gemini 3.
|
||||||
|
multimodalToolUse?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface ModelConfigServiceConfig {
|
export interface ModelConfigServiceConfig {
|
||||||
aliases?: Record<string, ModelConfigAlias>;
|
aliases?: Record<string, ModelConfigAlias>;
|
||||||
customAliases?: Record<string, ModelConfigAlias>;
|
customAliases?: Record<string, ModelConfigAlias>;
|
||||||
overrides?: ModelConfigOverride[];
|
overrides?: ModelConfigOverride[];
|
||||||
customOverrides?: ModelConfigOverride[];
|
customOverrides?: ModelConfigOverride[];
|
||||||
|
modelDefinitions?: Record<string, ModelDefinition>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_ALIAS_CHAIN_DEPTH = 100;
|
const MAX_ALIAS_CHAIN_DEPTH = 100;
|
||||||
@@ -76,6 +99,28 @@ export class ModelConfigService {
|
|||||||
// TODO(12597): Process config to build a typed alias hierarchy.
|
// TODO(12597): Process config to build a typed alias hierarchy.
|
||||||
constructor(private readonly config: ModelConfigServiceConfig) {}
|
constructor(private readonly config: ModelConfigServiceConfig) {}
|
||||||
|
|
||||||
|
getModelDefinition(modelId: string): ModelDefinition | undefined {
|
||||||
|
const definition = this.config.modelDefinitions?.[modelId];
|
||||||
|
if (definition) {
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For unknown models, return an implicit custom definition to match legacy behavior.
|
||||||
|
if (!modelId.startsWith('gemini-')) {
|
||||||
|
return {
|
||||||
|
tier: 'custom',
|
||||||
|
family: 'custom',
|
||||||
|
features: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getModelDefinitions(): Record<string, ModelDefinition> {
|
||||||
|
return this.config.modelDefinitions ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
registerRuntimeModelConfig(aliasName: string, alias: ModelConfigAlias): void {
|
registerRuntimeModelConfig(aliasName: string, alias: ModelConfigAlias): void {
|
||||||
this.runtimeAliases[aliasName] = alias;
|
this.runtimeAliases[aliasName] = alias;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user