From eb42ff6086458b1634083b9f3f8912693380b119 Mon Sep 17 00:00:00 2001 From: Sri Pasumarthi Date: Fri, 5 Jun 2026 11:30:34 -0700 Subject: [PATCH] feat: promote Gemini 3.1 Flash Lite to GA and support Gemini 3.5 Flash --- docs/reference/configuration.md | 189 ++++--- evals/interactive-hang.eval.ts | 5 + evals/llm-judge.ts | 28 +- evals/test-helper.ts | 4 +- package-lock.json | 35 +- .../cli/src/acp/acpSessionManager.test.ts | 17 +- packages/cli/src/acp/acpUtils.ts | 59 +- .../src/ui/components/ModelDialog.test.tsx | 45 +- .../cli/src/ui/components/ModelDialog.tsx | 119 ++-- .../ui/components/ModelStatsDisplay.test.tsx | 43 ++ .../src/ui/components/ModelStatsDisplay.tsx | 2 +- .../SessionBrowserSearchNav.test.tsx.snap | 6 + .../SessionBrowserStates.test.tsx.snap | 6 + .../src/ui/components/StatsDisplay.test.tsx | 27 + .../cli/src/ui/components/StatsDisplay.tsx | 4 +- ...ternateBufferQuittingDisplay.test.tsx.snap | 50 ++ ...Banner-handles-newlines-in-text-2.snap.svg | 20 + .../__snapshots__/Banner.test.tsx.snap | 7 + .../__snapshots__/Checklist.test.tsx.snap | 11 + .../ContextSummaryDisplay.test.tsx.snap | 5 + .../LoopDetectionConfirmation.test.tsx.snap | 13 + .../ModelStatsDisplay.test.tsx.snap | 23 + .../__snapshots__/ShortcutsHelp.test.tsx.snap | 16 + .../__snapshots__/StatsDisplay.test.tsx.snap | 27 + .../__snapshots__/ThemeDialog.test.tsx.snap | 39 ++ .../__snapshots__/Tips.test.tsx.snap | 9 + .../ToolStatsDisplay.test.tsx.snap | 21 + .../__snapshots__/ErrorMessage.test.tsx.snap | 6 + .../__snapshots__/GeminiMessage.test.tsx.snap | 16 + .../ToolGroupMessage.compact.test.tsx.snap | 6 + .../ToolMessageRawMarkdown.test.tsx.snap | 8 + .../__snapshots__/UserMessage.test.tsx.snap | 8 + .../WarningMessage.test.tsx.snap | 7 + .../__snapshots__/ChatList.test.tsx.snap | 9 + .../DefaultAppLayout.test.tsx.snap | 14 + .../availability/fallbackIntegration.test.ts | 1 + .../src/availability/policyCatalog.test.ts | 2 - .../core/src/availability/policyCatalog.ts | 5 +- .../src/availability/policyHelpers.test.ts | 12 +- .../core/src/availability/policyHelpers.ts | 35 +- .../core/src/code_assist/codeAssist.test.ts | 47 ++ packages/core/src/code_assist/codeAssist.ts | 13 +- .../src/code_assist/experiments/flagNames.ts | 2 +- packages/core/src/config/config.test.ts | 71 ++- packages/core/src/config/config.ts | 100 ++-- .../core/src/config/defaultModelConfigs.ts | 139 +++-- packages/core/src/config/models.test.ts | 531 +++++++++++++++--- packages/core/src/config/models.ts | 191 +++++-- .../src/context/chatCompressionService.ts | 12 +- packages/core/src/core/client.ts | 2 +- .../core/src/core/contentGenerator.test.ts | 193 ++++++- packages/core/src/core/contentGenerator.ts | 19 +- packages/core/src/core/geminiChat.test.ts | 1 + packages/core/src/core/geminiChat.ts | 9 +- .../src/core/geminiChat_network_retry.test.ts | 1 + .../core/modelMappingContentGenerator.test.ts | 135 +++++ .../src/core/modelMappingContentGenerator.ts | 88 +++ packages/core/src/core/prompts.test.ts | 13 +- packages/core/src/fallback/handler.test.ts | 1 + packages/core/src/prompts/promptProvider.ts | 4 +- .../strategies/approvalModeStrategy.test.ts | 19 +- .../strategies/approvalModeStrategy.ts | 22 +- .../strategies/classifierStrategy.test.ts | 23 +- .../routing/strategies/classifierStrategy.ts | 13 +- .../src/routing/strategies/defaultStrategy.ts | 2 +- .../routing/strategies/fallbackStrategy.ts | 2 +- .../gemmaClassifierStrategy.test.ts | 22 +- .../strategies/gemmaClassifierStrategy.ts | 20 +- .../numericalClassifierStrategy.test.ts | 24 +- .../strategies/numericalClassifierStrategy.ts | 13 +- .../routing/strategies/overrideStrategy.ts | 2 +- .../src/services/modelConfigService.test.ts | 73 +++ .../core/src/services/modelConfigService.ts | 51 +- .../resolved-aliases-retry.golden.json | 57 +- .../test-data/resolved-aliases.golden.json | 57 +- packages/core/src/tools/shell.ts | 28 +- packages/core/test-setup.ts | 4 + schemas/settings.schema.json | 400 +++++++++---- 78 files changed, 2658 insertions(+), 705 deletions(-) create mode 100644 packages/cli/src/ui/components/__snapshots__/Banner-Banner-handles-newlines-in-text-2.snap.svg create mode 100644 packages/core/src/core/modelMappingContentGenerator.test.ts create mode 100644 packages/core/src/core/modelMappingContentGenerator.ts diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index b82afe62fb..3561baf4b5 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -545,6 +545,24 @@ their corresponding top-level category object in your `settings.json` file. "model": "gemini-3-flash-preview" } }, + "gemini-3.1-pro-preview": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-pro-preview" + } + }, + "gemini-3.1-pro-preview-customtools": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-pro-preview-customtools" + } + }, + "gemini-3.1-flash-lite-preview": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-flash-lite-preview" + } + }, "gemini-2.5-pro": { "extends": "chat-base-2.5", "modelConfig": { @@ -564,11 +582,17 @@ their corresponding top-level category object in your `settings.json` file. } }, "gemini-3.1-flash-lite": { - "extends": "chat-base-2.5", + "extends": "chat-base-3", "modelConfig": { "model": "gemini-3.1-flash-lite" } }, + "gemini-3.5-flash": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.5-flash" + } + }, "gemma-4-31b-it": { "extends": "chat-base-3", "modelConfig": { @@ -593,6 +617,12 @@ their corresponding top-level category object in your `settings.json` file. "model": "gemini-3-flash-preview" } }, + "gemini-3.5-flash-base": { + "extends": "base", + "modelConfig": { + "model": "gemini-3.5-flash" + } + }, "classifier": { "extends": "base", "modelConfig": { @@ -795,16 +825,6 @@ their corresponding top-level category object in your `settings.json` file. "multimodalToolUse": true } }, - "gemini-3.1-flash-lite-preview": { - "tier": "flash-lite", - "family": "gemini-3", - "isPreview": true, - "isVisible": true, - "features": { - "thinking": false, - "multimodalToolUse": true - } - }, "gemini-3.1-pro-preview": { "tier": "pro", "family": "gemini-3", @@ -845,6 +865,16 @@ their corresponding top-level category object in your `settings.json` file. "multimodalToolUse": true } }, + "gemini-3.5-flash": { + "tier": "flash", + "family": "gemini-3", + "isPreview": false, + "isVisible": true, + "features": { + "thinking": false, + "multimodalToolUse": true + } + }, "gemini-2.5-pro": { "tier": "pro", "family": "gemini-2.5", @@ -898,9 +928,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 @@ -934,26 +965,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 } } ``` @@ -1006,37 +1027,50 @@ their corresponding top-level category object in your `settings.json` file. "contexts": [ { "condition": { - "hasAccessToPreview": false + "hasAccessToPreview": false, + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, + { + "condition": { + "hasAccessToPreview": false, + "useGemini3_5Flash": false }, "target": "gemini-2.5-flash" } ] }, - "gemini-3-pro-preview": { - "default": "gemini-3-pro-preview", + "gemini-3.5-flash": { + "default": "gemini-3.5-flash", "contexts": [ { "condition": { + "useGemini3_5Flash": false, "hasAccessToPreview": false }, - "target": "gemini-2.5-pro" + "target": "gemini-2.5-flash" }, { "condition": { - "useGemini3_1": true, - "useCustomTools": true + "useGemini3_5Flash": false }, - "target": "gemini-3.1-pro-preview-customtools" - }, - { - "condition": { - "useGemini3_1": true - }, - "target": "gemini-3.1-pro-preview" + "target": "gemini-3-flash-preview" } ] }, - "auto-gemini-3": { + "gemini-2.5-flash": { + "default": "gemini-2.5-flash", + "contexts": [ + { + "condition": { + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + } + ] + }, + "gemini-3-pro-preview": { "default": "gemini-3-pro-preview", "contexts": [ { @@ -1108,34 +1142,18 @@ 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": [ - { - "condition": { - "useGemini3_1FlashLite": false - }, - "target": "gemini-2.5-flash-lite" - } - ] - }, "gemini-3.1-flash-lite": { - "default": "gemini-3.1-flash-lite", - "contexts": [ - { - "condition": { - "useGemini3_1FlashLite": false - }, - "target": "gemini-2.5-flash-lite" - } - ] + "default": "gemini-3.1-flash-lite" }, "flash": { "default": "gemini-3-flash-preview", "contexts": [ + { + "condition": { + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, { "condition": { "hasAccessToPreview": false @@ -1145,15 +1163,34 @@ their corresponding top-level category object in your `settings.json` file. ] }, "flash-lite": { - "default": "gemini-3.1-flash-lite", + "default": "gemini-3.1-flash-lite" + }, + "auto-gemini-3": { + "default": "gemini-3-pro-preview", "contexts": [ { "condition": { - "useGemini3_1FlashLite": false + "hasAccessToPreview": false }, - "target": "gemini-2.5-flash-lite" + "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" } } ``` @@ -1172,15 +1209,21 @@ their corresponding top-level category object in your `settings.json` file. "contexts": [ { "condition": { - "requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"] + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, + { + "condition": { + "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" } ] }, @@ -1189,7 +1232,13 @@ 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": { + "requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"] }, "target": "gemini-2.5-pro" }, diff --git a/evals/interactive-hang.eval.ts b/evals/interactive-hang.eval.ts index 72a5067fcc..3f497f443b 100644 --- a/evals/interactive-hang.eval.ts +++ b/evals/interactive-hang.eval.ts @@ -54,6 +54,11 @@ describe('interactive_commands', () => { suiteName: 'default', suiteType: 'behavioral', name: 'should use non-interactive flags when scaffolding a new app', + params: { + settings: { + plan: false, + }, + }, prompt: 'Create a new react application named my-app using vite.', assert: async (rig, result) => { const logs = rig.readToolLogs(); diff --git a/evals/llm-judge.ts b/evals/llm-judge.ts index a7490e2626..6203adf556 100644 --- a/evals/llm-judge.ts +++ b/evals/llm-judge.ts @@ -76,10 +76,30 @@ export class LLMJudge { for (const res of rawResults) { // Remove any punctuation the model might have appended - const cleanRes = res.replace(/[^A-Z]/g, ''); - if (cleanRes.startsWith('YES')) yes++; - else if (cleanRes.startsWith('NO')) no++; - else other++; + const cleanRes = res.toUpperCase().replace(/[^A-Z ]/g, ''); + if ( + cleanRes.includes('THE ANSWER IS YES') || + cleanRes.includes('ANSWER IS YES') || + cleanRes.endsWith('YES') + ) { + yes++; + } else if ( + cleanRes.includes('THE ANSWER IS NO') || + cleanRes.includes('ANSWER IS NO') || + cleanRes.endsWith('NO') + ) { + no++; + } else if (cleanRes.trim() === 'YES') { + yes++; + } else if (cleanRes.trim() === 'NO') { + no++; + } else { + // Fallback: look for YES or NO as standalone words or at the end + const words = cleanRes.split(/\s+/); + if (words.includes('YES')) yes++; + else if (words.includes('NO')) no++; + else other++; + } } // Pass if YES > NO and YES > OTHER (plurality) diff --git a/evals/test-helper.ts b/evals/test-helper.ts index 79263b9344..68001d36fc 100644 --- a/evals/test-helper.ts +++ b/evals/test-helper.ts @@ -16,7 +16,6 @@ import { Storage, getProjectHash, SESSION_FILE_PREFIX, - PREVIEW_GEMINI_FLASH_MODEL, getErrorMessage, } from '@google/gemini-cli-core'; @@ -26,8 +25,7 @@ export * from '@google/gemini-cli-test-utils'; * The default model used for all evaluations. * Can be overridden by setting the GEMINI_MODEL environment variable. */ -export const EVAL_MODEL = - process.env['GEMINI_MODEL'] || PREVIEW_GEMINI_FLASH_MODEL; +export const EVAL_MODEL = process.env['GEMINI_MODEL'] || 'gemini-3.5-flash'; // Indicates the consistency expectation for this test. // - ALWAYS_PASSES - Means that the test is expected to pass 100% of the time. These diff --git a/package-lock.json b/package-lock.json index 72b4b23e58..d155ad1e0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -449,7 +449,8 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", - "license": "(Apache-2.0 AND BSD-3-Clause)" + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true }, "node_modules/@bundled-es-modules/cookie": { "version": "2.0.1", @@ -1535,6 +1536,7 @@ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" @@ -2212,6 +2214,7 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -2392,6 +2395,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2441,6 +2445,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -2815,6 +2820,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz", "integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -2848,6 +2854,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz", "integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0" @@ -2902,6 +2909,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz", "integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0", @@ -4139,6 +4147,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4412,6 +4421,7 @@ "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/types": "8.58.2", @@ -5187,6 +5197,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7304,7 +7315,8 @@ "version": "0.0.1581282", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz", "integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/dezalgo": { "version": "1.0.4", @@ -7889,6 +7901,7 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -8499,6 +8512,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -9765,6 +9779,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -10024,6 +10039,7 @@ "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.6.9.tgz", "integrity": "sha512-RL9sSiLQZECnjbmBwjIHOp8yVGdWF7C/uifg7ISv/e+F3nLNsfl7FdUFQs8iZARFMJAYxMFpxW6OW+HSt9drwQ==", "license": "MIT", + "peer": true, "dependencies": { "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.3", @@ -13799,6 +13815,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13809,6 +13826,7 @@ "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -15961,6 +15979,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16183,7 +16202,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.20.3", @@ -16191,6 +16211,7 @@ "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -16356,6 +16377,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16423,6 +16445,7 @@ "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", @@ -16842,6 +16865,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -17412,6 +17436,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17424,6 +17449,7 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -18062,6 +18088,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -18498,6 +18525,7 @@ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" @@ -18616,6 +18644,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/packages/cli/src/acp/acpSessionManager.test.ts b/packages/cli/src/acp/acpSessionManager.test.ts index c37cde7820..1a1d62d947 100644 --- a/packages/cli/src/acp/acpSessionManager.test.ts +++ b/packages/cli/src/acp/acpSessionManager.test.ts @@ -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,20 +209,19 @@ describe('AcpSessionManager', () => { expect(response.models?.availableModels).toEqual( expect.arrayContaining([ expect.objectContaining({ - modelId: 'auto-gemini-3', + modelId: GEMINI_MODEL_ALIAS_AUTO, name: expect.stringContaining('Auto'), }), ]), ); }); - it('should include gemini-3.1-flash-lite when useGemini31FlashLite is true', async () => { + it('should NOT include retired preview models (none) in available models', async () => { mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({ apiKey: 'test-key', }); mockConfig.getHasAccessToPreviewModel = vi.fn().mockReturnValue(true); mockConfig.getGemini31LaunchedSync = vi.fn().mockReturnValue(true); - mockConfig.getGemini31FlashLiteLaunchedSync = vi.fn().mockReturnValue(true); const response = await manager.newSession( { @@ -231,14 +231,9 @@ describe('AcpSessionManager', () => { {}, ); - expect(response.models?.availableModels).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - modelId: 'gemini-3.1-flash-lite', - name: 'gemini-3.1-flash-lite', - }), - ]), - ); + const modelIds = + response.models?.availableModels?.map((m) => m.modelId) ?? []; + expect(modelIds).not.toContain('none'); }); it('should return modes with plan mode when plan is enabled', async () => { diff --git a/packages/cli/src/acp/acpUtils.ts b/packages/cli/src/acp/acpUtils.ts index 8f898e3ffe..edd6156db7 100644 --- a/packages/cli/src/acp/acpUtils.ts +++ b/packages/cli/src/acp/acpUtils.ts @@ -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, @@ -19,10 +18,11 @@ import { PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, PREVIEW_GEMINI_FLASH_MODEL, - GEMINI_3_1_FLASH_LITE_MODEL, + PREVIEW_GEMINI_FLASH_LITE_MODEL, getDisplayString, AuthType, ToolConfirmationOutcome, + getAutoModelDescription, } from '@google/gemini-cli-core'; import type * as acp from '@agentclientprotocol/sdk'; import { z } from 'zod'; @@ -262,11 +262,10 @@ 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 = - config.getGemini31FlashLiteLaunchedSync?.() ?? false; + const useGemini3_5Flash = config.hasGemini35FlashGAAccess?.() ?? false; const selectedAuthType = settings.merged.security.auth.selectedType; const useCustomToolModel = useGemini31 && selectedAuthType === AuthType.USE_GEMINI; @@ -278,7 +277,7 @@ export function buildAvailableModels( ) { const options = config.getModelConfigService().getAvailableModelOptions({ useGemini3_1: useGemini31, - useGemini3_1FlashLite: useGemini31FlashLite, + useGemini3_5Flash, useCustomTools: useCustomToolModel, hasAccessToPreview: shouldShowPreviewModels, }); @@ -290,37 +289,35 @@ export function buildAvailableModels( } // --- LEGACY PATH --- + const defaultFlashModel = + config.getDefaultGeminiFlashModel?.() ?? DEFAULT_GEMINI_FLASH_MODEL; + const previewFlashModel = + config.getPreviewGeminiFlashModel?.() ?? PREVIEW_GEMINI_FLASH_MODEL; + 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, config), + description: getAutoModelDescription( + shouldShowPreviewModels, + useGemini31, + useGemini3_5Flash, + ), }, ]; - 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, - title: getDisplayString(DEFAULT_GEMINI_MODEL), + title: getDisplayString(DEFAULT_GEMINI_MODEL, config), }, { - value: DEFAULT_GEMINI_FLASH_MODEL, - title: getDisplayString(DEFAULT_GEMINI_FLASH_MODEL), + value: defaultFlashModel, + title: getDisplayString(defaultFlashModel, config), }, { value: DEFAULT_GEMINI_FLASH_LITE_MODEL, - title: getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL), + title: getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL, config), }, ]; @@ -336,18 +333,18 @@ export function buildAvailableModels( const previewOptions = [ { value: previewProValue, - title: getDisplayString(previewProModel), + title: getDisplayString(previewProModel, config), }, { - value: PREVIEW_GEMINI_FLASH_MODEL, - title: getDisplayString(PREVIEW_GEMINI_FLASH_MODEL), + value: previewFlashModel, + title: getDisplayString(previewFlashModel, config), }, ]; - if (useGemini31FlashLite) { + if (PREVIEW_GEMINI_FLASH_LITE_MODEL !== 'none') { previewOptions.push({ - value: GEMINI_3_1_FLASH_LITE_MODEL, - title: getDisplayString(GEMINI_3_1_FLASH_LITE_MODEL), + value: PREVIEW_GEMINI_FLASH_LITE_MODEL, + title: getDisplayString(PREVIEW_GEMINI_FLASH_LITE_MODEL, config), }); } diff --git a/packages/cli/src/ui/components/ModelDialog.test.tsx b/packages/cli/src/ui/components/ModelDialog.test.tsx index 8696cdc8ba..f694d7174b 100644 --- a/packages/cli/src/ui/components/ModelDialog.test.tsx +++ b/packages/cli/src/ui/components/ModelDialog.test.tsx @@ -12,14 +12,14 @@ 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, PREVIEW_GEMINI_3_1_MODEL, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, PREVIEW_GEMINI_FLASH_MODEL, - GEMINI_3_1_FLASH_LITE_MODEL, + PREVIEW_GEMINI_FLASH_LITE_MODEL, AuthType, } from '@google/gemini-cli-core'; import type { Config, ModelSlashCommandEvent } from '@google/gemini-cli-core'; @@ -34,6 +34,11 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { await importOriginal(); return { ...actual, + getAutoModelDescription: ( + hasAccessToPreview: boolean, + useGemini3_1?: boolean, + ) => + `Auto Model Description (preview: ${hasAccessToPreview}, 3.1: ${useGemini3_1})`, getDisplayString: (val: string) => mockGetDisplayString(val), logModelSlashCommand: (config: Config, event: ModelSlashCommandEvent) => mockLogModelSlashCommand(config, event), @@ -42,8 +47,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { mockModelSlashCommandEvent(model); } }, - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL: 'gemini-3.1-flash-lite-preview', - GEMINI_3_1_FLASH_LITE_MODEL: 'gemini-3.1-flash-lite', + PREVIEW_GEMINI_FLASH_LITE_MODEL: 'none', }; }); @@ -63,7 +67,6 @@ describe('', () => { getHasAccessToPreviewModel: () => boolean; getIdeMode: () => boolean; getGemini31LaunchedSync: () => boolean; - getGemini31FlashLiteLaunchedSync: () => boolean; getProModelNoAccess: () => Promise; getProModelNoAccessSync: () => boolean; getExperimentalGemma: () => boolean; @@ -84,7 +87,6 @@ describe('', () => { getHasAccessToPreviewModel: mockGetHasAccessToPreviewModel, getIdeMode: () => false, getGemini31LaunchedSync: mockGetGemini31LaunchedSync, - getGemini31FlashLiteLaunchedSync: mockGetGemini31FlashLiteLaunchedSync, getProModelNoAccess: mockGetProModelNoAccess, getProModelNoAccessSync: mockGetProModelNoAccessSync, getExperimentalGemma: () => false, @@ -94,17 +96,15 @@ describe('', () => { beforeEach(() => { vi.resetAllMocks(); - mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO); + mockGetModel.mockReturnValue(GEMINI_MODEL_ALIAS_AUTO); mockGetHasAccessToPreviewModel.mockReturnValue(false); mockGetGemini31LaunchedSync.mockReturnValue(false); - mockGetGemini31FlashLiteLaunchedSync.mockReturnValue(false); mockGetProModelNoAccess.mockResolvedValue(false); mockGetProModelNoAccessSync.mockReturnValue(false); // 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; }); }); @@ -154,15 +154,13 @@ describe('', () => { expect(output).not.toContain(DEFAULT_GEMINI_MODEL); expect(output).not.toContain(PREVIEW_GEMINI_MODEL); - // Verify order: Flash Preview -> Flash Lite Preview -> Flash -> Flash Lite + // Verify order: Flash Preview -> Flash Lite (Preview/Default) -> Flash const flashPreviewIdx = output.indexOf(PREVIEW_GEMINI_FLASH_MODEL); - const flashLitePreviewIdx = output.indexOf(GEMINI_3_1_FLASH_LITE_MODEL); - const flashIdx = output.indexOf(DEFAULT_GEMINI_FLASH_MODEL); const flashLiteIdx = output.indexOf(DEFAULT_GEMINI_FLASH_LITE_MODEL); + const flashIdx = output.indexOf(DEFAULT_GEMINI_FLASH_MODEL); - expect(flashPreviewIdx).toBeLessThan(flashLitePreviewIdx); - expect(flashLitePreviewIdx).toBeLessThan(flashIdx); - expect(flashIdx).toBeLessThan(flashLiteIdx); + expect(flashPreviewIdx).toBeLessThan(flashLiteIdx); + expect(flashLiteIdx).toBeLessThan(flashIdx); expect(output).not.toContain('Auto'); unmount(); @@ -233,7 +231,7 @@ describe('', () => { await waitFor(() => { expect(mockSetModel).toHaveBeenCalledWith( - DEFAULT_GEMINI_MODEL_AUTO, + GEMINI_MODEL_ALIAS_AUTO, true, // Session only by default ); expect(mockOnClose).toHaveBeenCalled(); @@ -291,7 +289,7 @@ describe('', () => { await waitFor(() => { expect(mockSetModel).toHaveBeenCalledWith( - DEFAULT_GEMINI_MODEL_AUTO, + GEMINI_MODEL_ALIAS_AUTO, false, // Persist enabled ); expect(mockOnClose).toHaveBeenCalled(); @@ -354,7 +352,7 @@ describe('', () => { 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(); @@ -368,9 +366,9 @@ describe('', () => { 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(); }); @@ -448,7 +446,7 @@ describe('', () => { unmount(); }); - it('shows Flash Lite Preview model regardless of tier when flag is enabled', async () => { + it('does not show Flash Lite Preview model when it is retired (none) even if flag is enabled', async () => { mockGetProModelNoAccessSync.mockReturnValue(false); mockGetProModelNoAccess.mockResolvedValue(false); mockGetHasAccessToPreviewModel.mockReturnValue(true); @@ -467,7 +465,8 @@ describe('', () => { await waitUntilReady(); const output = lastFrame(); - expect(output).toContain(GEMINI_3_1_FLASH_LITE_MODEL); + expect(output).not.toContain(PREVIEW_GEMINI_FLASH_LITE_MODEL); + expect(output).toContain(DEFAULT_GEMINI_FLASH_LITE_MODEL); unmount(); }); }); diff --git a/packages/cli/src/ui/components/ModelDialog.tsx b/packages/cli/src/ui/components/ModelDialog.tsx index ccf1b01c88..26ef34c227 100644 --- a/packages/cli/src/ui/components/ModelDialog.tsx +++ b/packages/cli/src/ui/components/ModelDialog.tsx @@ -13,13 +13,11 @@ import { PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_3_1_MODEL, PREVIEW_GEMINI_FLASH_MODEL, - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, - PREVIEW_GEMINI_MODEL_AUTO, - GEMINI_3_1_FLASH_LITE_MODEL, + PREVIEW_GEMINI_FLASH_LITE_MODEL, 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, @@ -28,6 +26,7 @@ import { AuthType, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, isProModel, + getAutoModelDescription, } from '@google/gemini-cli-core'; import { useKeypress } from '../hooks/useKeypress.js'; import { theme } from '../semantic-colors.js'; @@ -64,12 +63,11 @@ 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 shouldShowPreviewModels = config?.getHasAccessToPreviewModel() ?? false; const useGemini31 = config?.getGemini31LaunchedSync?.() ?? false; - const useGemini31FlashLite = - config?.getGemini31FlashLiteLaunchedSync?.() ?? false; + const useGemini3_5Flash = config?.hasGemini35FlashGAAccess?.() ?? false; const selectedAuthType = settings.merged.security.auth.selectedType; const useCustomToolModel = useGemini31 && selectedAuthType === AuthType.USE_GEMINI; @@ -88,17 +86,21 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { : ''; } + const defaultFlashModel = + config?.getDefaultGeminiFlashModel?.() ?? DEFAULT_GEMINI_FLASH_MODEL; + const previewFlashModel = + config?.getPreviewGeminiFlashModel?.() ?? PREVIEW_GEMINI_FLASH_MODEL; + const manualModels = [ DEFAULT_GEMINI_MODEL, - DEFAULT_GEMINI_FLASH_MODEL, + defaultFlashModel, DEFAULT_GEMINI_FLASH_LITE_MODEL, PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_3_1_MODEL, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, - GEMINI_3_1_FLASH_LITE_MODEL, - PREVIEW_GEMINI_FLASH_MODEL, - ]; + PREVIEW_GEMINI_FLASH_LITE_MODEL, + previewFlashModel, + ].filter((m) => m !== 'none'); if (manualModels.includes(preferredModel)) { return preferredModel; } @@ -123,7 +125,6 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { }, { isActive: true }, ); - const mainOptions = useMemo(() => { // --- DYNAMIC PATH --- if ( @@ -134,7 +135,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { .getModelConfigService() .getAvailableModelOptions({ useGemini3_1: useGemini31, - useGemini3_1FlashLite: useGemini31FlashLite, + useGemini3_5Flash, useCustomTools: useCustomToolModel, hasAccessToPreview: shouldShowPreviewModels, hasAccessToProModel, @@ -163,11 +164,14 @@ 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( + shouldShowPreviewModels, + useGemini31, + useGemini3_5Flash, + ), + key: GEMINI_MODEL_ALIAS_AUTO, }, { value: 'Manual', @@ -179,23 +183,13 @@ 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, shouldShowPreviewModels, manualModelSelected, useGemini31, - useGemini31FlashLite, + useGemini3_5Flash, useCustomToolModel, hasAccessToProModel, ]); @@ -210,7 +204,7 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { .getModelConfigService() .getAvailableModelOptions({ useGemini3_1: useGemini31, - useGemini3_1FlashLite: useGemini31FlashLite, + useGemini3_5Flash, useCustomTools: useCustomToolModel, hasAccessToPreview: shouldShowPreviewModels, hasAccessToProModel, @@ -227,22 +221,29 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { // --- LEGACY PATH --- const showGemmaModels = config?.getExperimentalGemma() ?? false; + const defaultFlashModel = + config?.getDefaultGeminiFlashModel?.() ?? DEFAULT_GEMINI_FLASH_MODEL; + const previewFlashModel = + config?.getPreviewGeminiFlashModel?.() ?? PREVIEW_GEMINI_FLASH_MODEL; const options = [ { value: DEFAULT_GEMINI_MODEL, - title: getDisplayString(DEFAULT_GEMINI_MODEL), + title: getDisplayString(DEFAULT_GEMINI_MODEL, config ?? undefined), key: DEFAULT_GEMINI_MODEL, }, { - value: DEFAULT_GEMINI_FLASH_MODEL, - title: getDisplayString(DEFAULT_GEMINI_FLASH_MODEL), - key: DEFAULT_GEMINI_FLASH_MODEL, + value: DEFAULT_GEMINI_FLASH_LITE_MODEL, + title: getDisplayString( + DEFAULT_GEMINI_FLASH_LITE_MODEL, + config ?? undefined, + ), + key: DEFAULT_GEMINI_FLASH_LITE_MODEL, }, { - value: DEFAULT_GEMINI_FLASH_LITE_MODEL, - title: getDisplayString(DEFAULT_GEMINI_FLASH_LITE_MODEL), - key: DEFAULT_GEMINI_FLASH_LITE_MODEL, + value: defaultFlashModel, + title: getDisplayString(defaultFlashModel, config ?? undefined), + key: defaultFlashModel, }, ]; @@ -250,12 +251,15 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { options.push( { value: GEMMA_4_31B_IT_MODEL, - title: getDisplayString(GEMMA_4_31B_IT_MODEL), + title: getDisplayString(GEMMA_4_31B_IT_MODEL, config ?? undefined), key: GEMMA_4_31B_IT_MODEL, }, { value: GEMMA_4_26B_A4B_IT_MODEL, - title: getDisplayString(GEMMA_4_26B_A4B_IT_MODEL), + title: getDisplayString( + GEMMA_4_26B_A4B_IT_MODEL, + config ?? undefined, + ), key: GEMMA_4_26B_A4B_IT_MODEL, }, ); @@ -273,21 +277,24 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { const previewOptions = [ { value: previewProValue, - title: getDisplayString(previewProModel), + title: getDisplayString(previewProModel, config ?? undefined), key: previewProModel, }, { - value: PREVIEW_GEMINI_FLASH_MODEL, - title: getDisplayString(PREVIEW_GEMINI_FLASH_MODEL), - key: PREVIEW_GEMINI_FLASH_MODEL, + value: previewFlashModel, + title: getDisplayString(previewFlashModel, config ?? undefined), + key: previewFlashModel, }, ]; - if (useGemini31FlashLite) { + if (PREVIEW_GEMINI_FLASH_LITE_MODEL !== 'none') { previewOptions.push({ - value: GEMINI_3_1_FLASH_LITE_MODEL, - title: getDisplayString(GEMINI_3_1_FLASH_LITE_MODEL), - key: GEMINI_3_1_FLASH_LITE_MODEL, + value: PREVIEW_GEMINI_FLASH_LITE_MODEL, + title: getDisplayString( + PREVIEW_GEMINI_FLASH_LITE_MODEL, + config ?? undefined, + ), + key: PREVIEW_GEMINI_FLASH_LITE_MODEL, }); } @@ -303,13 +310,23 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element { }, [ shouldShowPreviewModels, useGemini31, - useGemini31FlashLite, + useGemini3_5Flash, useCustomToolModel, hasAccessToProModel, config, ]); - const options = view === 'main' ? mainOptions : manualOptions; + const options = useMemo(() => { + const rawOptions = view === 'main' ? mainOptions : manualOptions; + const seen = new Set(); + return rawOptions.filter((option) => { + if (seen.has(option.value)) { + return false; + } + seen.add(option.value); + return true; + }); + }, [view, mainOptions, manualOptions]); // Calculate the initial index based on the preferred model. const initialIndex = useMemo(() => { diff --git a/packages/cli/src/ui/components/ModelStatsDisplay.test.tsx b/packages/cli/src/ui/components/ModelStatsDisplay.test.tsx index f71eb72266..dce6c604b0 100644 --- a/packages/cli/src/ui/components/ModelStatsDisplay.test.tsx +++ b/packages/cli/src/ui/components/ModelStatsDisplay.test.tsx @@ -353,6 +353,49 @@ describe('', () => { unmount(); }); + it('should resolve gemini-3-flash to gemini-3.5-flash via getDisplayString', async () => { + const { lastFrame, unmount } = await renderWithMockedStats({ + models: { + 'gemini-3-flash': { + api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 }, + tokens: { + input: 5, + prompt: 10, + candidates: 20, + total: 30, + cached: 5, + thoughts: 2, + tool: 1, + }, + roles: {}, + }, + }, + tools: { + totalCalls: 0, + totalSuccess: 0, + totalFail: 0, + totalDurationMs: 0, + totalDecisions: { + accept: 0, + reject: 0, + modify: 0, + [ToolCallDecision.AUTO_ACCEPT]: 0, + }, + byName: {}, + }, + files: { + totalLinesAdded: 0, + totalLinesRemoved: 0, + }, + }); + + const output = lastFrame(); + expect(output).toContain('gemini-3.5-flash'); + expect(output).not.toContain('gemini-3-flash'); + expect(output).toMatchSnapshot(); + unmount(); + }); + it('should handle models with long names (gemini-3-*-preview) without layout breaking', async () => { const { lastFrame, unmount } = await renderWithMockedStats( { diff --git a/packages/cli/src/ui/components/ModelStatsDisplay.tsx b/packages/cli/src/ui/components/ModelStatsDisplay.tsx index 0c6ae45e8c..f4e739e6d8 100644 --- a/packages/cli/src/ui/components/ModelStatsDisplay.tsx +++ b/packages/cli/src/ui/components/ModelStatsDisplay.tsx @@ -299,7 +299,7 @@ export const ModelStatsDisplay: React.FC = ({ }, ...modelNames.map((name) => ({ key: name, - header: name, + header: getDisplayString(name), flexGrow: 1, renderCell: (row: StatRowData) => { // Don't render anything for section headers in model columns diff --git a/packages/cli/src/ui/components/SessionBrowser/__snapshots__/SessionBrowserSearchNav.test.tsx.snap b/packages/cli/src/ui/components/SessionBrowser/__snapshots__/SessionBrowserSearchNav.test.tsx.snap index c5ed5e5454..110490fb26 100644 --- a/packages/cli/src/ui/components/SessionBrowser/__snapshots__/SessionBrowserSearchNav.test.tsx.snap +++ b/packages/cli/src/ui/components/SessionBrowser/__snapshots__/SessionBrowserSearchNav.test.tsx.snap @@ -6,6 +6,12 @@ Sort: s Reverse: r First/Last: g/G " `; +exports[`SessionBrowser Search and Navigation Components > NavigationHelp renders correctly 2`] = ` +"Navigate: ↑/↓ Resume: Enter Search: / Delete: x Quit: q +Sort: s Reverse: r First/Last: g/G +" +`; + exports[`SessionBrowser Search and Navigation Components > NoResultsDisplay renders correctly 1`] = ` " No sessions found matching 'no match'. diff --git a/packages/cli/src/ui/components/SessionBrowser/__snapshots__/SessionBrowserStates.test.tsx.snap b/packages/cli/src/ui/components/SessionBrowser/__snapshots__/SessionBrowserStates.test.tsx.snap index e5939219cb..1b062e73b6 100644 --- a/packages/cli/src/ui/components/SessionBrowser/__snapshots__/SessionBrowserStates.test.tsx.snap +++ b/packages/cli/src/ui/components/SessionBrowser/__snapshots__/SessionBrowserStates.test.tsx.snap @@ -6,6 +6,12 @@ exports[`SessionBrowser UI States > SessionBrowserEmpty renders correctly 1`] = " `; +exports[`SessionBrowser UI States > SessionBrowserEmpty renders correctly 2`] = ` +" No auto-saved conversations found. + Press q to exit +" +`; + exports[`SessionBrowser UI States > SessionBrowserError renders correctly 1`] = ` " Error: Test error message Press q to exit diff --git a/packages/cli/src/ui/components/StatsDisplay.test.tsx b/packages/cli/src/ui/components/StatsDisplay.test.tsx index cd98ed400d..b0d2577337 100644 --- a/packages/cli/src/ui/components/StatsDisplay.test.tsx +++ b/packages/cli/src/ui/components/StatsDisplay.test.tsx @@ -131,6 +131,33 @@ describe('', () => { expect(output).toMatchSnapshot(); }); + it('resolves gemini-3-flash to gemini-3.5-flash in the model usage table', async () => { + const metrics = createTestMetrics({ + models: { + 'gemini-3-flash': { + api: { totalRequests: 5, totalErrors: 0, totalLatencyMs: 3000 }, + tokens: { + input: 1000, + prompt: 2000, + candidates: 3000, + total: 5000, + cached: 500, + thoughts: 100, + tool: 50, + }, + roles: {}, + }, + }, + }); + + const { lastFrame } = await renderWithMockedStats(metrics); + const output = lastFrame(); + + expect(output).toContain('gemini-3.5-flash'); + expect(output).not.toContain('gemini-3-flash\u0020'); // Avoid matching parts of substrings if not intended + expect(output).toMatchSnapshot(); + }); + it('renders role breakdown correctly under models', async () => { const metrics = createTestMetrics({ models: { diff --git a/packages/cli/src/ui/components/StatsDisplay.tsx b/packages/cli/src/ui/components/StatsDisplay.tsx index 233e9f3ed4..1d2e2e437c 100644 --- a/packages/cli/src/ui/components/StatsDisplay.tsx +++ b/packages/cli/src/ui/components/StatsDisplay.tsx @@ -24,7 +24,7 @@ import { import { computeSessionStats } from '../utils/computeStats.js'; import { useSettings } from '../contexts/SettingsContext.js'; import type { QuotaStats } from '../types.js'; -import { LlmRole } from '@google/gemini-cli-core'; +import { LlmRole, getDisplayString } from '@google/gemini-cli-core'; // A more flexible and powerful StatRow component interface StatRowProps { @@ -101,7 +101,7 @@ const ModelUsageTable: React.FC = ({ models }) => { Object.entries(models).forEach(([name, metrics]) => { rows.push({ name, - displayName: name, + displayName: getDisplayString(name), requests: metrics.api.totalRequests, cachedTokens: metrics.tokens.cached.toLocaleString(), inputTokens: metrics.tokens.prompt.toLocaleString(), diff --git a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap index 26a8b0a8e7..f335f3d640 100644 --- a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap @@ -84,6 +84,33 @@ exports[`AlternateBufferQuittingDisplay > renders with history but no pending it +Tips for getting started: +1. Create GEMINI.md files to customize your interactions +2. /help for more information +3. Ask coding questions, edit code or run commands +4. Be specific for the best results +╭──────────────────────────────────────────────────────────────────────────╮ +│ ✓ tool1 Description for tool 1 │ +│ │ +╰──────────────────────────────────────────────────────────────────────────╯ +╭──────────────────────────────────────────────────────────────────────────╮ +│ ✓ tool2 Description for tool 2 │ +│ │ +╰──────────────────────────────────────────────────────────────────────────╯ +" +`; + +exports[`AlternateBufferQuittingDisplay > renders with pending items but no history > with_history_no_pending 1`] = ` +" + ▝▜▄ ▗█▀▀▜▙▝█▛▀▀▌▜██▖▟██▘▜█▘▜██▖▝█▛▝█▛ + ▝▜▄ █▌ █▙▟ ▐█▝█▛▐█ ▐█ ▐█▝█▖█▌ █▌ + ▗▟▀ ▜▙ ▝█▛ █▌▝ ▖▐█ ▐█ ▐█ ▐█ ▝██▌ █▌ + ▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀ + + Gemini CLI v0.10.0 + + + Tips for getting started: 1. Create GEMINI.md files to customize your interactions 2. /help for more information @@ -123,6 +150,29 @@ Tips for getting started: " `; +exports[`AlternateBufferQuittingDisplay > renders with user and gemini messages > with_confirming_tool 1`] = ` +" + ▝▜▄ ▗█▀▀▜▙▝█▛▀▀▌▜██▖▟██▘▜█▘▜██▖▝█▛▝█▛ + ▝▜▄ █▌ █▙▟ ▐█▝█▛▐█ ▐█ ▐█▝█▖█▌ █▌ + ▗▟▀ ▜▙ ▝█▛ █▌▝ ▖▐█ ▐█ ▐█ ▐█ ▝██▌ █▌ + ▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀ + + Gemini CLI v0.10.0 + + + +Tips for getting started: +1. Create GEMINI.md files to customize your interactions +2. /help for more information +3. Ask coding questions, edit code or run commands +4. Be specific for the best results + +Action Required (was prompted): + +? confirming_tool Confirming tool description +" +`; + exports[`AlternateBufferQuittingDisplay > renders with user and gemini messages > with_user_gemini_messages 1`] = ` " ▝▜▄ ▗█▀▀▜▙▝█▛▀▀▌▜██▖▟██▘▜█▘▜██▖▝█▛▝█▛ diff --git a/packages/cli/src/ui/components/__snapshots__/Banner-Banner-handles-newlines-in-text-2.snap.svg b/packages/cli/src/ui/components/__snapshots__/Banner-Banner-handles-newlines-in-text-2.snap.svg new file mode 100644 index 0000000000..a6272e0fa9 --- /dev/null +++ b/packages/cli/src/ui/components/__snapshots__/Banner-Banner-handles-newlines-in-text-2.snap.svg @@ -0,0 +1,20 @@ + + + + + ╭──────────────────────────────────────────────────────────────────────────────╮ + + L + i + n + e + 1 + + + Line 2 + + ╰──────────────────────────────────────────────────────────────────────────────╯ + + \ No newline at end of file diff --git a/packages/cli/src/ui/components/__snapshots__/Banner.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Banner.test.tsx.snap index 6df246dede..84e54366a4 100644 --- a/packages/cli/src/ui/components/__snapshots__/Banner.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/Banner.test.tsx.snap @@ -7,6 +7,13 @@ exports[`Banner > handles newlines in text 1`] = ` ╰──────────────────────────────────────────────────────────────────────────────╯" `; +exports[`Banner > handles newlines in text 2`] = ` +"╭──────────────────────────────────────────────────────────────────────────────╮ +│ Line 1 │ +│ Line 2 │ +╰──────────────────────────────────────────────────────────────────────────────╯" +`; + exports[`Banner > renders in info mode 1`] = ` "╭──────────────────────────────────────────────────────────────────────────────╮ │ Info Message │ diff --git a/packages/cli/src/ui/components/__snapshots__/Checklist.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Checklist.test.tsx.snap index 94dff26bb2..d755319541 100644 --- a/packages/cli/src/ui/components/__snapshots__/Checklist.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/Checklist.test.tsx.snap @@ -11,6 +11,17 @@ exports[` > renders expanded view correctly 1`] = ` " `; +exports[` > renders expanded view correctly 2`] = ` +"──────────────────────────────────────────────────────────────────────────────────────────────────── + Test List 1/3 completed (toggle me) + + ✓ Task 1 + » Task 2 + ☐ Task 3 + ✗ Task 4 +" +`; + exports[` > renders summary view correctly (collapsed) 1`] = ` "──────────────────────────────────────────────────────────────────────────────────────────────────── Test List 1/3 completed (toggle me) » Task 2 diff --git a/packages/cli/src/ui/components/__snapshots__/ContextSummaryDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ContextSummaryDisplay.test.tsx.snap index 7330b89e4d..1b4085504e 100644 --- a/packages/cli/src/ui/components/__snapshots__/ContextSummaryDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ContextSummaryDisplay.test.tsx.snap @@ -5,6 +5,11 @@ exports[` > should not render empty parts 1`] = ` " `; +exports[` > should not render empty parts 2`] = ` +" 1 open file (F4 to view) +" +`; + exports[` > should render on a single line on a wide screen 1`] = ` " 1 open file (F4 to view) · 1 GEMINI.md file · 1 MCP server · 1 skill " diff --git a/packages/cli/src/ui/components/__snapshots__/LoopDetectionConfirmation.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/LoopDetectionConfirmation.test.tsx.snap index e8914dceeb..a36c321f09 100644 --- a/packages/cli/src/ui/components/__snapshots__/LoopDetectionConfirmation.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/LoopDetectionConfirmation.test.tsx.snap @@ -1,5 +1,18 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`LoopDetectionConfirmation > contains the expected options 1`] = ` +" ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ ? A potential loop was detected │ + │ │ + │ This can happen due to repetitive tool calls or other model behavior. Do you want to keep loop │ + │ detection enabled or disable it for this session? │ + │ │ + │ ● 1. Keep loop detection enabled (esc) │ + │ 2. Disable loop detection for this session │ + ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +" +`; + exports[`LoopDetectionConfirmation > renders correctly 1`] = ` " ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │ ? A potential loop was detected │ diff --git a/packages/cli/src/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap index 841c830a84..6830e10c2a 100644 --- a/packages/cli/src/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ModelStatsDisplay.test.tsx.snap @@ -215,3 +215,26 @@ exports[` > should render "no API calls" message when there ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ " `; + +exports[` > should resolve gemini-3-flash to gemini-3.5-flash via getDisplayString 1`] = ` +"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ +│ Model Stats For Nerds │ +│ │ +│ │ +│ Metric gemini-3.5-flash │ +│ ────────────────────────────────────────────────────────────────────────────────────────────── │ +│ API │ +│ Requests 1 │ +│ Errors 0 (0.0%) │ +│ Avg Latency 100ms │ +│ Tokens │ +│ Total 30 │ +│ ↳ Input 5 │ +│ ↳ Cache Reads 5 (50.0%) │ +│ ↳ Thoughts 2 │ +│ ↳ Tool 1 │ +│ ↳ Output 20 │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +" +`; diff --git a/packages/cli/src/ui/components/__snapshots__/ShortcutsHelp.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ShortcutsHelp.test.tsx.snap index f51dca0860..dd0f0b66fe 100644 --- a/packages/cli/src/ui/components/__snapshots__/ShortcutsHelp.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ShortcutsHelp.test.tsx.snap @@ -16,6 +16,22 @@ exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'linux' 1`] = ` " `; +exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'linux' 2`] = ` +"──────────────────────────────────────── + Shortcuts See /help for more + ! shell mode + @ select file or folder + Double Esc clear & rewind + Tab focus UI + Ctrl+Y YOLO mode + Shift+Tab cycle mode + Ctrl+V paste images + Alt+M raw markdown mode + Ctrl+R reverse-search history + Ctrl+G open external editor +" +`; + exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'mac' 1`] = ` "──────────────────────────────────────── Shortcuts See /help for more diff --git a/packages/cli/src/ui/components/__snapshots__/StatsDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/StatsDisplay.test.tsx.snap index 8a58ee3440..59a257144a 100644 --- a/packages/cli/src/ui/components/__snapshots__/StatsDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/StatsDisplay.test.tsx.snap @@ -292,3 +292,30 @@ exports[` > renders role breakdown correctly under models 1`] = ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ " `; + +exports[` > resolves gemini-3-flash to gemini-3.5-flash in the model usage table 1`] = ` +"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ +│ Session Stats │ +│ │ +│ Interaction Summary │ +│ Session ID: test-session-id │ +│ Tool Calls: 0 ( ✓ 0 x 0 ) │ +│ Success Rate: 0.0% │ +│ │ +│ Performance │ +│ Wall Time: 1s │ +│ Agent Active: 3.0s │ +│ » API Time: 3.0s (100.0%) │ +│ » Tool Time: 0s (0.0%) │ +│ │ +│ │ +│ Model Usage │ +│ Use /model to view model quota information │ +│ │ +│ Model Reqs Input Tokens Cache Reads Output Tokens │ +│ ────────────────────────────────────────────────────────────────────────────────────────────── │ +│ gemini-3.5-flash 5 2,000 500 3,000 │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +" +`; diff --git a/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap index 37ed33585c..953310dbd5 100644 --- a/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ThemeDialog.test.tsx.snap @@ -25,6 +25,31 @@ exports[`Initial Theme Selection > should default to a dark theme when terminal " `; +exports[`Initial Theme Selection > should default to a dark theme when terminal background is dark and no theme is set 2`] = ` +"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ +│ > Select Theme Preview │ +│ ▲ ┌─────────────────────────────────────────────────┐ │ +│ 1. ANSI Dark │ │ │ +│ 2. Atom One Dark │ 1 # function │ │ +│ 3. Ayu Dark │ 2 def fibonacci(n): │ │ +│ ● 4. Default Dark (Matches terminal) │ 3 a, b = 0, 1 │ │ +│ 5. Dracula Dark │ 4 for _ in range(n): │ │ +│ 6. GitHub Dark │ 5 a, b = b, a + b │ │ +│ 7. GitHub Dark Colorblind Dark │ 6 return a │ │ +│ 8. Holiday Dark │ │ │ +│ 9. Shades Of Purple Dark │ 1 - print("Hello, " + name) │ │ +│ 10. Solarized Dark │ 1 + print(f"Hello, {name}!") │ │ +│ 11. Tokyo Night Dark │ │ │ +│ 12. ANSI Light (Incompatible) └─────────────────────────────────────────────────┘ │ +│ ▼ │ +│ │ +│ (Use Enter to select, Tab to configure scope, Esc to close) │ +│ │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +" +`; + exports[`Initial Theme Selection > should default to a light theme when terminal background is light and no theme is set 1`] = ` "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ @@ -89,6 +114,20 @@ exports[`ThemeDialog Snapshots > should render correctly in scope selector mode " `; +exports[`ThemeDialog Snapshots > should render correctly in scope selector mode 2`] = ` +"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ │ +│ > Apply To │ +│ ● 1. User Settings │ +│ 2. Workspace Settings │ +│ 3. System Settings │ +│ │ +│ (Use Enter to apply scope, Tab to select theme, Esc to close) │ +│ │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +" +`; + exports[`ThemeDialog Snapshots > should render correctly in theme selection mode (isDevelopment: false) 1`] = ` "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ diff --git a/packages/cli/src/ui/components/__snapshots__/Tips.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Tips.test.tsx.snap index dbc60fcf4d..7a42180790 100644 --- a/packages/cli/src/ui/components/__snapshots__/Tips.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/Tips.test.tsx.snap @@ -18,3 +18,12 @@ Tips for getting started: 3. Be specific for the best results " `; + +exports[`Tips > 'renders fewer tips when GEMINI.md exi…' 2`] = ` +" +Tips for getting started: +1. /help for more information +2. Ask coding questions, edit code or run commands +3. Be specific for the best results +" +`; diff --git a/packages/cli/src/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap index 6b7b847f12..7673d1e2da 100644 --- a/packages/cli/src/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/ToolStatsDisplay.test.tsx.snap @@ -41,6 +41,27 @@ exports[` > should display stats for multiple tools correctl " `; +exports[` > should display stats for multiple tools correctly 2`] = ` +"╭────────────────────────────────────────────────────────────────────╮ +│ │ +│ Tool Stats For Nerds │ +│ │ +│ Tool Name Calls Success Rate Avg Duration │ +│ ──────────────────────────────────────────────────────────────── │ +│ tool-a 2 50.0% 100ms │ +│ tool-b 1 100.0% 100ms │ +│ │ +│ User Decision Summary │ +│ Total Reviewed Suggestions: 3 │ +│ » Accepted: 1 │ +│ » Rejected: 1 │ +│ » Modified: 1 │ +│ ──────────────────────────────────────────────────────────────── │ +│ Overall Agreement Rate: 33.3% │ +╰────────────────────────────────────────────────────────────────────╯ +" +`; + exports[` > should handle large values without wrapping or overlapping 1`] = ` "╭────────────────────────────────────────────────────────────────────╮ │ │ diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ErrorMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ErrorMessage.test.tsx.snap index 0f5c270ae4..9a29255f18 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/ErrorMessage.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/ErrorMessage.test.tsx.snap @@ -6,6 +6,12 @@ exports[`ErrorMessage > renders multiline error messages 1`] = ` " `; +exports[`ErrorMessage > renders multiline error messages 2`] = ` +"✕ Error line 1 + Error line 2 +" +`; + exports[`ErrorMessage > renders with the correct prefix and text 1`] = ` "✕ Something went wrong " diff --git a/packages/cli/src/ui/components/messages/__snapshots__/GeminiMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/GeminiMessage.test.tsx.snap index 6e624485e2..9d7734854f 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/GeminiMessage.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/GeminiMessage.test.tsx.snap @@ -16,6 +16,13 @@ exports[` - Raw Markdown Display Snapshots > renders pending st " `; +exports[` - Raw Markdown Display Snapshots > renders pending state with renderMarkdown=true 2`] = ` +"✦ Test bold and code markdown + + 1 const x = 1; +" +`; + exports[` - Raw Markdown Display Snapshots > renders with renderMarkdown=false '(raw markdown with syntax highlightin…' 1`] = ` "✦ Test **bold** and \`code\` markdown @@ -40,3 +47,12 @@ exports[` - Raw Markdown Display Snapshots > wraps long lines c truncation " `; + +exports[` - Raw Markdown Display Snapshots > wraps long lines correctly in raw markdown mode 2`] = ` +"✦ This is a long + line that should + wrap correctly + without + truncation +" +`; diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.compact.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.compact.test.tsx.snap index a60ac429c7..b42f8a5a1a 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.compact.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.compact.test.tsx.snap @@ -26,6 +26,12 @@ exports[`ToolGroupMessage Compact Rendering > does not add an extra empty line i " `; +exports[`ToolGroupMessage Compact Rendering > does not add an extra empty line if a compact tool has a dense payload 2`] = ` +" ✓ ReadFolder Listing files → file1.txt + ✓ ReadFile Reading file → read file +" +`; + exports[`ToolGroupMessage Compact Rendering > renders consecutive compact tools without empty lines between them 1`] = ` " ✓ ReadFolder Listing files → file1.txt file2.txt ✓ ReadFolder Listing files → file3.txt diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolMessageRawMarkdown.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolMessageRawMarkdown.test.tsx.snap index 43140c27d1..15ad761eed 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/ToolMessageRawMarkdown.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolMessageRawMarkdown.test.tsx.snap @@ -16,6 +16,14 @@ exports[` - Raw Markdown Display Snapshots > renders with renderM " `; +exports[` - Raw Markdown Display Snapshots > renders with renderMarkdown=false, useAlternateBuffer=true '(raw markdown, alternate buffer)' 2`] = ` +"╭──────────────────────────────────────────────────────────────────────────────╮ +│ ✓ test-tool A tool for testing │ +│ │ +│ Test **bold** and \`code\` markdown │ +" +`; + exports[` - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=false '(constrained height, regular buffer -…' 1`] = ` "╭──────────────────────────────────────────────────────────────────────────────╮ │ ✓ test-tool A tool for testing │ diff --git a/packages/cli/src/ui/components/messages/__snapshots__/UserMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/UserMessage.test.tsx.snap index 0459cae90e..c86b1b13e2 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/UserMessage.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/UserMessage.test.tsx.snap @@ -8,6 +8,14 @@ exports[`UserMessage > renders multiline user message 1`] = ` " `; +exports[`UserMessage > renders multiline user message 2`] = ` +"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ + > Line 1 + Line 2 +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +" +`; + exports[`UserMessage > renders normal user message with correct prefix 1`] = ` "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ > Hello Gemini diff --git a/packages/cli/src/ui/components/messages/__snapshots__/WarningMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/WarningMessage.test.tsx.snap index f7f4b40397..7049a6899d 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/WarningMessage.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/WarningMessage.test.tsx.snap @@ -7,6 +7,13 @@ exports[`WarningMessage > renders multiline warning messages 1`] = ` " `; +exports[`WarningMessage > renders multiline warning messages 2`] = ` +" +⚠ Warning line 1 + Warning line 2 +" +`; + exports[`WarningMessage > renders with the correct prefix and text 1`] = ` " ⚠ Watch out! diff --git a/packages/cli/src/ui/components/views/__snapshots__/ChatList.test.tsx.snap b/packages/cli/src/ui/components/views/__snapshots__/ChatList.test.tsx.snap index b6c2c684e1..1a47cfa1a0 100644 --- a/packages/cli/src/ui/components/views/__snapshots__/ChatList.test.tsx.snap +++ b/packages/cli/src/ui/components/views/__snapshots__/ChatList.test.tsx.snap @@ -9,6 +9,15 @@ Note: Newest last, oldest first " `; +exports[` > handles invalid date formats gracefully 2`] = ` +"List of saved conversations: + + - bad-date-chat (Invalid Date) + +Note: Newest last, oldest first +" +`; + exports[` > renders correctly with a list of chats 1`] = ` "List of saved conversations: diff --git a/packages/cli/src/ui/layouts/__snapshots__/DefaultAppLayout.test.tsx.snap b/packages/cli/src/ui/layouts/__snapshots__/DefaultAppLayout.test.tsx.snap index 48cb662534..0ff6e08216 100644 --- a/packages/cli/src/ui/layouts/__snapshots__/DefaultAppLayout.test.tsx.snap +++ b/packages/cli/src/ui/layouts/__snapshots__/DefaultAppLayout.test.tsx.snap @@ -30,6 +30,20 @@ BackgroundTaskDisplay +Notifications +CopyModeWarning +Composer +ExitWarning +" +`; + +exports[` > shows BackgroundTaskDisplay when StreamingState is NOT WaitingForConfirmation 2`] = ` +"MainContent +BackgroundTaskDisplay + + + + Notifications CopyModeWarning Composer diff --git a/packages/core/src/availability/fallbackIntegration.test.ts b/packages/core/src/availability/fallbackIntegration.test.ts index 6c49938ed9..1ee53da37e 100644 --- a/packages/core/src/availability/fallbackIntegration.test.ts +++ b/packages/core/src/availability/fallbackIntegration.test.ts @@ -28,6 +28,7 @@ describe('Fallback Integration', () => { getActiveModel: () => PREVIEW_GEMINI_MODEL_AUTO, setActiveModel: vi.fn(), getUserTier: () => undefined, + getHasAccessToPreviewModel: () => true, getModelAvailabilityService: () => availabilityService, modelConfigService: undefined as unknown as ModelConfigService, } as unknown as Config; diff --git a/packages/core/src/availability/policyCatalog.test.ts b/packages/core/src/availability/policyCatalog.test.ts index 04e35018d5..9aa605abd4 100644 --- a/packages/core/src/availability/policyCatalog.test.ts +++ b/packages/core/src/availability/policyCatalog.test.ts @@ -28,7 +28,6 @@ describe('policyCatalog', () => { const chain = getModelPolicyChain({ previewEnabled: true, useGemini31: true, - useGemini31FlashLite: false, }); expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_MODEL); expect(chain).toHaveLength(2); @@ -39,7 +38,6 @@ describe('policyCatalog', () => { const chain = getModelPolicyChain({ previewEnabled: true, useGemini31: true, - useGemini31FlashLite: false, useCustomToolModel: true, }); expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL); diff --git a/packages/core/src/availability/policyCatalog.ts b/packages/core/src/availability/policyCatalog.ts index a5694e94b8..e2d1df075e 100644 --- a/packages/core/src/availability/policyCatalog.ts +++ b/packages/core/src/availability/policyCatalog.ts @@ -33,6 +33,7 @@ export interface ModelPolicyOptions { useGemini31?: boolean; useGemini31FlashLite?: boolean; useCustomToolModel?: boolean; + useGemini3_5Flash?: boolean; } const DEFAULT_ACTIONS: ModelPolicyActionMap = { @@ -93,8 +94,10 @@ export function getModelPolicyChain( const proModel = resolveModel( PREVIEW_GEMINI_MODEL, options.useGemini31, - options.useGemini31FlashLite, options.useCustomToolModel, + true, + undefined, + options.useGemini3_5Flash, ); return [ definePolicy({ diff --git a/packages/core/src/availability/policyHelpers.test.ts b/packages/core/src/availability/policyHelpers.test.ts index 7db99bb1aa..0d4a25e8c4 100644 --- a/packages/core/src/availability/policyHelpers.test.ts +++ b/packages/core/src/availability/policyHelpers.test.ts @@ -30,14 +30,17 @@ const createMockConfig = (overrides: Partial = {}): Config => { getUserTier: () => undefined, getModel: () => 'gemini-2.5-pro', getGemini31LaunchedSync: () => false, - getGemini31FlashLiteLaunchedSync: () => false, getUseCustomToolModelSync: () => { const useGemini31 = config.getGemini31LaunchedSync(); const authType = config.getContentGeneratorConfig().authType; 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; @@ -110,7 +113,7 @@ describe('policyHelpers', () => { }); const chain = resolvePolicyChain(config, DEFAULT_GEMINI_FLASH_LITE_MODEL); expect(chain).toHaveLength(3); - expect(chain[0]?.model).toBe('gemini-2.5-flash-lite'); + expect(chain[0]?.model).toBe('gemini-3.1-flash-lite'); expect(chain[1]?.model).toBe('gemini-2.5-flash'); expect(chain[2]?.model).toBe('gemini-2.5-pro'); }); @@ -121,7 +124,7 @@ describe('policyHelpers', () => { }); const chain = resolvePolicyChain(config); expect(chain).toHaveLength(3); - expect(chain[0]?.model).toBe('gemini-2.5-flash-lite'); + expect(chain[0]?.model).toBe('gemini-3.1-flash-lite'); expect(chain[1]?.model).toBe('gemini-2.5-flash'); expect(chain[2]?.model).toBe('gemini-2.5-pro'); }); @@ -187,6 +190,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)', @@ -222,9 +226,9 @@ describe('policyHelpers', () => { getExperimentalDynamicModelConfiguration: () => dynamic, getModel: () => model, getGemini31LaunchedSync: () => useGemini31 ?? false, - getGemini31FlashLiteLaunchedSync: () => false, getHasAccessToPreviewModel: () => hasAccess ?? true, getContentGeneratorConfig: () => ({ authType }), + getReleaseChannel: () => 'preview', modelConfigService: new ModelConfigService(DEFAULT_MODEL_CONFIGS), }); diff --git a/packages/core/src/availability/policyHelpers.ts b/packages/core/src/availability/policyHelpers.ts index 28447bd836..71b8240120 100644 --- a/packages/core/src/availability/policyHelpers.ts +++ b/packages/core/src/availability/policyHelpers.ts @@ -52,19 +52,21 @@ export function resolvePolicyChain( let chain: ModelPolicyChain | undefined; const useGemini31 = config.getGemini31LaunchedSync?.() ?? false; - const useGemini31FlashLite = - config.getGemini31FlashLiteLaunchedSync?.() ?? false; const useCustomToolModel = config.getUseCustomToolModelSync?.() ?? false; - const hasAccessToPreview = config.getHasAccessToPreviewModel?.() ?? true; + const hasAccessToPreview = config.getHasAccessToPreviewModel?.() ?? false; + const useGemini3_5Flash = config.hasGemini35FlashGAAccess?.() ?? false; + + // Capture the original family intent before any normalization or early downgrade. + const isOriginallyGemini3 = isGemini3Model(modelFromConfig, config); const resolvedModel = normalizeModelId( resolveModel( modelFromConfig, useGemini31, - useGemini31FlashLite, useCustomToolModel, hasAccessToPreview, config, + useGemini3_5Flash, ), ); const isAutoPreferred = normalizedPreferredModel @@ -75,26 +77,19 @@ export function resolvePolicyChain( // We always wrap around for Gemini 3 chains to ensure maximum availability // between models in the same family (e.g. fallback to Pro if Flash is exhausted). const effectiveWrapsAround = - wrapsAround || - isAutoPreferred || - isAutoConfigured || - isGemini3Model(resolvedModel, config); + wrapsAround || isAutoPreferred || isAutoConfigured || isOriginallyGemini3; // --- DYNAMIC PATH --- if (config.getExperimentalDynamicModelConfiguration?.() === true) { const context = { useGemini3_1: useGemini31, - useGemini3_1FlashLite: useGemini31FlashLite, useCustomTools: useCustomToolModel, + useGemini3_5Flash, }; if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) { chain = config.modelConfigService.resolveChain('lite', context); - } else if ( - isGemini3Model(normalizeModelId(resolvedModel), config) || - isAutoPreferred || - isAutoConfigured - ) { + } else if (isOriginallyGemini3 || isAutoPreferred || isAutoConfigured) { // 1. Try to find a chain specifically for the current configured alias if ( isAutoConfigured && @@ -131,15 +126,11 @@ export function resolvePolicyChain( if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) { chain = getFlashLitePolicyChain(); - } else if ( - isGemini3Model(resolvedModel, config) || - isAutoPreferred || - isAutoConfigured - ) { + } else if (isOriginallyGemini3 || isAutoPreferred || isAutoConfigured) { const isAutoSelection = isAutoPreferred || isAutoConfigured; if (hasAccessToPreview) { const previewEnabled = - isGemini3Model(resolvedModel, config) || + isOriginallyGemini3 || normalizedPreferredModel === PREVIEW_GEMINI_MODEL_AUTO || configuredModel === PREVIEW_GEMINI_MODEL_AUTO; chain = getModelPolicyChain({ @@ -147,8 +138,8 @@ export function resolvePolicyChain( isAutoSelection, userTier: config.getUserTier(), useGemini31, - useGemini31FlashLite, useCustomToolModel, + useGemini3_5Flash, }); } else { // User requested Gemini 3 but has no access. Proactively downgrade @@ -158,8 +149,8 @@ export function resolvePolicyChain( isAutoSelection, userTier: config.getUserTier(), useGemini31, - useGemini31FlashLite, useCustomToolModel, + useGemini3_5Flash, }); } } else { diff --git a/packages/core/src/code_assist/codeAssist.test.ts b/packages/core/src/code_assist/codeAssist.test.ts index 1a4ba66f27..0a20be1d43 100644 --- a/packages/core/src/code_assist/codeAssist.test.ts +++ b/packages/core/src/code_assist/codeAssist.test.ts @@ -15,6 +15,7 @@ import { } from './codeAssist.js'; import type { Config } from '../config/config.js'; import { LoggingContentGenerator } from '../core/loggingContentGenerator.js'; +import { ModelMappingContentGenerator } from '../core/modelMappingContentGenerator.js'; import { UserTierId } from './types.js'; // Mock dependencies @@ -22,11 +23,15 @@ vi.mock('./oauth2.js'); vi.mock('./setup.js'); vi.mock('./server.js'); vi.mock('../core/loggingContentGenerator.js'); +vi.mock('../core/modelMappingContentGenerator.js'); const mockedGetOauthClient = vi.mocked(getOauthClient); const mockedSetupUser = vi.mocked(setupUser); const MockedCodeAssistServer = vi.mocked(CodeAssistServer); const MockedLoggingContentGenerator = vi.mocked(LoggingContentGenerator); +const MockedModelMappingContentGenerator = vi.mocked( + ModelMappingContentGenerator, +); describe('codeAssist', () => { beforeEach(() => { @@ -178,5 +183,47 @@ describe('codeAssist', () => { const server = getCodeAssistServer(mockConfig); expect(server).toBeUndefined(); }); + + it('should unwrap and return the server if it is wrapped in a ModelMappingContentGenerator', () => { + const mockServer = new MockedCodeAssistServer({} as never, '', {}); + const mockMapper = new MockedModelMappingContentGenerator( + {} as never, + {}, + ); + vi.spyOn(mockMapper, 'getWrapped').mockReturnValue(mockServer); + + const mockConfig = { + getContentGenerator: () => mockMapper, + } as unknown as Config; + + const server = getCodeAssistServer(mockConfig); + expect(server).toBe(mockServer); + expect(mockMapper.getWrapped).toHaveBeenCalled(); + }); + + it('should recursively unwrap multiple layers of LoggingContentGenerator and ModelMappingContentGenerator', () => { + const mockServer = new MockedCodeAssistServer({} as never, '', {}); + const mockLogger = new MockedLoggingContentGenerator( + {} as never, + {} as never, + ); + const mockMapper = new MockedModelMappingContentGenerator( + {} as never, + {}, + ); + + // Mapper wraps Logger wraps Server + vi.spyOn(mockMapper, 'getWrapped').mockReturnValue(mockLogger); + vi.spyOn(mockLogger, 'getWrapped').mockReturnValue(mockServer); + + const mockConfig = { + getContentGenerator: () => mockMapper, + } as unknown as Config; + + const server = getCodeAssistServer(mockConfig); + expect(server).toBe(mockServer); + expect(mockMapper.getWrapped).toHaveBeenCalled(); + expect(mockLogger.getWrapped).toHaveBeenCalled(); + }); }); }); diff --git a/packages/core/src/code_assist/codeAssist.ts b/packages/core/src/code_assist/codeAssist.ts index 4fcbea7853..b6c28c44a7 100644 --- a/packages/core/src/code_assist/codeAssist.ts +++ b/packages/core/src/code_assist/codeAssist.ts @@ -10,6 +10,7 @@ import { setupUser } from './setup.js'; import { CodeAssistServer, type HttpOptions } from './server.js'; import type { Config } from '../config/config.js'; import { LoggingContentGenerator } from '../core/loggingContentGenerator.js'; +import { ModelMappingContentGenerator } from '../core/modelMappingContentGenerator.js'; export async function createCodeAssistContentGenerator( httpOptions: HttpOptions, @@ -43,9 +44,15 @@ export function getCodeAssistServer( ): CodeAssistServer | undefined { let server = config.getContentGenerator(); - // Unwrap LoggingContentGenerator if present - if (server instanceof LoggingContentGenerator) { - server = server.getWrapped(); + // Recursively unwrap LoggingContentGenerator and ModelMappingContentGenerator + while (true) { + if (server instanceof LoggingContentGenerator) { + server = server.getWrapped(); + } else if (server instanceof ModelMappingContentGenerator) { + server = server.getWrapped(); + } else { + break; + } } if (!(server instanceof CodeAssistServer)) { diff --git a/packages/core/src/code_assist/experiments/flagNames.ts b/packages/core/src/code_assist/experiments/flagNames.ts index 125ff005a9..ceb9b2e0f4 100644 --- a/packages/core/src/code_assist/experiments/flagNames.ts +++ b/packages/core/src/code_assist/experiments/flagNames.ts @@ -18,8 +18,8 @@ export const ExperimentFlags = { MASKING_PROTECT_LATEST_TURN: 45758819, GEMINI_3_1_PRO_LAUNCHED: 45760185, PRO_MODEL_NO_ACCESS: 45768879, - GEMINI_3_1_FLASH_LITE_LAUNCHED: 45771641, DEFAULT_REQUEST_TIMEOUT: 45773134, + GEMINI_3_5_FLASH_GA_LAUNCHED: 45780819, } as const; export type ExperimentFlagName = diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 6696541107..b3f36a0c51 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -68,7 +68,6 @@ import { PREVIEW_GEMINI_3_1_MODEL, DEFAULT_GEMINI_MODEL_AUTO, PREVIEW_GEMINI_MODEL_AUTO, - PREVIEW_GEMINI_FLASH_MODEL, } from './models.js'; import { Storage } from './storage.js'; import type { AgentLoopContext } from './agent-loop-context.js'; @@ -702,20 +701,6 @@ describe('Server Config (config.ts)', () => { }); }); - describe('getGemini31FlashLiteLaunchedSync', () => { - it.each([AuthType.USE_GEMINI, AuthType.USE_VERTEX_AI, AuthType.GATEWAY])( - 'should return true for %s', - async (authType) => { - const config = new Config(baseParams); - vi.mocked(createContentGeneratorConfig).mockResolvedValue({ - authType, - }); - await config.refreshAuth(authType); - expect(config.getGemini31FlashLiteLaunchedSync()).toBe(true); - }, - ); - }); - describe('getProModelNoAccessSync', () => { it('should return experiment value for AuthType.LOGIN_WITH_GOOGLE', async () => { vi.mocked(getExperiments).mockResolvedValue({ @@ -969,7 +954,7 @@ describe('Server Config (config.ts)', () => { await config.getExperimentsAsync(); await vi.waitFor(() => { - expect(config.getModel()).toBe(PREVIEW_GEMINI_FLASH_MODEL); + expect(config.getModel()).toBe(config.getPreviewGeminiFlashModel()); }); }); @@ -4331,3 +4316,57 @@ describe('ADKSettings', () => { expect(config.getAgentSessionNoninteractiveEnabled()).toBe(true); }); }); + +describe('hasGemini35FlashGAAccess model setting', () => { + const baseParams: ConfigParameters = { + sessionId: 'test', + targetDir: '.', + debugMode: false, + model: 'test-model', + cwd: '.', + }; + + it('should set DEFAULT_GEMINI_FLASH_MODEL to gemini-3.5-flash and PREVIEW_GEMINI_FLASH_MODEL to gemini-3-flash-preview if hasGemini35FlashGAAccess returns true and authType is USE_GEMINI', () => { + const config = new Config(baseParams); + config['contentGeneratorConfig'] = { authType: AuthType.USE_GEMINI }; + + // Set experiment to return true for GEMINI_3_5_FLASH_GA_LAUNCHED + config.setExperiments({ + experimentIds: [], + flags: { + [ExperimentFlags.GEMINI_3_5_FLASH_GA_LAUNCHED]: { + boolValue: true, + }, + }, + }); + + // Call the method + const result = config.hasGemini35FlashGAAccess(); + expect(result).toBe(true); + + expect(config.getDefaultGeminiFlashModel()).toBe('gemini-3.5-flash'); + expect(config.getPreviewGeminiFlashModel()).toBe('gemini-3-flash-preview'); + }); + + it('should set DEFAULT_GEMINI_FLASH_MODEL and PREVIEW_GEMINI_FLASH_MODEL to gemini-3.5-flash if hasGemini35FlashGAAccess returns true and authType is not USE_GEMINI', () => { + const config = new Config(baseParams); + config['contentGeneratorConfig'] = { authType: AuthType.LOGIN_WITH_GOOGLE }; + + // Set experiment to return true for GEMINI_3_5_FLASH_GA_LAUNCHED + config.setExperiments({ + experimentIds: [], + flags: { + [ExperimentFlags.GEMINI_3_5_FLASH_GA_LAUNCHED]: { + boolValue: true, + }, + }, + }); + + // Call the method + const result = config.hasGemini35FlashGAAccess(); + expect(result).toBe(true); + + expect(config.getDefaultGeminiFlashModel()).toBe('gemini-3.5-flash'); + expect(config.getPreviewGeminiFlashModel()).toBe('gemini-3.5-flash'); + }); +}); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index b81737b0ea..ad2fdf2b9c 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -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'; @@ -184,6 +181,7 @@ import { InjectionService } from './injectionService.js'; import { ExecutionLifecycleService } from '../services/executionLifecycleService.js'; import { WORKSPACE_POLICY_TIER } from '../policy/config.js'; import { loadPoliciesFromToml } from '../policy/toml-loader.js'; +import { getChannelFromVersion } from '../utils/channel.js'; import { CheckerRunner } from '../safety/checker-runner.js'; import { ContextBuilder } from '../safety/context-builder.js'; @@ -762,7 +760,7 @@ 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,7 +980,7 @@ 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; @@ -2013,13 +2011,20 @@ export class Config implements McpContext, AgentLoopContext { 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.getUseCustomToolModelSync(), + this.getHasAccessToPreviewModel(), + this, + this.hasGemini35FlashGAAccess(), + ); + + const isPreview = isPreviewModel(primaryModel, this); + const proModel = primaryModel; const flashModel = isPreview - ? PREVIEW_GEMINI_FLASH_MODEL - : DEFAULT_GEMINI_FLASH_MODEL; + ? this.getPreviewGeminiFlashModel() + : this.getDefaultGeminiFlashModel(); const proQuota = this.modelQuotas.get(proModel); const flashQuota = this.modelQuotas.get(flashModel); @@ -2049,10 +2054,10 @@ export class Config implements McpContext, AgentLoopContext { const primaryModel = resolveModel( this.getModel(), this.getGemini31LaunchedSync(), - this.getGemini31FlashLiteLaunchedSync(), this.getUseCustomToolModelSync(), this.getHasAccessToPreviewModel(), this, + this.hasGemini35FlashGAAccess(), ); return this.modelQuotas.get(primaryModel)?.remaining; } @@ -2065,10 +2070,10 @@ export class Config implements McpContext, AgentLoopContext { const primaryModel = resolveModel( this.getModel(), this.getGemini31LaunchedSync(), - this.getGemini31FlashLiteLaunchedSync(), this.getUseCustomToolModelSync(), this.getHasAccessToPreviewModel(), this, + this.hasGemini35FlashGAAccess(), ); return this.modelQuotas.get(primaryModel)?.limit; } @@ -2081,10 +2086,10 @@ export class Config implements McpContext, AgentLoopContext { const primaryModel = resolveModel( this.getModel(), this.getGemini31LaunchedSync(), - this.getGemini31FlashLiteLaunchedSync(), this.getUseCustomToolModelSync(), this.getHasAccessToPreviewModel(), this, + this.hasGemini35FlashGAAccess(), ); return this.modelQuotas.get(primaryModel)?.resetTime; } @@ -3441,15 +3446,6 @@ export class Config implements McpContext, AgentLoopContext { return this.getGemini31LaunchedSync(); } - /** - * Returns whether Gemini 3.1 Flash Lite has been launched. - * This method is async and ensures that experiments are loaded before returning the result. - */ - async getGemini31FlashLiteLaunched(): Promise { - await this.ensureExperimentsLoaded(); - return this.getGemini31FlashLiteLaunchedSync(); - } - /** * Returns whether the custom tool model should be used. */ @@ -3478,6 +3474,47 @@ export class Config implements McpContext, AgentLoopContext { ); } + getReleaseChannel(): string { + return getChannelFromVersion(this._clientVersion); + } + + /** + * Returns whether Gemini 3.5 Flash GA has been launched. + * + * Note: This method should only be called after startup, once experiments have been loaded. + */ + hasGemini35FlashGAAccess(): boolean { + const authType = this.contentGeneratorConfig?.authType; + return (() => { + if (this.isGemini31LaunchedForAuthType(authType)) { + return true; + } + return ( + this.experiments?.flags[ExperimentFlags.GEMINI_3_5_FLASH_GA_LAUNCHED] + ?.boolValue ?? false + ); + })(); + } + + getPreviewGeminiFlashModel(): string { + const authType = this.contentGeneratorConfig?.authType; + if (this.hasGemini35FlashGAAccess()) { + if (authType === AuthType.USE_GEMINI) { + return 'gemini-3-flash-preview'; + } else { + return 'gemini-3.5-flash'; + } + } + return 'gemini-3-flash-preview'; + } + + getDefaultGeminiFlashModel(): string { + if (this.hasGemini35FlashGAAccess()) { + return 'gemini-3.5-flash'; + } + return 'gemini-2.5-flash'; + } + /** * Returns whether Gemini 3.1 has been launched. * @@ -3512,21 +3549,10 @@ export class Config implements McpContext, AgentLoopContext { } /** - * Returns whether Gemini 3.1 Flash Lite has been launched. - * - * Note: This method should only be called after startup, once experiments have been loaded. - * If you need to call this during startup or from an async context, use - * getGemini31FlashLiteLaunched instead. + * Returns the client version. */ - getGemini31FlashLiteLaunchedSync(): boolean { - const authType = this.contentGeneratorConfig?.authType; - if (this.isGemini31LaunchedForAuthType(authType)) { - return true; - } - return ( - this.experiments?.flags[ExperimentFlags.GEMINI_3_1_FLASH_LITE_LAUNCHED] - ?.boolValue ?? false - ); + get clientVersion(): string { + return this._clientVersion; } private async ensureExperimentsLoaded(): Promise { diff --git a/packages/core/src/config/defaultModelConfigs.ts b/packages/core/src/config/defaultModelConfigs.ts index 4ca12d1275..056b91b351 100644 --- a/packages/core/src/config/defaultModelConfigs.ts +++ b/packages/core/src/config/defaultModelConfigs.ts @@ -71,6 +71,24 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { model: 'gemini-3-flash-preview', }, }, + 'gemini-3.1-pro-preview': { + extends: 'chat-base-3', + modelConfig: { + model: 'gemini-3.1-pro-preview', + }, + }, + 'gemini-3.1-pro-preview-customtools': { + extends: 'chat-base-3', + modelConfig: { + model: 'gemini-3.1-pro-preview-customtools', + }, + }, + 'gemini-3.1-flash-lite-preview': { + extends: 'chat-base-3', + modelConfig: { + model: 'gemini-3.1-flash-lite-preview', + }, + }, 'gemini-2.5-pro': { extends: 'chat-base-2.5', modelConfig: { @@ -90,11 +108,17 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { }, }, 'gemini-3.1-flash-lite': { - extends: 'chat-base-2.5', + extends: 'chat-base-3', modelConfig: { model: 'gemini-3.1-flash-lite', }, }, + 'gemini-3.5-flash': { + extends: 'chat-base-3', + modelConfig: { + model: 'gemini-3.5-flash', + }, + }, 'gemma-4-31b-it': { extends: 'chat-base-3', modelConfig: { @@ -121,6 +145,12 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { model: 'gemini-3-flash-preview', }, }, + 'gemini-3.5-flash-base': { + extends: 'base', + modelConfig: { + model: 'gemini-3.5-flash', + }, + }, classifier: { extends: 'base', modelConfig: { @@ -300,13 +330,6 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { isVisible: true, features: { thinking: false, multimodalToolUse: true }, }, - 'gemini-3.1-flash-lite-preview': { - tier: 'flash-lite', - family: 'gemini-3', - isPreview: true, - isVisible: true, - features: { thinking: false, multimodalToolUse: true }, - }, 'gemini-3.1-pro-preview': { tier: 'pro', family: 'gemini-3', @@ -335,6 +358,13 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { isVisible: true, features: { thinking: false, multimodalToolUse: true }, }, + 'gemini-3.5-flash': { + tier: 'flash', + family: 'gemini-3', + isPreview: false, + isVisible: true, + features: { thinking: false, multimodalToolUse: true }, + }, 'gemini-2.5-pro': { tier: 'pro', family: 'gemini-2.5', @@ -375,9 +405,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: { @@ -399,22 +430,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: { @@ -445,26 +470,35 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { default: 'gemini-3-flash-preview', contexts: [ { - condition: { hasAccessToPreview: false }, + condition: { hasAccessToPreview: false, useGemini3_5Flash: true }, + target: 'gemini-3.5-flash', + }, + { + condition: { hasAccessToPreview: false, useGemini3_5Flash: false }, target: 'gemini-2.5-flash', }, ], }, - 'gemini-3-pro-preview': { - default: 'gemini-3-pro-preview', + 'gemini-3.5-flash': { + default: 'gemini-3.5-flash', contexts: [ - { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' }, { - condition: { useGemini3_1: true, useCustomTools: true }, - target: 'gemini-3.1-pro-preview-customtools', + condition: { useGemini3_5Flash: false, hasAccessToPreview: false }, + target: 'gemini-2.5-flash', }, { - condition: { useGemini3_1: true }, - target: 'gemini-3.1-pro-preview', + condition: { useGemini3_5Flash: false }, + target: 'gemini-3-flash-preview', }, ], }, - 'auto-gemini-3': { + 'gemini-2.5-flash': { + default: 'gemini-2.5-flash', + contexts: [ + { condition: { useGemini3_5Flash: true }, target: 'gemini-3.5-flash' }, + ], + }, + 'gemini-3-pro-preview': { default: 'gemini-3-pro-preview', contexts: [ { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' }, @@ -506,30 +540,13 @@ 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: [ - { - condition: { useGemini3_1FlashLite: false }, - target: 'gemini-2.5-flash-lite', - }, - ], - }, 'gemini-3.1-flash-lite': { default: 'gemini-3.1-flash-lite', - contexts: [ - { - condition: { useGemini3_1FlashLite: false }, - target: 'gemini-2.5-flash-lite', - }, - ], }, flash: { default: 'gemini-3-flash-preview', contexts: [ + { condition: { useGemini3_5Flash: true }, target: 'gemini-3.5-flash' }, { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-flash', @@ -538,27 +555,37 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { }, 'flash-lite': { default: 'gemini-3.1-flash-lite', + }, + 'auto-gemini-3': { + default: 'gemini-3-pro-preview', contexts: [ + { condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' }, { - condition: { useGemini3_1FlashLite: false }, - target: 'gemini-2.5-flash-lite', + 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: { useGemini3_5Flash: true }, target: 'gemini-3.5-flash' }, { - 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', }, ], }, @@ -566,7 +593,11 @@ 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: { requestedModels: ['gemini-2.5-pro', 'auto-gemini-2.5'] }, target: 'gemini-2.5-pro', }, { diff --git a/packages/core/src/config/models.test.ts b/packages/core/src/config/models.test.ts index b791b3dbfc..fe6799e7c8 100644 --- a/packages/core/src/config/models.test.ts +++ b/packages/core/src/config/models.test.ts @@ -14,9 +14,11 @@ import { supportsModernFeatures, isAutoModel, getDisplayString, + getAutoModelDescription, DEFAULT_GEMINI_MODEL, PREVIEW_GEMINI_MODEL, DEFAULT_GEMINI_FLASH_MODEL, + DEFAULT_GEMINI_3_5_FLASH_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, supportsMultimodalFunctionResponse, GEMINI_MODEL_ALIAS_PRO, @@ -28,8 +30,7 @@ import { DEFAULT_GEMINI_MODEL_AUTO, isActiveModel, PREVIEW_GEMINI_3_1_MODEL, - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, - GEMINI_3_1_FLASH_LITE_MODEL, + PREVIEW_GEMINI_FLASH_LITE_MODEL, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, isPreviewModel, isProModel, @@ -67,22 +68,14 @@ describe('Dynamic Configuration Parity', () => { const flagCombos = [ { useGemini3_1: false, - useGemini3_1FlashLite: false, useCustomToolModel: false, }, { useGemini3_1: true, - useGemini3_1FlashLite: false, useCustomToolModel: false, }, { useGemini3_1: true, - useGemini3_1FlashLite: true, - useCustomToolModel: false, - }, - { - useGemini3_1: true, - useGemini3_1FlashLite: true, useCustomToolModel: true, }, ]; @@ -105,7 +98,6 @@ describe('Dynamic Configuration Parity', () => { const legacy = resolveModel( model, flags.useGemini3_1, - flags.useGemini3_1FlashLite, flags.useCustomToolModel, hasAccess, mockLegacyConfig, @@ -113,7 +105,6 @@ describe('Dynamic Configuration Parity', () => { const dynamic = resolveModel( model, flags.useGemini3_1, - flags.useGemini3_1FlashLite, flags.useCustomToolModel, hasAccess, mockDynamicConfig, @@ -152,7 +143,6 @@ describe('Dynamic Configuration Parity', () => { anchor, tier, flags.useGemini3_1, - flags.useGemini3_1FlashLite, flags.useCustomToolModel, hasAccess, mockLegacyConfig, @@ -161,7 +151,6 @@ describe('Dynamic Configuration Parity', () => { anchor, tier, flags.useGemini3_1, - flags.useGemini3_1FlashLite, flags.useCustomToolModel, hasAccess, mockDynamicConfig, @@ -229,12 +218,29 @@ describe('Dynamic Configuration Parity', () => { }); describe('isPreviewModel', () => { - it('should return true for preview models', () => { - expect(isPreviewModel(PREVIEW_GEMINI_MODEL)).toBe(true); - expect(isPreviewModel(PREVIEW_GEMINI_3_1_MODEL)).toBe(true); - expect(isPreviewModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL)).toBe(true); - expect(isPreviewModel(PREVIEW_GEMINI_FLASH_MODEL)).toBe(true); + const PREVIEW_MODELS = [ + PREVIEW_GEMINI_MODEL, + PREVIEW_GEMINI_3_1_MODEL, + PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, + PREVIEW_GEMINI_FLASH_MODEL, + PREVIEW_GEMINI_FLASH_LITE_MODEL, + ]; + + it('should return true for active preview models', () => { + for (const model of PREVIEW_MODELS) { + if (model !== 'none') { + expect(isPreviewModel(model)).toBe(true); + } + } expect(isPreviewModel(PREVIEW_GEMINI_MODEL_AUTO)).toBe(true); + expect(isPreviewModel(GEMINI_MODEL_ALIAS_AUTO)).toBe(true); + }); + + it('should return false if a preview model is retired (set to none)', () => { + const retiredModels = PREVIEW_MODELS.filter((m) => m === 'none'); + for (const model of retiredModels) { + expect(isPreviewModel(model)).toBe(false); + } }); it('should return false for non-preview models', () => { @@ -358,15 +364,9 @@ describe('getDisplayString', () => { ); }); - it('should return PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL for PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL', () => { - expect(getDisplayString(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL)).toBe( - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, - ); - }); - - it('should return GEMINI_3_1_FLASH_LITE_MODEL for GEMINI_3_1_FLASH_LITE_MODEL', () => { - expect(getDisplayString(GEMINI_3_1_FLASH_LITE_MODEL)).toBe( - GEMINI_3_1_FLASH_LITE_MODEL, + it('should return PREVIEW_GEMINI_FLASH_LITE_MODEL for PREVIEW_GEMINI_FLASH_LITE_MODEL', () => { + expect(getDisplayString(PREVIEW_GEMINI_FLASH_LITE_MODEL)).toBe( + PREVIEW_GEMINI_FLASH_LITE_MODEL, ); }); @@ -411,7 +411,7 @@ describe('resolveModel', () => { }); it('should return Gemini 3.1 Pro Custom Tools when auto-gemini-3 is requested, useGemini3_1 is true, and useCustomToolModel is true', () => { - const model = resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, false, true); + const model = resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, true); expect(model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL); }); @@ -425,9 +425,9 @@ describe('resolveModel', () => { expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL); }); - it('should return the Preview Flash-Lite model when flash-lite is requested and useGemini3_1FlashLite is true', () => { - const model = resolveModel(GEMINI_MODEL_ALIAS_FLASH_LITE, false, true); - expect(model).toBe(GEMINI_3_1_FLASH_LITE_MODEL); + it('should return the Flash-Lite model when flash-lite is requested', () => { + const model = resolveModel(GEMINI_MODEL_ALIAS_FLASH_LITE, false); + expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL); }); it('should return the requested model as-is for explicit specific models', () => { @@ -458,45 +458,39 @@ describe('resolveModel', () => { describe('hasAccessToPreview logic', () => { it('should return default model when access to preview is false and preview model is requested', () => { - expect( - resolveModel(PREVIEW_GEMINI_MODEL, false, false, false, false), - ).toBe(DEFAULT_GEMINI_MODEL); + expect(resolveModel(PREVIEW_GEMINI_MODEL, false, false, false)).toBe( + DEFAULT_GEMINI_MODEL, + ); }); it('should return default flash model when access to preview is false and preview flash model is requested', () => { expect( - resolveModel(PREVIEW_GEMINI_FLASH_MODEL, false, false, false, false), + resolveModel(PREVIEW_GEMINI_FLASH_MODEL, false, false, false), ).toBe(DEFAULT_GEMINI_FLASH_MODEL); }); it('should return default flash lite model when access to preview is false and preview flash lite model is requested', () => { expect( - resolveModel( - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, - false, - false, - false, - false, - ), + resolveModel(PREVIEW_GEMINI_FLASH_LITE_MODEL, false, false, false), ).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL); }); it('should return default model when access to preview is false and auto-gemini-3 is requested', () => { - expect( - resolveModel(PREVIEW_GEMINI_MODEL_AUTO, false, false, false, false), - ).toBe(DEFAULT_GEMINI_MODEL); + expect(resolveModel(PREVIEW_GEMINI_MODEL_AUTO, false, false, false)).toBe( + DEFAULT_GEMINI_MODEL, + ); }); it('should return default model when access to preview is false and Gemini 3.1 is requested', () => { - expect( - resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, false, false, false), - ).toBe(DEFAULT_GEMINI_MODEL); + expect(resolveModel(PREVIEW_GEMINI_MODEL_AUTO, true, false, false)).toBe( + DEFAULT_GEMINI_MODEL, + ); }); it('should still return default model when access to preview is false and auto-gemini-2.5 is requested', () => { - expect( - resolveModel(DEFAULT_GEMINI_MODEL_AUTO, false, false, false, false), - ).toBe(DEFAULT_GEMINI_MODEL); + expect(resolveModel(DEFAULT_GEMINI_MODEL_AUTO, false, false, false)).toBe( + DEFAULT_GEMINI_MODEL, + ); }); }); }); @@ -588,7 +582,6 @@ describe('resolveClassifierModel', () => { PREVIEW_GEMINI_MODEL_AUTO, GEMINI_MODEL_ALIAS_PRO, true, - false, true, ), ).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL); @@ -605,17 +598,14 @@ describe('isActiveModel', () => { it('should return true for Gemma 4 models when experimentalGemma is not provided (defaults to true)', () => { expect(isActiveModel(GEMMA_4_31B_IT_MODEL)).toBe(true); expect(isActiveModel(GEMMA_4_26B_A4B_IT_MODEL)).toBe(true); - expect(isActiveModel(GEMMA_4_31B_IT_MODEL, false, false, false, true)).toBe( + expect(isActiveModel(GEMMA_4_31B_IT_MODEL, false, false, true)).toBe(true); + expect(isActiveModel(GEMMA_4_26B_A4B_IT_MODEL, false, false, true)).toBe( true, ); - expect( - isActiveModel(GEMMA_4_26B_A4B_IT_MODEL, false, false, false, true), - ).toBe(true); }); it('should return false for Gemini 3.1 models when Gemini 3.1 is not launched', () => { expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL)).toBe(false); - expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL)).toBe(false); }); it('should return true for unknown models and aliases', () => { @@ -631,40 +621,48 @@ describe('isActiveModel', () => { expect(isActiveModel(DEFAULT_GEMINI_MODEL, true)).toBe(true); }); - it('should return true for PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL and GEMINI_3_1_FLASH_LITE_MODEL only when useGemini3_1FlashLite is true', () => { - expect( - isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, false, true), - ).toBe(true); - expect(isActiveModel(GEMINI_3_1_FLASH_LITE_MODEL, false, true)).toBe(true); - expect(isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, true, true)).toBe( + it('should handle PREVIEW_GEMINI_FLASH_LITE_MODEL activity correctly based on retirement status', () => { + if (PREVIEW_GEMINI_FLASH_LITE_MODEL === 'none') { + expect(isActiveModel(PREVIEW_GEMINI_FLASH_LITE_MODEL, false, true)).toBe( + false, + ); + expect(isActiveModel(PREVIEW_GEMINI_FLASH_LITE_MODEL, true, true)).toBe( + false, + ); + } else { + expect(isActiveModel(PREVIEW_GEMINI_FLASH_LITE_MODEL, false, true)).toBe( + true, + ); + expect(isActiveModel(PREVIEW_GEMINI_FLASH_LITE_MODEL, true, true)).toBe( + true, + ); + } + expect(isActiveModel(DEFAULT_GEMINI_FLASH_LITE_MODEL, false, false)).toBe( + true, + ); + expect(isActiveModel(DEFAULT_GEMINI_FLASH_LITE_MODEL, true, true)).toBe( + true, + ); + expect(isActiveModel(DEFAULT_GEMINI_FLASH_LITE_MODEL, true, false)).toBe( true, ); - expect(isActiveModel(GEMINI_3_1_FLASH_LITE_MODEL, true, true)).toBe(true); - expect( - isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, true, false), - ).toBe(false); - expect(isActiveModel(GEMINI_3_1_FLASH_LITE_MODEL, true, false)).toBe(false); }); it('should correctly filter Gemini 3.1 models based on useCustomToolModel when useGemini3_1 is true', () => { // When custom tools are preferred, standard 3.1 should be inactive - expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, false, true)).toBe( - false, - ); + expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, true)).toBe(false); expect( - isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, false, true), + isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, true), ).toBe(true); // When custom tools are NOT preferred, custom tools 3.1 should be inactive - expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, false, false)).toBe( - true, - ); + expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, true, false)).toBe(true); expect( - isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, false, false), + isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, true, false), ).toBe(false); }); - it('should return false for Gemini 3.1 models when useGemini3_1 and useGemini3_1FlashLite are false', () => { + it('should return false for Gemini 3.1 preview models when useGemini3_1 is false', () => { expect(isActiveModel(PREVIEW_GEMINI_3_1_MODEL, false, false, true)).toBe( false, ); @@ -677,11 +675,378 @@ describe('isActiveModel', () => { expect( isActiveModel(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, false, false, false), ).toBe(false); - expect( - isActiveModel(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, false, false), - ).toBe(false); - expect(isActiveModel(GEMINI_3_1_FLASH_LITE_MODEL, false, false)).toBe( - false, + if (PREVIEW_GEMINI_FLASH_LITE_MODEL !== 'none') { + expect(isActiveModel(PREVIEW_GEMINI_FLASH_LITE_MODEL, false, false)).toBe( + false, + ); + } + expect(isActiveModel(DEFAULT_GEMINI_FLASH_LITE_MODEL, false, false)).toBe( + true, ); }); }); + +describe('Gemini 3.1 Config Resolution', () => { + it('PREVIEW_GEMINI_3_1_MODEL should resolve to chat-base-3 config (including thinkingLevel)', () => { + const resolved = modelConfigService.getResolvedConfig({ + model: PREVIEW_GEMINI_3_1_MODEL, + isChatModel: true, + }); + expect( + resolved.generateContentConfig?.thinkingConfig?.thinkingLevel, + ).toBeDefined(); + }); + + it('PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL should resolve to chat-base-3 config (including thinkingLevel)', () => { + const resolved = modelConfigService.getResolvedConfig({ + model: PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, + isChatModel: true, + }); + expect( + resolved.generateContentConfig?.thinkingConfig?.thinkingLevel, + ).toBeDefined(); + }); + + it('PREVIEW_GEMINI_FLASH_LITE_MODEL should resolve to appropriate config based on retirement status', () => { + if (PREVIEW_GEMINI_FLASH_LITE_MODEL === 'none') { + // If none, it falls back to chat-base which may not have thinkingLevel + const resolved = modelConfigService.getResolvedConfig({ + model: PREVIEW_GEMINI_FLASH_LITE_MODEL, + isChatModel: true, + }); + expect(resolved.model).toBe(PREVIEW_GEMINI_FLASH_LITE_MODEL); + } else { + const resolved = modelConfigService.getResolvedConfig({ + model: PREVIEW_GEMINI_FLASH_LITE_MODEL, + isChatModel: true, + }); + expect( + resolved.generateContentConfig?.thinkingConfig?.thinkingLevel, + ).toBeDefined(); + } + }); +}); + +describe('getAutoModelDescription', () => { + it('should return Gemini 2.5 description when hasAccessToPreview is false', () => { + const desc = getAutoModelDescription(false, false); + expect(desc).toContain('gemini-2.5-pro'); + expect(desc).toContain('gemini-2.5-flash'); + }); + + it('should return Gemini 3.0 description when hasAccessToPreview is true', () => { + const desc = getAutoModelDescription(true, false); + expect(desc).toContain('gemini-3-pro-preview'); + expect(desc).toContain('gemini-3-flash-preview'); + }); + + it('should return Gemini 3.1 description when hasAccessToPreview and useGemini3_1 are true', () => { + const desc = getAutoModelDescription(true, true); + expect(desc).toContain('gemini-3.1-pro-preview'); + expect(desc).toContain('gemini-3-flash-preview'); + }); + + it('should return Gemini 3.5 Flash description when hasAccessToPreview and useGemini3_5Flash are true', () => { + const desc = getAutoModelDescription(true, true, true); + expect(desc).toContain('gemini-3.1-pro-preview'); + expect(desc).toContain(DEFAULT_GEMINI_3_5_FLASH_MODEL); + }); +}); + +describe('resolveModel Gemini 3.5 Flash GA', () => { + it('should resolve all but preview flash models to DEFAULT_GEMINI_FLASH_MODEL when useGemini3_5Flash is true (legacy)', () => { + expect( + resolveModel( + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + undefined, + true, + ), + ).toBe(DEFAULT_GEMINI_FLASH_MODEL); + expect( + resolveModel( + DEFAULT_GEMINI_FLASH_MODEL, + false, + false, + true, + undefined, + true, + ), + ).toBe(DEFAULT_GEMINI_FLASH_MODEL); + expect( + resolveModel( + PREVIEW_GEMINI_FLASH_MODEL, + false, + false, + true, + undefined, + true, + ), + ).toBe(PREVIEW_GEMINI_FLASH_MODEL); + }); + + it('should resolve all but preview flash models to gemini-3.5-flash when useGemini3_5Flash is true (dynamic)', () => { + const mockDynamicConfig = { + getExperimentalDynamicModelConfiguration: () => true, + modelConfigService, + } as unknown as Config; + + expect( + resolveModel( + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + mockDynamicConfig, + true, + ), + ).toBe('gemini-3.5-flash'); + expect( + resolveModel( + DEFAULT_GEMINI_FLASH_MODEL, + false, + false, + true, + mockDynamicConfig, + true, + ), + ).toBe('gemini-3.5-flash'); + expect( + resolveModel( + PREVIEW_GEMINI_FLASH_MODEL, + false, + false, + true, + mockDynamicConfig, + true, + ), + ).toBe(PREVIEW_GEMINI_FLASH_MODEL); + }); + + it('should NOT resolve flash models to DEFAULT_GEMINI_FLASH_MODEL when useGemini3_5Flash is false', () => { + expect( + resolveModel( + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + undefined, + false, + ), + ).toBe(PREVIEW_GEMINI_FLASH_MODEL); + expect( + resolveModel( + DEFAULT_GEMINI_FLASH_MODEL, + false, + false, + true, + undefined, + false, + ), + ).toBe(DEFAULT_GEMINI_FLASH_MODEL); + expect( + resolveModel( + PREVIEW_GEMINI_FLASH_MODEL, + false, + false, + true, + undefined, + false, + ), + ).toBe(PREVIEW_GEMINI_FLASH_MODEL); + }); + + it('should resolve to DEFAULT_GEMINI_FLASH_MODEL when GA is false AND preview access is false (dynamic)', () => { + const mockDynamicConfig = { + getExperimentalDynamicModelConfiguration: () => true, + modelConfigService, + } as unknown as Config; + + expect( + resolveModel( + DEFAULT_GEMINI_FLASH_MODEL, + false, + false, + false, // No preview access + mockDynamicConfig, + false, // GA false + ), + ).toBe('gemini-2.5-flash'); + }); + + it('should resolve auto to DEFAULT_GEMINI_FLASH_MODEL when useGemini3_5Flash is true and classifier selects flash', () => { + expect( + resolveClassifierModel( + GEMINI_MODEL_ALIAS_AUTO, + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + undefined, + true, + ), + ).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); + + it('should resolve auto to gemini-3.5-flash when useGemini3_5Flash is true and classifier selects flash (dynamic)', () => { + const mockDynamicConfig = { + getExperimentalDynamicModelConfiguration: () => true, + modelConfigService, + } as unknown as Config; + + expect( + resolveClassifierModel( + GEMINI_MODEL_ALIAS_AUTO, + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + mockDynamicConfig, + true, + ), + ).toBe('gemini-3.5-flash'); + }); + + describe('Flash model promotion and manual override routing logic', () => { + it('should resolve flash alias to DEFAULT_GEMINI_FLASH_MODEL when useGemini3_5Flash is true (static)', () => { + expect( + resolveModel( + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + undefined, + true, + ), + ).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); + + it('should resolve flash alias to gemini-3.5-flash when useGemini3_5Flash is true (dynamic)', () => { + const mockDynamicConfig = { + getExperimentalDynamicModelConfiguration: () => true, + modelConfigService, + } as unknown as Config; + + expect( + resolveModel( + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + mockDynamicConfig, + true, + ), + ).toBe('gemini-3.5-flash'); + }); + + it('should resolve manual selection of gemini-3-flash-preview to gemini-3-flash-preview when useGemini3_5Flash is true and has preview access (static)', () => { + expect( + resolveModel( + PREVIEW_GEMINI_FLASH_MODEL, + false, + false, + true, + undefined, + true, + ), + ).toBe('gemini-3-flash-preview'); + }); + + it('should resolve manual selection of gemini-3-flash-preview to gemini-3-flash-preview when useGemini3_5Flash is true and has preview access (dynamic)', () => { + const mockDynamicConfig = { + getExperimentalDynamicModelConfiguration: () => true, + modelConfigService, + } as unknown as Config; + + expect( + resolveModel( + PREVIEW_GEMINI_FLASH_MODEL, + false, + false, + true, + mockDynamicConfig, + true, + ), + ).toBe('gemini-3-flash-preview'); + }); + + it('should resolve manual selection of gemini-3-flash-preview to DEFAULT_GEMINI_FLASH_MODEL when useGemini3_5Flash is true but lacks preview access (static)', () => { + expect( + resolveModel( + PREVIEW_GEMINI_FLASH_MODEL, + false, + false, + false, + undefined, + true, + ), + ).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); + + it('should resolve manual selection of gemini-3-flash-preview to gemini-3.5-flash when useGemini3_5Flash is true but lacks preview access (dynamic)', () => { + const mockDynamicConfig = { + getExperimentalDynamicModelConfiguration: () => true, + modelConfigService, + } as unknown as Config; + + expect( + resolveModel( + PREVIEW_GEMINI_FLASH_MODEL, + false, + false, + false, + mockDynamicConfig, + true, + ), + ).toBe('gemini-3.5-flash'); + }); + + it('should resolve classifier-selected flash alias to DEFAULT_GEMINI_FLASH_MODEL when useGemini3_5Flash is true (static)', () => { + expect( + resolveClassifierModel( + GEMINI_MODEL_ALIAS_AUTO, + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + undefined, + true, + ), + ).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); + + it('should resolve classifier-selected flash alias to gemini-3.5-flash when useGemini3_5Flash is true (dynamic)', () => { + const mockDynamicConfig = { + getExperimentalDynamicModelConfiguration: () => true, + modelConfigService, + } as unknown as Config; + + expect( + resolveClassifierModel( + GEMINI_MODEL_ALIAS_AUTO, + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + true, + mockDynamicConfig, + true, + ), + ).toBe('gemini-3.5-flash'); + }); + + it('should resolve auto to PREVIEW_GEMINI_MODEL when useGemini3_5Flash is true and has preview access', () => { + expect( + resolveModel( + GEMINI_MODEL_ALIAS_AUTO, + false, + false, + true, // hasAccessToPreview + undefined, + true, // useGemini3_5Flash + ), + ).toBe(PREVIEW_GEMINI_MODEL); + }); + }); +}); diff --git a/packages/core/src/config/models.ts b/packages/core/src/config/models.ts index dfc6569dc1..ca872f1bcb 100644 --- a/packages/core/src/config/models.ts +++ b/packages/core/src/config/models.ts @@ -6,10 +6,11 @@ export interface ModelResolutionContext { useGemini3_1?: boolean; - useGemini3_1FlashLite?: boolean; + useGemini3_5Flash?: boolean; useCustomTools?: boolean; hasAccessToPreview?: boolean; requestedModel?: string; + releaseChannel?: string; } /** @@ -48,19 +49,52 @@ export interface IModelConfigService { export interface ModelCapabilityContext { readonly modelConfigService: IModelConfigService; getExperimentalDynamicModelConfiguration(): boolean; + getPreviewGeminiFlashModel?(): string; + getDefaultGeminiFlashModel?(): string; } 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_CUSTOM_TOOLS_MODEL = 'gemini-3.1-pro-preview-customtools'; +// TODO: set to none and const once the experiment for 3_5 flash rollut can be +// cleaned up. export const PREVIEW_GEMINI_FLASH_MODEL = 'gemini-3-flash-preview'; -export const PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL = - 'gemini-3.1-flash-lite-preview'; -export const GEMINI_3_1_FLASH_LITE_MODEL = 'gemini-3.1-flash-lite'; export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro'; +// TODO: Set to const and update to 'gemini-3.5-flash' once the experiment for +// 3_5 flash rollut can be cleaned up. +// This is set to either the same as the DEFAULT_GEMINI_3_5_FLASH_MODEL const +// OR the SECONDARY_GEMINI_3_5_FLASH_MODEL depending on which is needed for +// the user's backend as determined by hasGemini35FlashGAAccess in +// packages/core/src/config/config.ts export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash'; -export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite'; +export const DEFAULT_GEMINI_3_5_FLASH_MODEL = 'gemini-3.5-flash'; +// This is resolved to 3.5 flash in backends where it is used, +// however those backends do not expect to see the string gemini-3.5-flash +// so we need to provide this model as an alternative name in certain instances. +export const SECONDARY_GEMINI_3_5_FLASH_MODEL = 'gemini-3-flash'; + +export function getPreviewGeminiFlashModel( + config?: ModelCapabilityContext, +): string { + if (config?.getPreviewGeminiFlashModel) { + return config.getPreviewGeminiFlashModel(); + } + return PREVIEW_GEMINI_FLASH_MODEL; +} + +export function getDefaultGeminiFlashModel( + config?: ModelCapabilityContext, +): string { + if (config?.getDefaultGeminiFlashModel) { + return config.getDefaultGeminiFlashModel(); + } + return DEFAULT_GEMINI_FLASH_MODEL; +} + +export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-3.1-flash-lite'; +/** @deprecated Gemini 3.1 Flash Lite is now GA. Use DEFAULT_GEMINI_FLASH_LITE_MODEL. */ +export const PREVIEW_GEMINI_FLASH_LITE_MODEL = 'none'; export const GEMMA_4_31B_IT_MODEL = 'gemma-4-31b-it'; export const GEMMA_4_26B_A4B_IT_MODEL = 'gemma-4-26b-a4b-it'; @@ -70,17 +104,20 @@ export const VALID_GEMINI_MODELS = new Set([ PREVIEW_GEMINI_3_1_MODEL, PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL, PREVIEW_GEMINI_FLASH_MODEL, - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, - GEMINI_3_1_FLASH_LITE_MODEL, + PREVIEW_GEMINI_FLASH_LITE_MODEL, DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_FLASH_MODEL, + DEFAULT_GEMINI_3_5_FLASH_MODEL, + SECONDARY_GEMINI_3_5_FLASH_MODEL, DEFAULT_GEMINI_FLASH_LITE_MODEL, GEMMA_4_31B_IT_MODEL, 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. @@ -94,22 +131,41 @@ 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( + hasAccessToPreview: boolean, + useGemini3_1: boolean = false, + useGemini3_5Flash: boolean = false, +) { + const proModel = hasAccessToPreview + ? useGemini3_1 + ? PREVIEW_GEMINI_3_1_MODEL + : PREVIEW_GEMINI_MODEL + : DEFAULT_GEMINI_MODEL; + const flashModel = hasAccessToPreview + ? useGemini3_5Flash + ? DEFAULT_GEMINI_3_5_FLASH_MODEL + : PREVIEW_GEMINI_FLASH_MODEL + : DEFAULT_GEMINI_FLASH_MODEL; + return `Let Gemini CLI decide the best model for the task: ${getDisplayString(proModel)}, ${getDisplayString(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. * @param useGemini3_1 Whether to use Gemini 3.1 Pro Preview for auto/pro aliases. + * @param useGemini3_5Flash Whether to use Gemini 3.5 Flash GA. * @param hasAccessToPreview Whether the user has access to preview models. * @returns The resolved concrete model name. */ export function resolveModel( requestedModel: string, useGemini3_1: boolean = false, - useGemini3_1FlashLite: boolean = false, useCustomToolModel: boolean = false, hasAccessToPreview: boolean = true, config?: ModelCapabilityContext, + useGemini3_5Flash: boolean = false, ): string { // Defensive check against non-string inputs at runtime const normalizedModel = Array.isArray(requestedModel) @@ -121,9 +177,9 @@ export function resolveModel( if (config?.getExperimentalDynamicModelConfiguration?.() === true) { const resolved = config.modelConfigService.resolveModelId(normalizedModel, { useGemini3_1, - useGemini3_1FlashLite, useCustomTools: useCustomToolModel, hasAccessToPreview, + useGemini3_5Flash, }); if (!hasAccessToPreview && isPreviewModel(resolved, config)) { @@ -142,10 +198,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 (!hasAccessToPreview) { + 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 @@ -160,13 +222,13 @@ export function resolveModel( break; } case GEMINI_MODEL_ALIAS_FLASH: { - resolved = PREVIEW_GEMINI_FLASH_MODEL; + resolved = useGemini3_5Flash + ? getDefaultGeminiFlashModel(config) + : getPreviewGeminiFlashModel(config); break; } case GEMINI_MODEL_ALIAS_FLASH_LITE: { - resolved = useGemini3_1FlashLite - ? GEMINI_3_1_FLASH_LITE_MODEL - : DEFAULT_GEMINI_FLASH_LITE_MODEL; + resolved = DEFAULT_GEMINI_FLASH_LITE_MODEL; break; } default: { @@ -175,13 +237,23 @@ export function resolveModel( } } + if (resolved === 'none') { + return DEFAULT_GEMINI_FLASH_LITE_MODEL; + } + + if ( + useGemini3_5Flash && + isFlashModel(resolved) && + normalizedModel !== getPreviewGeminiFlashModel(config) + ) { + return getDefaultGeminiFlashModel(config); + } + if (!hasAccessToPreview && isPreviewModel(resolved)) { // Downgrade to stable models if user lacks preview access. switch (resolved) { - case PREVIEW_GEMINI_FLASH_MODEL: - return DEFAULT_GEMINI_FLASH_MODEL; - case PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL: - return DEFAULT_GEMINI_FLASH_LITE_MODEL; + case getPreviewGeminiFlashModel(config): + return getDefaultGeminiFlashModel(config); case PREVIEW_GEMINI_MODEL: case PREVIEW_GEMINI_3_1_MODEL: case PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL: @@ -192,7 +264,7 @@ export function resolveModel( return DEFAULT_GEMINI_FLASH_LITE_MODEL; } if (resolved.includes('flash')) { - return DEFAULT_GEMINI_FLASH_MODEL; + return getDefaultGeminiFlashModel(config); } return DEFAULT_GEMINI_MODEL; } @@ -201,10 +273,21 @@ export function resolveModel( return resolved; } +function isFlashModel(model: string): boolean { + return ( + model === DEFAULT_GEMINI_FLASH_MODEL || + model === PREVIEW_GEMINI_FLASH_MODEL || + model === DEFAULT_GEMINI_3_5_FLASH_MODEL || + model === SECONDARY_GEMINI_3_5_FLASH_MODEL || + model === 'flash' || + model.endsWith('flash') + ); +} + /** * 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. @@ -215,10 +298,10 @@ export function resolveClassifierModel( requestedModel: string, modelAlias: string, useGemini3_1: boolean = false, - useGemini3_1FlashLite: boolean = false, useCustomToolModel: boolean = false, hasAccessToPreview: boolean = true, config?: ModelCapabilityContext, + useGemini3_5Flash: boolean = false, ): string { if (config?.getExperimentalDynamicModelConfiguration?.() === true) { return config.modelConfigService.resolveClassifierModelId( @@ -226,9 +309,9 @@ export function resolveClassifierModel( requestedModel, { useGemini3_1, - useGemini3_1FlashLite, useCustomTools: useCustomToolModel, hasAccessToPreview, + useGemini3_5Flash, }, ); } @@ -238,21 +321,36 @@ export function resolveClassifierModel( requestedModel === DEFAULT_GEMINI_MODEL_AUTO || requestedModel === DEFAULT_GEMINI_MODEL ) { - return DEFAULT_GEMINI_FLASH_MODEL; + return getDefaultGeminiFlashModel(config); } if ( requestedModel === PREVIEW_GEMINI_MODEL_AUTO || - requestedModel === PREVIEW_GEMINI_MODEL + requestedModel === PREVIEW_GEMINI_MODEL || + requestedModel === GEMINI_MODEL_ALIAS_AUTO ) { - return PREVIEW_GEMINI_FLASH_MODEL; + if (useGemini3_5Flash) { + return getDefaultGeminiFlashModel(config); + } + return hasAccessToPreview + ? getPreviewGeminiFlashModel(config) + : getDefaultGeminiFlashModel(config); } - return resolveModel(GEMINI_MODEL_ALIAS_FLASH); + return resolveModel( + GEMINI_MODEL_ALIAS_FLASH, + false, + false, + hasAccessToPreview, + config, + useGemini3_5Flash, + ); } return resolveModel( requestedModel, useGemini3_1, - useGemini3_1FlashLite, useCustomToolModel, + hasAccessToPreview, + config, + useGemini3_5Flash, ); } @@ -268,6 +366,10 @@ export function getDisplayString( } switch (model) { + case 'gemini-3-flash': + return DEFAULT_GEMINI_3_5_FLASH_MODEL; + case GEMINI_MODEL_ALIAS_AUTO: + return 'Auto'; case PREVIEW_GEMINI_MODEL_AUTO: return 'Auto (Gemini 3)'; case DEFAULT_GEMINI_MODEL_AUTO: @@ -279,13 +381,11 @@ export function getDisplayString( case GEMINI_MODEL_ALIAS_PRO: return PREVIEW_GEMINI_MODEL; case GEMINI_MODEL_ALIAS_FLASH: - return PREVIEW_GEMINI_FLASH_MODEL; + return getPreviewGeminiFlashModel(config); case PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL: return PREVIEW_GEMINI_3_1_MODEL; - case PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL: - return PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL; - case GEMINI_3_1_FLASH_LITE_MODEL: - return GEMINI_3_1_FLASH_LITE_MODEL; + case PREVIEW_GEMINI_FLASH_LITE_MODEL: + return PREVIEW_GEMINI_FLASH_LITE_MODEL; default: return model; } @@ -302,6 +402,9 @@ export function isPreviewModel( model: string, config?: ModelCapabilityContext, ): boolean { + if (model === 'none') { + return false; + } if (config?.getExperimentalDynamicModelConfiguration?.() === true) { return ( config.modelConfigService.getModelDefinition(model)?.isPreview === true @@ -315,7 +418,7 @@ export function isPreviewModel( model === PREVIEW_GEMINI_FLASH_MODEL || model === PREVIEW_GEMINI_MODEL_AUTO || model === GEMINI_MODEL_ALIAS_AUTO || - model === PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL + model === PREVIEW_GEMINI_FLASH_LITE_MODEL ); } @@ -349,7 +452,7 @@ export function isGemini3Model( ): boolean { if (config?.getExperimentalDynamicModelConfiguration?.() === true) { // Legacy behavior resolves the model first. - const resolved = resolveModel(model, false, false, false, true, config); + const resolved = resolveModel(model, false, false, true, config); return ( config.modelConfigService.getModelDefinition(resolved)?.family === 'gemini-3' @@ -384,7 +487,7 @@ export function isCustomModel( config?: ModelCapabilityContext, ): boolean { if (config?.getExperimentalDynamicModelConfiguration?.() === true) { - const resolved = resolveModel(model, false, false, false, true, config); + const resolved = resolveModel(model, false, false, true, config); return ( config.modelConfigService.getModelDefinition(resolved)?.tier === 'custom' || !resolved.startsWith('gemini-') @@ -457,21 +560,17 @@ export function supportsMultimodalFunctionResponse( export function isActiveModel( model: string, useGemini3_1: boolean = false, - useGemini3_1FlashLite: boolean = false, useCustomToolModel: boolean = false, experimentalGemma: boolean = true, ): boolean { - if (!VALID_GEMINI_MODELS.has(model)) { + if (!VALID_GEMINI_MODELS.has(model) || model === 'none') { return false; } if (model === GEMMA_4_31B_IT_MODEL || model === GEMMA_4_26B_A4B_IT_MODEL) { return experimentalGemma; } - if ( - model === PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL || - model === GEMINI_3_1_FLASH_LITE_MODEL - ) { - return useGemini3_1FlashLite; + if (model === PREVIEW_GEMINI_FLASH_LITE_MODEL) { + return false; } if (useGemini3_1) { if (model === PREVIEW_GEMINI_MODEL) { @@ -489,3 +588,7 @@ export function isActiveModel( ); } } + +export const CCPA_AI_MODEL_MAPPINGS: Record = { + [DEFAULT_GEMINI_3_5_FLASH_MODEL]: SECONDARY_GEMINI_3_5_FLASH_MODEL, +}; diff --git a/packages/core/src/context/chatCompressionService.ts b/packages/core/src/context/chatCompressionService.ts index 8fcef5b495..b60e96064b 100644 --- a/packages/core/src/context/chatCompressionService.ts +++ b/packages/core/src/context/chatCompressionService.ts @@ -27,11 +27,10 @@ import { DEFAULT_GEMINI_FLASH_LITE_MODEL, DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_MODEL, - GEMINI_3_1_FLASH_LITE_MODEL, PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_FLASH_MODEL, PREVIEW_GEMINI_3_1_MODEL, - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, + PREVIEW_GEMINI_FLASH_LITE_MODEL, } from '../config/models.js'; import { PreCompressTrigger } from '../hooks/types.js'; @@ -107,15 +106,16 @@ export function modelStringToModelConfigAlias(model: string): string { return 'chat-compression-3-pro'; case PREVIEW_GEMINI_FLASH_MODEL: return 'chat-compression-3-flash'; - case PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL: - case GEMINI_3_1_FLASH_LITE_MODEL: + case PREVIEW_GEMINI_FLASH_LITE_MODEL: + // fallthrough + case DEFAULT_GEMINI_FLASH_LITE_MODEL: return 'chat-compression-3.1-flash-lite'; + case 'gemini-2.5-flash-lite': + return 'chat-compression-2.5-flash-lite'; case DEFAULT_GEMINI_MODEL: return 'chat-compression-2.5-pro'; case DEFAULT_GEMINI_FLASH_MODEL: return 'chat-compression-2.5-flash'; - case DEFAULT_GEMINI_FLASH_LITE_MODEL: - return 'chat-compression-2.5-flash-lite'; default: return 'chat-compression-default'; } diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 302b89d7f0..198daec45b 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -604,10 +604,10 @@ export class GeminiClient { return resolveModel( this.config.getActiveModel(), this.config.getGemini31LaunchedSync?.() ?? false, - this.config.getGemini31FlashLiteLaunchedSync?.() ?? false, false, this.config.getHasAccessToPreviewModel?.() ?? true, this.config, + this.config.hasGemini35FlashGAAccess?.() ?? false, ); } diff --git a/packages/core/src/core/contentGenerator.test.ts b/packages/core/src/core/contentGenerator.test.ts index 4efd9f65c6..75b1d7ccb3 100644 --- a/packages/core/src/core/contentGenerator.test.ts +++ b/packages/core/src/core/contentGenerator.test.ts @@ -15,10 +15,13 @@ import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js'; import { GoogleGenAI } from '@google/genai'; import type { Config } from '../config/config.js'; import { LoggingContentGenerator } from './loggingContentGenerator.js'; +import { ModelMappingContentGenerator } from './modelMappingContentGenerator.js'; +import { CCPA_AI_MODEL_MAPPINGS } from '../config/models.js'; import { loadApiKey } from './apiKeyCredentialStorage.js'; import { FakeContentGenerator } from './fakeContentGenerator.js'; import { RecordingContentGenerator } from './recordingContentGenerator.js'; import { resetVersionCache } from '../utils/version.js'; +import type { LlmRole } from '../telemetry/llmRole.js'; vi.mock('../code_assist/codeAssist.js'); vi.mock('@google/genai'); @@ -33,6 +36,14 @@ const mockConfig = { getProxy: vi.fn().mockReturnValue(undefined), getUsageStatisticsEnabled: vi.fn().mockReturnValue(true), getClientName: vi.fn().mockReturnValue(undefined), + getTelemetryLogPromptsEnabled: vi.fn().mockReturnValue(true), + getTelemetryTracesEnabled: vi.fn().mockReturnValue(true), + getSessionId: vi.fn().mockReturnValue('test-session-id'), + refreshUserQuotaIfStale: vi.fn().mockResolvedValue(undefined), + setLatestApiRequest: vi.fn(), + getContentGeneratorConfig: vi.fn().mockReturnValue({}), + isInteractive: vi.fn().mockReturnValue(false), + getExperiments: vi.fn().mockReturnValue(undefined), } as unknown as Config; describe('createContentGenerator', () => { @@ -100,7 +111,10 @@ describe('createContentGenerator', () => { ); expect(createCodeAssistContentGenerator).toHaveBeenCalled(); expect(generator).toEqual( - new LoggingContentGenerator(mockGenerator, mockConfig), + new LoggingContentGenerator( + new ModelMappingContentGenerator(mockGenerator, CCPA_AI_MODEL_MAPPINGS), + mockConfig, + ), ); }); @@ -117,7 +131,10 @@ describe('createContentGenerator', () => { ); expect(createCodeAssistContentGenerator).toHaveBeenCalled(); expect(generator).toEqual( - new LoggingContentGenerator(mockGenerator, mockConfig), + new LoggingContentGenerator( + new ModelMappingContentGenerator(mockGenerator, CCPA_AI_MODEL_MAPPINGS), + mockConfig, + ), ); }); @@ -851,6 +868,178 @@ describe('createContentGenerator', () => { ), ).rejects.toThrow('Invalid custom base URL: not-a-url'); }); + + it('should not apply model mapping for Vertex AI', async () => { + const mockModels = { + generateContent: vi.fn().mockResolvedValue({}), + }; + const mockGenerator = { + models: mockModels, + } as unknown as GoogleGenAI; + vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never); + + const generator = await createContentGenerator( + { + apiKey: 'test-api-key', + authType: AuthType.USE_VERTEX_AI, + vertexai: true, + }, + mockConfig, + ); + + await generator.generateContent( + { + model: 'gemini-3-flash', + contents: [], + }, + 'prompt-id', + 'user' as LlmRole, + ); + + expect(mockModels.generateContent).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'gemini-3-flash', + }), + 'prompt-id', + 'user', + ); + }); + + it('should not apply model mapping for Gemini API', async () => { + const mockModels = { + generateContent: vi.fn().mockResolvedValue({}), + }; + const mockGenerator = { + models: mockModels, + } as unknown as GoogleGenAI; + vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never); + + const generator = await createContentGenerator( + { + apiKey: 'test-api-key', + authType: AuthType.USE_GEMINI, + }, + mockConfig, + ); + + await generator.generateContent( + { + model: 'gemini-3-flash', + contents: [], + }, + 'prompt-id', + 'user' as LlmRole, + ); + + expect(mockModels.generateContent).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'gemini-3-flash', + }), + 'prompt-id', + 'user', + ); + }); + + it('should not apply model mapping for GATEWAY', async () => { + const mockModels = { + generateContent: vi.fn().mockResolvedValue({}), + }; + const mockGenerator = { + models: mockModels, + } as unknown as GoogleGenAI; + vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never); + + const generator = await createContentGenerator( + { + apiKey: 'test-api-key', + authType: AuthType.GATEWAY, + }, + mockConfig, + ); + + await generator.generateContent( + { + model: 'gemini-3.5-flash', + contents: [], + }, + 'prompt-id', + 'user' as LlmRole, + ); + + expect(mockModels.generateContent).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'gemini-3.5-flash', + }), + 'prompt-id', + 'user', + ); + }); + + it('should apply model mapping for LOGIN_WITH_GOOGLE', async () => { + const mockInnerGenerator = { + generateContent: vi.fn().mockResolvedValue({}), + } as unknown as ContentGenerator; + vi.mocked(createCodeAssistContentGenerator).mockResolvedValue( + mockInnerGenerator as never, + ); + + const generator = await createContentGenerator( + { + authType: AuthType.LOGIN_WITH_GOOGLE, + }, + mockConfig, + ); + + await generator.generateContent( + { + model: 'gemini-3.5-flash', + contents: [], + }, + 'prompt-id', + 'user' as LlmRole, + ); + + expect(mockInnerGenerator.generateContent).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'gemini-3-flash', + }), + 'prompt-id', + 'user', + ); + }); + + it('should apply model mapping for COMPUTE_ADC', async () => { + const mockInnerGenerator = { + generateContent: vi.fn().mockResolvedValue({}), + } as unknown as ContentGenerator; + vi.mocked(createCodeAssistContentGenerator).mockResolvedValue( + mockInnerGenerator as never, + ); + + const generator = await createContentGenerator( + { + authType: AuthType.COMPUTE_ADC, + }, + mockConfig, + ); + + await generator.generateContent( + { + model: 'gemini-3.5-flash', + contents: [], + }, + 'prompt-id', + 'user' as LlmRole, + ); + + expect(mockInnerGenerator.generateContent).toHaveBeenCalledWith( + expect.objectContaining({ + model: 'gemini-3-flash', + }), + 'prompt-id', + 'user', + ); + }); }); describe('createContentGeneratorConfig', () => { diff --git a/packages/core/src/core/contentGenerator.ts b/packages/core/src/core/contentGenerator.ts index bcee8cfef4..bc432460bd 100644 --- a/packages/core/src/core/contentGenerator.ts +++ b/packages/core/src/core/contentGenerator.ts @@ -28,6 +28,8 @@ import { determineSurface } from '../utils/surface.js'; import { RecordingContentGenerator } from './recordingContentGenerator.js'; import { getVersion, resolveModel } from '../../index.js'; import type { LlmRole } from '../telemetry/llmRole.js'; +import { ModelMappingContentGenerator } from './modelMappingContentGenerator.js'; +import { CCPA_AI_MODEL_MAPPINGS } from '../config/models.js'; /** * Interface abstracting the core functionalities for generating content and counting tokens. @@ -205,12 +207,10 @@ export async function createContentGenerator( config.authType === AuthType.USE_GEMINI || config.authType === AuthType.USE_VERTEX_AI || ((await gcConfig.getGemini31Launched?.()) ?? false), - config.authType === AuthType.USE_GEMINI || - config.authType === AuthType.USE_VERTEX_AI || - ((await gcConfig.getGemini31FlashLiteLaunched?.()) ?? false), false, gcConfig.getHasAccessToPreviewModel?.() ?? true, gcConfig, + gcConfig.hasGemini35FlashGAAccess?.() ?? false, ); const customHeadersEnv = process.env['GEMINI_CLI_CUSTOM_HEADERS'] || undefined; @@ -271,11 +271,14 @@ export async function createContentGenerator( ) { const httpOptions = { headers: baseHeaders }; return new LoggingContentGenerator( - await createCodeAssistContentGenerator( - httpOptions, - config.authType, - gcConfig, - sessionId, + new ModelMappingContentGenerator( + await createCodeAssistContentGenerator( + httpOptions, + config.authType, + gcConfig, + sessionId, + ), + CCPA_AI_MODEL_MAPPINGS, ), gcConfig, ); diff --git a/packages/core/src/core/geminiChat.test.ts b/packages/core/src/core/geminiChat.test.ts index 05a27f8bbc..267ead184c 100644 --- a/packages/core/src/core/geminiChat.test.ts +++ b/packages/core/src/core/geminiChat.test.ts @@ -157,6 +157,7 @@ describe('GeminiChat', () => { getTelemetryLogPromptsEnabled: () => true, getTelemetryTracesEnabled: () => false, getUsageStatisticsEnabled: () => true, + hasGemini35FlashGAAccess: vi.fn().mockReturnValue(false), getDebugMode: () => false, getContentGeneratorConfig: vi.fn().mockImplementation(() => ({ authType: 'oauth-personal', diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index 6a728884a5..97c5b80116 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -604,19 +604,16 @@ export class GeminiChat { const apiCall = async () => { const useGemini3_1 = (await this.context.config.getGemini31Launched?.()) ?? false; - const useGemini3_1FlashLite = - (await this.context.config.getGemini31FlashLiteLaunched?.()) ?? false; const hasAccessToPreview = this.context.config.getHasAccessToPreviewModel?.() ?? true; - // Default to the last used model (which respects arguments/availability selection) let modelToUse = resolveModel( lastModelToUse, useGemini3_1, - useGemini3_1FlashLite, false, hasAccessToPreview, this.context.config, + this.context.config.hasGemini35FlashGAAccess?.() ?? false, ); // If the active model has changed (e.g. due to a fallback updating the config), @@ -625,10 +622,10 @@ export class GeminiChat { modelToUse = resolveModel( this.context.config.getActiveModel(), useGemini3_1, - useGemini3_1FlashLite, false, hasAccessToPreview, this.context.config, + this.context.config.hasGemini35FlashGAAccess?.() ?? false, ); } @@ -688,10 +685,10 @@ export class GeminiChat { modelToUse = resolveModel( beforeModelResult.modifiedModel, useGemini3_1, - useGemini3_1FlashLite, false, hasAccessToPreview, this.context.config, + this.context.config.hasGemini35FlashGAAccess?.() ?? false, ); lastModelToUse = modelToUse; // Re-evaluate contentsToUse based on the new model's feature support diff --git a/packages/core/src/core/geminiChat_network_retry.test.ts b/packages/core/src/core/geminiChat_network_retry.test.ts index a2b2ee6e9f..5db803b2e3 100644 --- a/packages/core/src/core/geminiChat_network_retry.test.ts +++ b/packages/core/src/core/geminiChat_network_retry.test.ts @@ -98,6 +98,7 @@ describe('GeminiChat Network Retries', () => { getTelemetryLogPromptsEnabled: () => true, getTelemetryTracesEnabled: () => false, getUsageStatisticsEnabled: () => true, + hasGemini35FlashGAAccess: vi.fn().mockReturnValue(false), getDebugMode: () => false, getContentGeneratorConfig: vi.fn().mockReturnValue({ authType: 'oauth-personal', diff --git a/packages/core/src/core/modelMappingContentGenerator.test.ts b/packages/core/src/core/modelMappingContentGenerator.test.ts new file mode 100644 index 0000000000..926f55fb36 --- /dev/null +++ b/packages/core/src/core/modelMappingContentGenerator.test.ts @@ -0,0 +1,135 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi } from 'vitest'; +import { ModelMappingContentGenerator } from './modelMappingContentGenerator.js'; +import type { ContentGenerator } from './contentGenerator.js'; +import { LlmRole } from '../telemetry/llmRole.js'; +import type { GenerateContentParameters } from '@google/genai'; + +describe('ModelMappingContentGenerator', () => { + const mockMappings = { + 'gemini-3.5-flash': 'gemini-3-flash', + 'gemini-pro': 'gemini-1.5-pro', + }; + + it('delegates userTier, userTierName, and paidTier properties', () => { + const mockWrapped = { + userTier: 'free', + userTierName: 'Free Tier', + paidTier: { id: 'paid' }, + } as unknown as ContentGenerator; + + const generator = new ModelMappingContentGenerator( + mockWrapped, + mockMappings, + ); + + expect(generator.userTier).toBe('free'); + expect(generator.userTierName).toBe('Free Tier'); + expect(generator.paidTier).toEqual({ id: 'paid' }); + }); + + it('maps matching model without prefix', async () => { + const mockWrapped = { + generateContent: vi.fn().mockResolvedValue({}), + } as unknown as ContentGenerator; + + const generator = new ModelMappingContentGenerator( + mockWrapped, + mockMappings, + ); + const req = { model: 'gemini-3.5-flash', contents: [] }; + + await generator.generateContent(req, 'prompt-id', LlmRole.MAIN); + + expect(mockWrapped.generateContent).toHaveBeenCalledWith( + { model: 'gemini-3-flash', contents: [] }, + 'prompt-id', + LlmRole.MAIN, + ); + }); + + it('maps matching model with models/ prefix', async () => { + const mockWrapped = { + generateContent: vi.fn().mockResolvedValue({}), + } as unknown as ContentGenerator; + + const generator = new ModelMappingContentGenerator( + mockWrapped, + mockMappings, + ); + const req = { model: 'models/gemini-3.5-flash', contents: [] }; + + await generator.generateContent(req, 'prompt-id', LlmRole.MAIN); + + expect(mockWrapped.generateContent).toHaveBeenCalledWith( + { model: 'models/gemini-3-flash', contents: [] }, + 'prompt-id', + LlmRole.MAIN, + ); + }); + + it('leaves unmapped model unchanged', async () => { + const mockWrapped = { + generateContent: vi.fn().mockResolvedValue({}), + } as unknown as ContentGenerator; + + const generator = new ModelMappingContentGenerator( + mockWrapped, + mockMappings, + ); + const req = { model: 'unknown-model', contents: [] }; + + await generator.generateContent(req, 'prompt-id', LlmRole.MAIN); + + expect(mockWrapped.generateContent).toHaveBeenCalledWith( + { model: 'unknown-model', contents: [] }, + 'prompt-id', + LlmRole.MAIN, + ); + }); + + it('leaves model with prefix unchanged if no match after normalization', async () => { + const mockWrapped = { + generateContent: vi.fn().mockResolvedValue({}), + } as unknown as ContentGenerator; + + const generator = new ModelMappingContentGenerator( + mockWrapped, + mockMappings, + ); + const req = { model: 'models/unknown-model', contents: [] }; + + await generator.generateContent(req, 'prompt-id', LlmRole.MAIN); + + expect(mockWrapped.generateContent).toHaveBeenCalledWith( + { model: 'models/unknown-model', contents: [] }, + 'prompt-id', + LlmRole.MAIN, + ); + }); + + it('handles missing/undefined model property safely', async () => { + const mockWrapped = { + generateContent: vi.fn().mockResolvedValue({}), + } as unknown as ContentGenerator; + + const generator = new ModelMappingContentGenerator( + mockWrapped, + mockMappings, + ); + const req = { contents: [] } as unknown as GenerateContentParameters; + + await generator.generateContent(req, 'prompt-id', LlmRole.MAIN); + + expect(mockWrapped.generateContent).toHaveBeenCalledWith( + { contents: [] }, + 'prompt-id', + LlmRole.MAIN, + ); + }); +}); diff --git a/packages/core/src/core/modelMappingContentGenerator.ts b/packages/core/src/core/modelMappingContentGenerator.ts new file mode 100644 index 0000000000..ef07f614ae --- /dev/null +++ b/packages/core/src/core/modelMappingContentGenerator.ts @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + type CountTokensResponse, + type GenerateContentResponse, + type GenerateContentParameters, + type CountTokensParameters, + type EmbedContentResponse, + type EmbedContentParameters, +} from '@google/genai'; +import { type ContentGenerator } from './contentGenerator.js'; +import type { LlmRole } from '../telemetry/llmRole.js'; +import type { UserTierId, GeminiUserTier } from '../code_assist/types.js'; +import { normalizeModelId } from '../utils/modelUtils.js'; + +export class ModelMappingContentGenerator implements ContentGenerator { + constructor( + private readonly wrapped: ContentGenerator, + private readonly mappings: Record, + ) {} + + getWrapped(): ContentGenerator { + return this.wrapped; + } + + get userTier(): UserTierId | undefined { + return this.wrapped.userTier; + } + + get userTierName(): string | undefined { + return this.wrapped.userTierName; + } + + get paidTier(): GeminiUserTier | undefined { + return this.wrapped.paidTier; + } + + private mapModel(req: T): T { + if (req.model) { + const normalizedModel = normalizeModelId(req.model); + if (this.mappings[normalizedModel]) { + return { + ...req, + model: req.model.startsWith('models/') + ? `models/${this.mappings[normalizedModel]}` + : this.mappings[normalizedModel], + }; + } + } + return req; + } + + generateContent( + request: GenerateContentParameters, + userPromptId: string, + role: LlmRole, + ): Promise { + return this.wrapped.generateContent( + this.mapModel(request), + userPromptId, + role, + ); + } + + generateContentStream( + request: GenerateContentParameters, + userPromptId: string, + role: LlmRole, + ): Promise> { + return this.wrapped.generateContentStream( + this.mapModel(request), + userPromptId, + role, + ); + } + + countTokens(request: CountTokensParameters): Promise { + return this.wrapped.countTokens(this.mapModel(request)); + } + + embedContent(request: EmbedContentParameters): Promise { + return this.wrapped.embedContent(this.mapModel(request)); + } +} diff --git a/packages/core/src/core/prompts.test.ts b/packages/core/src/core/prompts.test.ts index 5937ed4900..4072040e83 100644 --- a/packages/core/src/core/prompts.test.ts +++ b/packages/core/src/core/prompts.test.ts @@ -22,7 +22,6 @@ import { PREVIEW_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_MODEL_AUTO, DEFAULT_GEMINI_MODEL, - DEFAULT_GEMINI_FLASH_LITE_MODEL, } from '../config/models.js'; import { ApprovalMode } from '../policy/types.js'; import { DiscoveredMCPTool } from '../tools/mcp-tool.js'; @@ -250,9 +249,7 @@ describe('Core System Prompt (prompts.ts)', () => { }); it('should use legacy system prompt for non-preview model', () => { - vi.mocked(mockConfig.getActiveModel).mockReturnValue( - DEFAULT_GEMINI_FLASH_LITE_MODEL, - ); + vi.mocked(mockConfig.getActiveModel).mockReturnValue(DEFAULT_GEMINI_MODEL); const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).toContain( 'You are an interactive CLI agent specializing in software engineering tasks.', @@ -267,9 +264,7 @@ describe('Core System Prompt (prompts.ts)', () => { }); it('should include the TASK MANAGEMENT PROTOCOL in legacy prompt when task tracker is enabled', () => { - vi.mocked(mockConfig.getActiveModel).mockReturnValue( - DEFAULT_GEMINI_FLASH_LITE_MODEL, - ); + vi.mocked(mockConfig.getActiveModel).mockReturnValue(DEFAULT_GEMINI_MODEL); vi.mocked(mockConfig.isTrackerEnabled).mockReturnValue(true); const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).toContain('# TASK MANAGEMENT PROTOCOL'); @@ -662,7 +657,7 @@ describe('Core System Prompt (prompts.ts)', () => { it('should include Windows-specific shell efficiency commands on win32', () => { mockPlatform('win32'); vi.mocked(mockConfig.getActiveModel).mockReturnValue( - DEFAULT_GEMINI_FLASH_LITE_MODEL, + DEFAULT_GEMINI_MODEL, ); const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).toContain( @@ -676,7 +671,7 @@ describe('Core System Prompt (prompts.ts)', () => { it('should include generic shell efficiency commands on non-Windows', () => { mockPlatform('linux'); vi.mocked(mockConfig.getActiveModel).mockReturnValue( - DEFAULT_GEMINI_FLASH_LITE_MODEL, + DEFAULT_GEMINI_MODEL, ); const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).toContain("using commands like 'grep', 'tail', 'head'"); diff --git a/packages/core/src/fallback/handler.test.ts b/packages/core/src/fallback/handler.test.ts index 698a5d7cfb..2f962345b9 100644 --- a/packages/core/src/fallback/handler.test.ts +++ b/packages/core/src/fallback/handler.test.ts @@ -77,6 +77,7 @@ const createMockConfig = (overrides: Partial = {}): Config => getModel: vi.fn(() => MOCK_PRO_MODEL), getUserTier: vi.fn(() => undefined), isInteractive: vi.fn(() => false), + getHasAccessToPreviewModel: vi.fn(() => true), ...overrides, }) as unknown as Config; diff --git a/packages/core/src/prompts/promptProvider.ts b/packages/core/src/prompts/promptProvider.ts index 2c1f9e8652..2d8593dce5 100644 --- a/packages/core/src/prompts/promptProvider.ts +++ b/packages/core/src/prompts/promptProvider.ts @@ -73,10 +73,10 @@ export class PromptProvider { const desiredModel = resolveModel( context.config.getActiveModel(), context.config.getGemini31LaunchedSync?.() ?? false, - context.config.getGemini31FlashLiteLaunchedSync?.() ?? false, false, context.config.getHasAccessToPreviewModel?.() ?? true, context.config, + context.config.hasGemini35FlashGAAccess?.() ?? false, ); const isModernModel = supportsModernFeatures(desiredModel); const activeSnippets = isModernModel ? snippets : legacySnippets; @@ -300,10 +300,10 @@ export class PromptProvider { const desiredModel = resolveModel( context.config.getActiveModel(), context.config.getGemini31LaunchedSync?.() ?? false, - context.config.getGemini31FlashLiteLaunchedSync?.() ?? false, false, context.config.getHasAccessToPreviewModel?.() ?? true, context.config, + context.config.hasGemini35FlashGAAccess?.() ?? false, ); const isModernModel = supportsModernFeatures(desiredModel); const activeSnippets = isModernModel ? snippets : legacySnippets; diff --git a/packages/core/src/routing/strategies/approvalModeStrategy.test.ts b/packages/core/src/routing/strategies/approvalModeStrategy.test.ts index 670c5c543e..a92c8332d3 100644 --- a/packages/core/src/routing/strategies/approvalModeStrategy.test.ts +++ b/packages/core/src/routing/strategies/approvalModeStrategy.test.ts @@ -43,7 +43,6 @@ describe('ApprovalModeStrategy', () => { getApprovedPlanPath: vi.fn().mockReturnValue(undefined), getPlanModeRoutingEnabled: vi.fn().mockResolvedValue(true), getGemini31Launched: vi.fn().mockResolvedValue(false), - getGemini31FlashLiteLaunched: vi.fn().mockResolvedValue(false), getHasAccessToPreviewModel: vi.fn().mockReturnValue(true), getUseCustomToolModel: vi.fn().mockImplementation(async () => { const launched = await mockConfig.getGemini31Launched(); @@ -243,4 +242,22 @@ describe('ApprovalModeStrategy', () => { // Should resolve to Preview Flash (3.0) because resolveClassifierModel uses preview variants for Gemini 3 expect(decision?.model).toBe(PREVIEW_GEMINI_FLASH_MODEL); }); + + it('should route to DEFAULT_GEMINI_FLASH_MODEL when hasGemini35FlashGAAccess is true and plan is approved', async () => { + vi.mocked(mockConfig.getModel).mockReturnValue(GEMINI_MODEL_ALIAS_AUTO); + mockConfig.hasGemini35FlashGAAccess = vi.fn().mockReturnValue(true); + + vi.mocked(mockConfig.getApprovalMode).mockReturnValue(ApprovalMode.DEFAULT); + vi.mocked(mockConfig.getApprovedPlanPath).mockReturnValue( + '/path/to/plan.md', + ); + + const decision = await strategy.route( + mockContext, + mockConfig, + mockBaseLlmClient, + ); + + expect(decision?.model).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); }); diff --git a/packages/core/src/routing/strategies/approvalModeStrategy.ts b/packages/core/src/routing/strategies/approvalModeStrategy.ts index 8538aed054..0d1b14a9ac 100644 --- a/packages/core/src/routing/strategies/approvalModeStrategy.ts +++ b/packages/core/src/routing/strategies/approvalModeStrategy.ts @@ -48,17 +48,13 @@ export class ApprovalModeStrategy implements RoutingStrategy { const approvalMode = config.getApprovalMode(); const approvedPlanPath = config.getApprovedPlanPath(); - const [ - useGemini3_1, - useGemini3_1FlashLite, - useCustomToolModel, - hasAccessToPreview, - ] = await Promise.all([ - config.getGemini31Launched(), - config.getGemini31FlashLiteLaunched(), - config.getUseCustomToolModel(), - config.getHasAccessToPreviewModel(), - ]); + const [useGemini3_1, useCustomToolModel, hasAccessToPreview] = + await Promise.all([ + config.getGemini31Launched(), + config.getUseCustomToolModel(), + config.getHasAccessToPreviewModel(), + ]); + const useGemini3_5Flash = config.hasGemini35FlashGAAccess?.() ?? false; // 1. Planning Phase: If ApprovalMode === PLAN, explicitly route to the Pro model. if (approvalMode === ApprovalMode.PLAN) { @@ -66,10 +62,10 @@ export class ApprovalModeStrategy implements RoutingStrategy { model, GEMINI_MODEL_ALIAS_PRO, useGemini3_1, - useGemini3_1FlashLite, useCustomToolModel, hasAccessToPreview, config, + useGemini3_5Flash, ); return { model: proModel, @@ -85,10 +81,10 @@ export class ApprovalModeStrategy implements RoutingStrategy { model, GEMINI_MODEL_ALIAS_FLASH, useGemini3_1, - useGemini3_1FlashLite, useCustomToolModel, hasAccessToPreview, config, + useGemini3_5Flash, ); return { model: flashModel, diff --git a/packages/core/src/routing/strategies/classifierStrategy.test.ts b/packages/core/src/routing/strategies/classifierStrategy.test.ts index a81cd53de3..4a1e463e6a 100644 --- a/packages/core/src/routing/strategies/classifierStrategy.test.ts +++ b/packages/core/src/routing/strategies/classifierStrategy.test.ts @@ -60,7 +60,6 @@ describe('ClassifierStrategy', () => { getModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO), getNumericalRoutingEnabled: vi.fn().mockResolvedValue(false), getGemini31Launched: vi.fn().mockResolvedValue(false), - getGemini31FlashLiteLaunched: vi.fn().mockResolvedValue(false), getUseCustomToolModel: vi.fn().mockImplementation(async () => { const launched = await mockConfig.getGemini31Launched(); const authType = mockConfig.getContentGeneratorConfig().authType; @@ -432,5 +431,27 @@ describe('ClassifierStrategy', () => { expect(decision?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL); }); + + it('should route to DEFAULT_GEMINI_FLASH_MODEL when hasGemini35FlashGAAccess is true', async () => { + mockConfig.hasGemini35FlashGAAccess = vi.fn().mockReturnValue(true); + vi.mocked(mockConfig.getModel).mockReturnValue(PREVIEW_GEMINI_MODEL_AUTO); + + const mockApiResponse = { + reasoning: 'Simple task', + model_choice: 'flash', + }; + vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue( + mockApiResponse, + ); + + const decision = await strategy.route( + mockContext, + mockConfig, + mockBaseLlmClient, + mockLocalLiteRtLmClient, + ); + + expect(decision?.model).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); }); }); diff --git a/packages/core/src/routing/strategies/classifierStrategy.ts b/packages/core/src/routing/strategies/classifierStrategy.ts index dda0f49665..30c16ea213 100644 --- a/packages/core/src/routing/strategies/classifierStrategy.ts +++ b/packages/core/src/routing/strategies/classifierStrategy.ts @@ -172,21 +172,20 @@ export class ClassifierStrategy implements RoutingStrategy { const reasoning = routerResponse.reasoning; const latencyMs = Date.now() - startTime; - const [useGemini3_1, useGemini3_1FlashLite, useCustomToolModel] = - await Promise.all([ - config.getGemini31Launched(), - config.getGemini31FlashLiteLaunched(), - config.getUseCustomToolModel(), - ]); + const [useGemini3_1, useCustomToolModel] = await Promise.all([ + config.getGemini31Launched(), + config.getUseCustomToolModel(), + ]); + const useGemini3_5Flash = config.hasGemini35FlashGAAccess?.() ?? false; const selectedModel = normalizeModelId( resolveClassifierModel( normalizeModelId(model), routerResponse.model_choice, useGemini3_1, - useGemini3_1FlashLite, useCustomToolModel, config.getHasAccessToPreviewModel?.() ?? true, config, + useGemini3_5Flash, ), ); diff --git a/packages/core/src/routing/strategies/defaultStrategy.ts b/packages/core/src/routing/strategies/defaultStrategy.ts index c43e013ae8..2eccedba26 100644 --- a/packages/core/src/routing/strategies/defaultStrategy.ts +++ b/packages/core/src/routing/strategies/defaultStrategy.ts @@ -26,10 +26,10 @@ export class DefaultStrategy implements TerminalStrategy { const defaultModel = resolveModel( config.getModel(), config.getGemini31LaunchedSync?.() ?? false, - config.getGemini31FlashLiteLaunchedSync?.() ?? false, false, config.getHasAccessToPreviewModel?.() ?? true, config, + config.hasGemini35FlashGAAccess?.() ?? false, ); return { model: defaultModel, diff --git a/packages/core/src/routing/strategies/fallbackStrategy.ts b/packages/core/src/routing/strategies/fallbackStrategy.ts index c911fb859e..5b152ec409 100644 --- a/packages/core/src/routing/strategies/fallbackStrategy.ts +++ b/packages/core/src/routing/strategies/fallbackStrategy.ts @@ -28,10 +28,10 @@ export class FallbackStrategy implements RoutingStrategy { const resolvedModel = resolveModel( requestedModel, config.getGemini31LaunchedSync?.() ?? false, - config.getGemini31FlashLiteLaunchedSync?.() ?? false, false, config.getHasAccessToPreviewModel?.() ?? true, config, + config.hasGemini35FlashGAAccess?.() ?? false, ); const service = config.getModelAvailabilityService(); const snapshot = service.snapshot(resolvedModel); diff --git a/packages/core/src/routing/strategies/gemmaClassifierStrategy.test.ts b/packages/core/src/routing/strategies/gemmaClassifierStrategy.test.ts index c66112c406..c07b2188d2 100644 --- a/packages/core/src/routing/strategies/gemmaClassifierStrategy.test.ts +++ b/packages/core/src/routing/strategies/gemmaClassifierStrategy.test.ts @@ -12,6 +12,7 @@ import type { BaseLlmClient } from '../../core/baseLlmClient.js'; import { DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_MODEL, + PREVIEW_GEMINI_MODEL_AUTO, } from '../../config/models.js'; import type { Content } from '@google/genai'; import { debugLogger } from '../../utils/debugLogger.js'; @@ -39,7 +40,6 @@ describe('GemmaClassifierStrategy', () => { getModel: () => DEFAULT_GEMINI_MODEL, getPreviewFeatures: () => false, getGemini31Launched: vi.fn().mockResolvedValue(false), - getGemini31FlashLiteLaunched: vi.fn().mockResolvedValue(false), getUseCustomToolModel: vi.fn().mockResolvedValue(false), getHasAccessToPreviewModel: vi.fn().mockReturnValue(true), } as unknown as Config; @@ -324,4 +324,24 @@ second message expect(lastTurn!.parts!.at(0)!.text).toEqual(expectedLastTurn); }); + + it('should route to DEFAULT_GEMINI_FLASH_MODEL when hasGemini35FlashGAAccess is true', async () => { + mockConfig.hasGemini35FlashGAAccess = vi.fn().mockReturnValue(true); + mockConfig.getModel = () => PREVIEW_GEMINI_MODEL_AUTO; + + const mockApiResponse = { + reasoning: 'Simple task', + model_choice: 'flash', + }; + mockGenerateJson.mockResolvedValue(mockApiResponse); + + const decision = await strategy.route( + mockContext, + mockConfig, + mockBaseLlmClient, + mockLocalLiteRtLmClient, + ); + + expect(decision?.model).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); }); diff --git a/packages/core/src/routing/strategies/gemmaClassifierStrategy.ts b/packages/core/src/routing/strategies/gemmaClassifierStrategy.ts index bf1c1b1b43..84ecce2e94 100644 --- a/packages/core/src/routing/strategies/gemmaClassifierStrategy.ts +++ b/packages/core/src/routing/strategies/gemmaClassifierStrategy.ts @@ -210,26 +210,22 @@ ${formattedHistory} const reasoning = routerResponse.reasoning; const latencyMs = Date.now() - startTime; - const [ - useGemini3_1, - useGemini3_1FlashLite, - useCustomToolModel, - hasAccessToPreview, - ] = await Promise.all([ - config.getGemini31Launched(), - config.getGemini31FlashLiteLaunched(), - config.getUseCustomToolModel(), - config.getHasAccessToPreviewModel?.() ?? true, - ]); + const [useGemini3_1, useCustomToolModel, hasAccessToPreview] = + await Promise.all([ + config.getGemini31Launched(), + config.getUseCustomToolModel(), + config.getHasAccessToPreviewModel(), + ]); + const useGemini3_5Flash = config.hasGemini35FlashGAAccess?.() ?? false; const selectedModel = resolveClassifierModel( context.requestedModel ?? config.getModel(), routerResponse.model_choice, useGemini3_1, - useGemini3_1FlashLite, useCustomToolModel, hasAccessToPreview, config, + useGemini3_5Flash, ); return { diff --git a/packages/core/src/routing/strategies/numericalClassifierStrategy.test.ts b/packages/core/src/routing/strategies/numericalClassifierStrategy.test.ts index fccd1c53eb..c7efc95280 100644 --- a/packages/core/src/routing/strategies/numericalClassifierStrategy.test.ts +++ b/packages/core/src/routing/strategies/numericalClassifierStrategy.test.ts @@ -20,6 +20,7 @@ import { PREVIEW_GEMINI_MODEL_AUTO, DEFAULT_GEMINI_MODEL_AUTO, DEFAULT_GEMINI_MODEL, + DEFAULT_GEMINI_FLASH_MODEL, } from '../../config/models.js'; import { promptIdContext } from '../../utils/promptIdContext.js'; import type { Content } from '@google/genai'; @@ -63,7 +64,6 @@ describe('NumericalClassifierStrategy', () => { getResolvedClassifierThreshold: vi.fn().mockResolvedValue(90), getClassifierThreshold: vi.fn().mockResolvedValue(undefined), getGemini31Launched: vi.fn().mockResolvedValue(false), - getGemini31FlashLiteLaunched: vi.fn().mockResolvedValue(false), getUseCustomToolModel: vi.fn().mockImplementation(async () => { const launched = await mockConfig.getGemini31Launched(); const authType = mockConfig.getContentGeneratorConfig().authType; @@ -796,5 +796,27 @@ describe('NumericalClassifierStrategy', () => { expect(decision?.model).toBe(PREVIEW_GEMINI_3_1_MODEL); }); + + it('should route to DEFAULT_GEMINI_FLASH_MODEL when hasGemini35FlashGAAccess is true', async () => { + mockConfig.hasGemini35FlashGAAccess = vi.fn().mockReturnValue(true); + vi.mocked(mockConfig.getModel).mockReturnValue(PREVIEW_GEMINI_MODEL_AUTO); + + const mockApiResponse = { + complexity_reasoning: 'Simple task', + complexity_score: 10, + }; + vi.mocked(mockBaseLlmClient.generateJson).mockResolvedValue( + mockApiResponse, + ); + + const decision = await strategy.route( + mockContext, + mockConfig, + mockBaseLlmClient, + mockLocalLiteRtLmClient, + ); + + expect(decision?.model).toBe(DEFAULT_GEMINI_FLASH_MODEL); + }); }); }); diff --git a/packages/core/src/routing/strategies/numericalClassifierStrategy.ts b/packages/core/src/routing/strategies/numericalClassifierStrategy.ts index a490601436..96f03d924f 100644 --- a/packages/core/src/routing/strategies/numericalClassifierStrategy.ts +++ b/packages/core/src/routing/strategies/numericalClassifierStrategy.ts @@ -167,21 +167,20 @@ export class NumericalClassifierStrategy implements RoutingStrategy { const { threshold, groupLabel, modelAlias } = await this.getRoutingDecision(score, config); - const [useGemini3_1, useGemini3_1FlashLite, useCustomToolModel] = - await Promise.all([ - config.getGemini31Launched(), - config.getGemini31FlashLiteLaunched(), - config.getUseCustomToolModel(), - ]); + const [useGemini3_1, useCustomToolModel] = await Promise.all([ + config.getGemini31Launched(), + config.getUseCustomToolModel(), + ]); + const useGemini3_5Flash = config.hasGemini35FlashGAAccess?.() ?? false; const selectedModel = normalizeModelId( resolveClassifierModel( normalizeModelId(model), modelAlias, useGemini3_1, - useGemini3_1FlashLite, useCustomToolModel, config.getHasAccessToPreviewModel?.() ?? true, config, + useGemini3_5Flash, ), ); diff --git a/packages/core/src/routing/strategies/overrideStrategy.ts b/packages/core/src/routing/strategies/overrideStrategy.ts index e93c0870ef..e01bda4879 100644 --- a/packages/core/src/routing/strategies/overrideStrategy.ts +++ b/packages/core/src/routing/strategies/overrideStrategy.ts @@ -38,10 +38,10 @@ export class OverrideStrategy implements RoutingStrategy { model: resolveModel( overrideModel, config.getGemini31LaunchedSync?.() ?? false, - config.getGemini31FlashLiteLaunchedSync?.() ?? false, false, config.getHasAccessToPreviewModel?.() ?? true, config, + config.hasGemini35FlashGAAccess?.() ?? false, ), metadata: { source: this.name, diff --git a/packages/core/src/services/modelConfigService.test.ts b/packages/core/src/services/modelConfigService.test.ts index 70df1aa7b0..76da7a2642 100644 --- a/packages/core/src/services/modelConfigService.test.ts +++ b/packages/core/src/services/modelConfigService.test.ts @@ -1019,6 +1019,79 @@ describe('ModelConfigService', () => { }); }); + // Resolves a model ID to a concrete model ID based on the provided context. + describe('resolveModelId', () => { + it('should resolve based on useGemini3_5Flash condition', () => { + const config: ModelConfigServiceConfig = { + modelIdResolutions: { + flash: { + default: 'gemini-2.0-flash', + contexts: [ + { + condition: { useGemini3_5Flash: true }, + target: 'gemini-3.5-flash', + }, + ], + }, + }, + }; + const service = new ModelConfigService(config); + + expect(service.resolveModelId('flash', { useGemini3_5Flash: true })).toBe( + 'gemini-3.5-flash', + ); + expect( + service.resolveModelId('flash', { useGemini3_5Flash: false }), + ).toBe('gemini-2.0-flash'); + expect(service.resolveModelId('flash', {})).toBe('gemini-2.0-flash'); + }); + + it('should resolve based on complex conditions including useGemini3_5Flash', () => { + const config: ModelConfigServiceConfig = { + modelIdResolutions: { + 'gemini-flash': { + default: 'gemini-3-flash-preview', + contexts: [ + { + condition: { + useGemini3_5Flash: false, + hasAccessToPreview: false, + }, + target: 'gemini-2.5-flash', + }, + { + condition: { useGemini3_5Flash: true }, + target: 'gemini-3.5-flash', + }, + ], + }, + }, + }; + const service = new ModelConfigService(config); + + // Case 1: GA Access granted + expect( + service.resolveModelId('gemini-flash', { useGemini3_5Flash: true }), + ).toBe('gemini-3.5-flash'); + + // Case 2: GA Access denied, but has preview access + expect( + service.resolveModelId('gemini-flash', { + useGemini3_5Flash: false, + hasAccessToPreview: true, + }), + ).toBe('gemini-3-flash-preview'); + + // Case 3: GA Access denied AND no preview access + expect( + service.resolveModelId('gemini-flash', { + useGemini3_5Flash: false, + hasAccessToPreview: false, + }), + ).toBe('gemini-2.5-flash'); + }); + }); + describe('getAvailableModelOptions', () => { it('should filter out Pro models when hasAccessToProModel is false', () => { const config: ModelConfigServiceConfig = { diff --git a/packages/core/src/services/modelConfigService.ts b/packages/core/src/services/modelConfigService.ts index 86a1ab11a5..f82f9267d1 100644 --- a/packages/core/src/services/modelConfigService.ts +++ b/packages/core/src/services/modelConfigService.ts @@ -9,9 +9,8 @@ import type { ModelPolicy } from '../availability/modelPolicy.js'; import { getDisplayString, PREVIEW_GEMINI_3_1_MODEL, - PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL, - 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 @@ -98,6 +97,7 @@ export interface ModelResolution { export interface ResolutionContext { useGemini3_1?: boolean; useGemini3_1FlashLite?: boolean; + useGemini3_5Flash?: boolean; useCustomTools?: boolean; hasAccessToPreview?: boolean; hasAccessToProModel?: boolean; @@ -108,6 +108,7 @@ export interface ResolutionContext { export interface ResolutionCondition { useGemini3_1?: boolean; useGemini3_1FlashLite?: boolean; + useGemini3_5Flash?: boolean; useCustomTools?: boolean; hasAccessToPreview?: boolean; /** Matches if the current model is in this list. */ @@ -156,7 +157,7 @@ export class ModelConfigService { const definitions = this.config.modelDefinitions ?? {}; const shouldShowPreviewModels = context.hasAccessToPreview ?? false; const useGemini31 = context.useGemini3_1 ?? false; - const useGemini31FlashLite = context.useGemini3_1FlashLite ?? false; + const useGemini3_5Flash = context.useGemini3_5Flash ?? false; const mainOptions = Object.entries(definitions) .filter(([_, m]) => { @@ -165,18 +166,25 @@ 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( + shouldShowPreviewModels, + useGemini31, + useGemini3_5Flash, + ); + } 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]) => { @@ -186,19 +194,12 @@ export class ModelConfigService { if (context.hasAccessToProModel === false && isProModel(id)) return false; if (id === PREVIEW_GEMINI_3_1_MODEL && !useGemini31) return false; - if ( - (id === PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL || - id === GEMINI_3_1_FLASH_LITE_MODEL) && - !useGemini31FlashLite - ) - return false; return true; }) .map(([id, m]) => { const resolvedId = this.resolveModelId(id, context); const titleId = this.resolveModelId(id, { useGemini3_1: useGemini31, - useGemini3_1FlashLite: useGemini31FlashLite, }); return { modelId: resolvedId, @@ -253,6 +254,8 @@ export class ModelConfigService { return value === context.useGemini3_1; case 'useGemini3_1FlashLite': return value === context.useGemini3_1FlashLite; + case 'useGemini3_5Flash': + return value === context.useGemini3_5Flash; case 'useCustomTools': return value === context.useCustomTools; case 'hasAccessToPreview': @@ -342,6 +345,10 @@ export class ModelConfigService { this.runtimeOverrides.push(override); } + clearRuntimeOverrides(): void { + this.runtimeOverrides.length = 0; + } + /** * Resolves a model configuration by merging settings from aliases and applying overrides. * diff --git a/packages/core/src/services/test-data/resolved-aliases-retry.golden.json b/packages/core/src/services/test-data/resolved-aliases-retry.golden.json index 3ccd494036..aa470d45fe 100644 --- a/packages/core/src/services/test-data/resolved-aliases-retry.golden.json +++ b/packages/core/src/services/test-data/resolved-aliases-retry.golden.json @@ -61,6 +61,42 @@ "topK": 64 } }, + "gemini-3.1-pro-preview": { + "model": "gemini-3.1-pro-preview", + "generateContentConfig": { + "temperature": 1, + "topP": 0.95, + "thinkingConfig": { + "includeThoughts": true, + "thinkingLevel": "HIGH" + }, + "topK": 64 + } + }, + "gemini-3.1-pro-preview-customtools": { + "model": "gemini-3.1-pro-preview-customtools", + "generateContentConfig": { + "temperature": 1, + "topP": 0.95, + "thinkingConfig": { + "includeThoughts": true, + "thinkingLevel": "HIGH" + }, + "topK": 64 + } + }, + "gemini-3.1-flash-lite-preview": { + "model": "gemini-3.1-flash-lite-preview", + "generateContentConfig": { + "temperature": 1, + "topP": 0.95, + "thinkingConfig": { + "includeThoughts": true, + "thinkingLevel": "HIGH" + }, + "topK": 64 + } + }, "gemini-2.5-pro": { "model": "gemini-2.5-pro", "generateContentConfig": { @@ -104,7 +140,19 @@ "topP": 0.95, "thinkingConfig": { "includeThoughts": true, - "thinkingBudget": 8192 + "thinkingLevel": "HIGH" + }, + "topK": 64 + } + }, + "gemini-3.5-flash": { + "model": "gemini-3.5-flash", + "generateContentConfig": { + "temperature": 1, + "topP": 0.95, + "thinkingConfig": { + "includeThoughts": true, + "thinkingLevel": "HIGH" }, "topK": 64 } @@ -147,6 +195,13 @@ "topP": 1 } }, + "gemini-3.5-flash-base": { + "model": "gemini-3.5-flash", + "generateContentConfig": { + "temperature": 0, + "topP": 1 + } + }, "classifier": { "model": "flash-lite", "generateContentConfig": { diff --git a/packages/core/src/services/test-data/resolved-aliases.golden.json b/packages/core/src/services/test-data/resolved-aliases.golden.json index 3ccd494036..aa470d45fe 100644 --- a/packages/core/src/services/test-data/resolved-aliases.golden.json +++ b/packages/core/src/services/test-data/resolved-aliases.golden.json @@ -61,6 +61,42 @@ "topK": 64 } }, + "gemini-3.1-pro-preview": { + "model": "gemini-3.1-pro-preview", + "generateContentConfig": { + "temperature": 1, + "topP": 0.95, + "thinkingConfig": { + "includeThoughts": true, + "thinkingLevel": "HIGH" + }, + "topK": 64 + } + }, + "gemini-3.1-pro-preview-customtools": { + "model": "gemini-3.1-pro-preview-customtools", + "generateContentConfig": { + "temperature": 1, + "topP": 0.95, + "thinkingConfig": { + "includeThoughts": true, + "thinkingLevel": "HIGH" + }, + "topK": 64 + } + }, + "gemini-3.1-flash-lite-preview": { + "model": "gemini-3.1-flash-lite-preview", + "generateContentConfig": { + "temperature": 1, + "topP": 0.95, + "thinkingConfig": { + "includeThoughts": true, + "thinkingLevel": "HIGH" + }, + "topK": 64 + } + }, "gemini-2.5-pro": { "model": "gemini-2.5-pro", "generateContentConfig": { @@ -104,7 +140,19 @@ "topP": 0.95, "thinkingConfig": { "includeThoughts": true, - "thinkingBudget": 8192 + "thinkingLevel": "HIGH" + }, + "topK": 64 + } + }, + "gemini-3.5-flash": { + "model": "gemini-3.5-flash", + "generateContentConfig": { + "temperature": 1, + "topP": 0.95, + "thinkingConfig": { + "includeThoughts": true, + "thinkingLevel": "HIGH" }, "topK": 64 } @@ -147,6 +195,13 @@ "topP": 1 } }, + "gemini-3.5-flash-base": { + "model": "gemini-3.5-flash", + "generateContentConfig": { + "temperature": 0, + "topP": 1 + } + }, "classifier": { "model": "flash-lite", "generateContentConfig": { diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index 4c695ab5ee..555155fcd8 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -968,18 +968,24 @@ export class ShellToolInvocation extends BaseToolInvocation< if (timeoutTimer) clearTimeout(timeoutTimer); signal.removeEventListener('abort', onAbort); timeoutController.signal.removeEventListener('abort', onAbort); - if (tempFilePath) { - try { - await fsPromises.unlink(tempFilePath); - } catch { - // Ignore errors during unlink + + // Only clean up if NOT running in background. + // Background processes need the temp directory and PID file to remain + // available until they exit. + if (!this.params.is_background) { + if (tempFilePath) { + try { + await fsPromises.unlink(tempFilePath); + } catch { + // Ignore errors during unlink + } } - } - if (tempDir) { - try { - await fsPromises.rm(tempDir, { recursive: true, force: true }); - } catch { - // Ignore errors during rm + if (tempDir) { + try { + await fsPromises.rm(tempDir, { recursive: true, force: true }); + } catch { + // Ignore errors during rm + } } } } diff --git a/packages/core/test-setup.ts b/packages/core/test-setup.ts index d730369578..dc1d7ac932 100644 --- a/packages/core/test-setup.ts +++ b/packages/core/test-setup.ts @@ -55,5 +55,9 @@ vi.mock('./src/config/storage.js', async (importOriginal) => { (actual.Storage.prototype as any).getProjectIdentifier = vi.fn( () => 'project-slug', ); + actual.Storage.getUserPoliciesDir = vi.fn(() => '/tmp/mock-user-policies'); + actual.Storage.getSystemPoliciesDir = vi.fn( + () => '/tmp/mock-system-policies', + ); return actual; }); diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index f4c89db862..0a5428f7eb 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -709,7 +709,7 @@ "modelConfigs": { "title": "Model Configs", "description": "Model configurations.", - "markdownDescription": "Model configurations.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `{\n \"aliases\": {\n \"base\": {\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 0,\n \"topP\": 1\n }\n }\n },\n \"chat-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"includeThoughts\": true\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-base-2.5\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 8192\n }\n }\n }\n },\n \"chat-base-3\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n }\n }\n }\n },\n \"gemini-3-pro-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"gemini-3-flash-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"gemini-2.5-pro\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"gemini-2.5-flash\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"gemma-4-31b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-31b-it\"\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-26b-a4b-it\"\n }\n },\n \"gemini-2.5-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-3-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"classifier\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 1024,\n \"thinkingConfig\": {\n \"thinkingBudget\": 512\n }\n }\n }\n },\n \"prompt-completion\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.3,\n \"maxOutputTokens\": 16000,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"fast-ack-helper\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.2,\n \"maxOutputTokens\": 120,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"edit-corrector\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"summarizer-default\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"summarizer-shell\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"web-search\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"googleSearch\": {}\n }\n ]\n }\n }\n },\n \"web-fetch\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"urlContext\": {}\n }\n ]\n }\n }\n },\n \"web-fetch-fallback\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection-double-check\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"llm-edit-fixer\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"next-speaker-checker\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"context-snapshotter\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-compression-3-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"chat-compression-3-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"chat-compression-3.1-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite-preview\"\n }\n },\n \"chat-compression-2.5-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"chat-compression-2.5-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"chat-compression-2.5-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"chat-compression-default\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"agent-history-provider-summarizer\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n }\n },\n \"overrides\": [\n {\n \"match\": {\n \"model\": \"chat-base\",\n \"isRetry\": true\n },\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 1\n }\n }\n }\n ],\n \"modelDefinitions\": {\n \"gemini-3.1-flash-lite-preview\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-flash-preview\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-2.5-pro\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-31b-it\": {\n \"displayName\": \"gemma-4-31b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"displayName\": \"gemma-4-26b-a4b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto\": {\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"pro\": {\n \"tier\": \"pro\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"flash\": {\n \"tier\": \"flash\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"flash-lite\": {\n \"tier\": \"flash-lite\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-3\": {\n \"displayName\": \"Auto (Gemini 3)\",\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"dialogDescription\": \"Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash\",\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-2.5\": {\n \"displayName\": \"Auto (Gemini 2.5)\",\n \"tier\": \"auto\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"dialogDescription\": \"Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash\",\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n }\n },\n \"modelIdResolutions\": {\n \"gemma-4-31b-it\": {\n \"default\": \"gemma-4-31b-it\"\n },\n \"gemma-4-26b-a4b-it\": {\n \"default\": \"gemma-4-26b-a4b-it\"\n },\n \"gemini-3.1-pro-preview\": {\n \"default\": \"gemini-3.1-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n }\n ]\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"default\": \"gemini-3.1-pro-preview-customtools\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n }\n ]\n },\n \"gemini-3-flash-preview\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"gemini-3-pro-preview\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-3\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-2.5\": {\n \"default\": \"gemini-2.5-pro\"\n },\n \"gemini-3.1-flash-lite-preview\": {\n \"default\": \"gemini-3.1-flash-lite-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_1FlashLite\": false\n },\n \"target\": \"gemini-2.5-flash-lite\"\n }\n ]\n },\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"flash-lite\": {\n \"default\": \"gemini-2.5-flash-lite\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_1FlashLite\": true\n },\n \"target\": \"gemini-3.1-flash-lite-preview\"\n }\n ]\n }\n },\n \"classifierIdResolutions\": {\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-2.5\",\n \"gemini-2.5-pro\"\n ]\n },\n \"target\": \"gemini-2.5-flash\"\n },\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-3\",\n \"gemini-3-pro-preview\"\n ]\n },\n \"target\": \"gemini-3-flash-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-2.5\",\n \"gemini-2.5-pro\"\n ]\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n }\n },\n \"modelChains\": {\n \"preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"lite\": [\n {\n \"model\": \"gemini-2.5-flash-lite\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-pro\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ]\n }\n}`", + "markdownDescription": "Model configurations.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `{\n \"aliases\": {\n \"base\": {\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 0,\n \"topP\": 1\n }\n }\n },\n \"chat-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"includeThoughts\": true\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-base-2.5\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 8192\n }\n }\n }\n },\n \"chat-base-3\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n }\n }\n }\n },\n \"gemini-3-pro-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"gemini-3-flash-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"gemini-3.1-pro-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.1-pro-preview\"\n }\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.1-pro-preview-customtools\"\n }\n },\n \"gemini-3.1-flash-lite-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite-preview\"\n }\n },\n \"gemini-2.5-pro\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"gemini-2.5-flash\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"gemini-3.1-flash-lite\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite\"\n }\n },\n \"gemini-3.5-flash\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.5-flash\"\n }\n },\n \"gemma-4-31b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-31b-it\"\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-26b-a4b-it\"\n }\n },\n \"gemini-2.5-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-3-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"gemini-3.5-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3.5-flash\"\n }\n },\n \"classifier\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 1024,\n \"thinkingConfig\": {\n \"thinkingBudget\": 512\n }\n }\n }\n },\n \"prompt-completion\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.3,\n \"maxOutputTokens\": 16000,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"fast-ack-helper\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.2,\n \"maxOutputTokens\": 120,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"edit-corrector\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"summarizer-default\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"summarizer-shell\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"web-search\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"googleSearch\": {}\n }\n ]\n }\n }\n },\n \"web-fetch\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"urlContext\": {}\n }\n ]\n }\n }\n },\n \"web-fetch-fallback\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection-double-check\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"llm-edit-fixer\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"next-speaker-checker\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"context-snapshotter\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-compression-3-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"chat-compression-3-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"chat-compression-3.1-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite\"\n }\n },\n \"chat-compression-2.5-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"chat-compression-2.5-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"chat-compression-2.5-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"chat-compression-default\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"agent-history-provider-summarizer\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n }\n },\n \"overrides\": [\n {\n \"match\": {\n \"model\": \"chat-base\",\n \"isRetry\": true\n },\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 1\n }\n }\n }\n ],\n \"modelDefinitions\": {\n \"gemini-3.1-flash-lite\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-3\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-flash-preview\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.5-flash\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-3\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-2.5-pro\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-31b-it\": {\n \"displayName\": \"gemma-4-31b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"displayName\": \"gemma-4-26b-a4b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto\": {\n \"displayName\": \"Auto\",\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"pro\": {\n \"tier\": \"pro\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"flash\": {\n \"tier\": \"flash\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"flash-lite\": {\n \"tier\": \"flash-lite\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-3\": {\n \"tier\": \"auto\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": false\n },\n \"auto-gemini-2.5\": {\n \"tier\": \"auto\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": false\n }\n },\n \"modelIdResolutions\": {\n \"gemma-4-31b-it\": {\n \"default\": \"gemma-4-31b-it\"\n },\n \"gemma-4-26b-a4b-it\": {\n \"default\": \"gemma-4-26b-a4b-it\"\n },\n \"gemini-3.1-pro-preview\": {\n \"default\": \"gemini-3.1-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n }\n ]\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"default\": \"gemini-3.1-pro-preview-customtools\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n }\n ]\n },\n \"gemini-3-flash-preview\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false,\n \"useGemini3_5Flash\": true\n },\n \"target\": \"gemini-3.5-flash\"\n },\n {\n \"condition\": {\n \"hasAccessToPreview\": false,\n \"useGemini3_5Flash\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"gemini-3.5-flash\": {\n \"default\": \"gemini-3.5-flash\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_5Flash\": false,\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n },\n {\n \"condition\": {\n \"useGemini3_5Flash\": false\n },\n \"target\": \"gemini-3-flash-preview\"\n }\n ]\n },\n \"gemini-2.5-flash\": {\n \"default\": \"gemini-2.5-flash\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_5Flash\": true\n },\n \"target\": \"gemini-3.5-flash\"\n }\n ]\n },\n \"gemini-3-pro-preview\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"gemini-3.1-flash-lite\": {\n \"default\": \"gemini-3.1-flash-lite\"\n },\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_5Flash\": true\n },\n \"target\": \"gemini-3.5-flash\"\n },\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"flash-lite\": {\n \"default\": \"gemini-3.1-flash-lite\"\n },\n \"auto-gemini-3\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-2.5\": {\n \"default\": \"gemini-2.5-pro\"\n }\n },\n \"classifierIdResolutions\": {\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_5Flash\": true\n },\n \"target\": \"gemini-3.5-flash\"\n },\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n },\n {\n \"condition\": {\n \"requestedModels\": [\n \"gemini-2.5-pro\",\n \"auto-gemini-2.5\"\n ]\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"requestedModels\": [\n \"gemini-2.5-pro\",\n \"auto-gemini-2.5\"\n ]\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n }\n },\n \"modelChains\": {\n \"preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"lite\": [\n {\n \"model\": \"flash-lite\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-pro\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ]\n }\n}`", "default": { "aliases": { "base": { @@ -765,6 +765,24 @@ "model": "gemini-3-flash-preview" } }, + "gemini-3.1-pro-preview": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-pro-preview" + } + }, + "gemini-3.1-pro-preview-customtools": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-pro-preview-customtools" + } + }, + "gemini-3.1-flash-lite-preview": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-flash-lite-preview" + } + }, "gemini-2.5-pro": { "extends": "chat-base-2.5", "modelConfig": { @@ -783,6 +801,18 @@ "model": "gemini-2.5-flash-lite" } }, + "gemini-3.1-flash-lite": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-flash-lite" + } + }, + "gemini-3.5-flash": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.5-flash" + } + }, "gemma-4-31b-it": { "extends": "chat-base-3", "modelConfig": { @@ -807,10 +837,16 @@ "model": "gemini-3-flash-preview" } }, + "gemini-3.5-flash-base": { + "extends": "base", + "modelConfig": { + "model": "gemini-3.5-flash" + } + }, "classifier": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "maxOutputTokens": 1024, "thinkingConfig": { @@ -822,7 +858,7 @@ "prompt-completion": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "temperature": 0.3, "maxOutputTokens": 16000, @@ -835,7 +871,7 @@ "fast-ack-helper": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "temperature": 0.2, "maxOutputTokens": 120, @@ -848,7 +884,7 @@ "edit-corrector": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "thinkingConfig": { "thinkingBudget": 0 @@ -859,7 +895,7 @@ "summarizer-default": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "maxOutputTokens": 2000 } @@ -868,7 +904,7 @@ "summarizer-shell": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "maxOutputTokens": 2000 } @@ -945,7 +981,7 @@ }, "chat-compression-3.1-flash-lite": { "modelConfig": { - "model": "gemini-3.1-flash-lite-preview" + "model": "gemini-3.1-flash-lite" } }, "chat-compression-2.5-pro": { @@ -988,10 +1024,10 @@ } ], "modelDefinitions": { - "gemini-3.1-flash-lite-preview": { + "gemini-3.1-flash-lite": { "tier": "flash-lite", "family": "gemini-3", - "isPreview": true, + "isPreview": false, "isVisible": true, "features": { "thinking": false, @@ -1038,6 +1074,16 @@ "multimodalToolUse": true } }, + "gemini-3.5-flash": { + "tier": "flash", + "family": "gemini-3", + "isPreview": false, + "isVisible": true, + "features": { + "thinking": false, + "multimodalToolUse": true + } + }, "gemini-2.5-pro": { "tier": "pro", "family": "gemini-2.5", @@ -1091,9 +1137,10 @@ } }, "auto": { + "displayName": "Auto", "tier": "auto", "isPreview": true, - "isVisible": false, + "isVisible": true, "features": { "thinking": true, "multimodalToolUse": false @@ -1127,26 +1174,16 @@ } }, "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": { @@ -1189,37 +1226,50 @@ "contexts": [ { "condition": { - "hasAccessToPreview": false + "hasAccessToPreview": false, + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, + { + "condition": { + "hasAccessToPreview": false, + "useGemini3_5Flash": false }, "target": "gemini-2.5-flash" } ] }, - "gemini-3-pro-preview": { - "default": "gemini-3-pro-preview", + "gemini-3.5-flash": { + "default": "gemini-3.5-flash", "contexts": [ { "condition": { + "useGemini3_5Flash": false, "hasAccessToPreview": false }, - "target": "gemini-2.5-pro" + "target": "gemini-2.5-flash" }, { "condition": { - "useGemini3_1": true, - "useCustomTools": true + "useGemini3_5Flash": false }, - "target": "gemini-3.1-pro-preview-customtools" - }, - { - "condition": { - "useGemini3_1": true - }, - "target": "gemini-3.1-pro-preview" + "target": "gemini-3-flash-preview" } ] }, - "auto-gemini-3": { + "gemini-2.5-flash": { + "default": "gemini-2.5-flash", + "contexts": [ + { + "condition": { + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + } + ] + }, + "gemini-3-pro-preview": { "default": "gemini-3-pro-preview", "contexts": [ { @@ -1291,23 +1341,18 @@ } ] }, - "auto-gemini-2.5": { - "default": "gemini-2.5-pro" - }, - "gemini-3.1-flash-lite-preview": { - "default": "gemini-3.1-flash-lite-preview", - "contexts": [ - { - "condition": { - "useGemini3_1FlashLite": false - }, - "target": "gemini-2.5-flash-lite" - } - ] + "gemini-3.1-flash-lite": { + "default": "gemini-3.1-flash-lite" }, "flash": { "default": "gemini-3-flash-preview", "contexts": [ + { + "condition": { + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, { "condition": { "hasAccessToPreview": false @@ -1317,15 +1362,34 @@ ] }, "flash-lite": { - "default": "gemini-2.5-flash-lite", + "default": "gemini-3.1-flash-lite" + }, + "auto-gemini-3": { + "default": "gemini-3-pro-preview", "contexts": [ { "condition": { - "useGemini3_1FlashLite": true + "hasAccessToPreview": false }, - "target": "gemini-3.1-flash-lite-preview" + "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": { @@ -1334,15 +1398,21 @@ "contexts": [ { "condition": { - "requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"] + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, + { + "condition": { + "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" } ] }, @@ -1351,7 +1421,13 @@ "contexts": [ { "condition": { - "requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"] + "hasAccessToPreview": false + }, + "target": "gemini-2.5-pro" + }, + { + "condition": { + "requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"] }, "target": "gemini-2.5-pro" }, @@ -1512,7 +1588,7 @@ ], "lite": [ { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "actions": { "terminal": "silent", "transient": "silent", @@ -1565,7 +1641,7 @@ "aliases": { "title": "Model Config Aliases", "description": "Named presets for model configs. Can be used in place of a model name and can inherit from other aliases using an `extends` property.", - "markdownDescription": "Named presets for model configs. Can be used in place of a model name and can inherit from other aliases using an `extends` property.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `{\n \"base\": {\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 0,\n \"topP\": 1\n }\n }\n },\n \"chat-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"includeThoughts\": true\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-base-2.5\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 8192\n }\n }\n }\n },\n \"chat-base-3\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n }\n }\n }\n },\n \"gemini-3-pro-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"gemini-3-flash-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"gemini-2.5-pro\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"gemini-2.5-flash\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"gemma-4-31b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-31b-it\"\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-26b-a4b-it\"\n }\n },\n \"gemini-2.5-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-3-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"classifier\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 1024,\n \"thinkingConfig\": {\n \"thinkingBudget\": 512\n }\n }\n }\n },\n \"prompt-completion\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.3,\n \"maxOutputTokens\": 16000,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"fast-ack-helper\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.2,\n \"maxOutputTokens\": 120,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"edit-corrector\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"summarizer-default\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"summarizer-shell\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"web-search\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"googleSearch\": {}\n }\n ]\n }\n }\n },\n \"web-fetch\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"urlContext\": {}\n }\n ]\n }\n }\n },\n \"web-fetch-fallback\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection-double-check\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"llm-edit-fixer\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"next-speaker-checker\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"context-snapshotter\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-compression-3-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"chat-compression-3-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"chat-compression-3.1-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite-preview\"\n }\n },\n \"chat-compression-2.5-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"chat-compression-2.5-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"chat-compression-2.5-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"chat-compression-default\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"agent-history-provider-summarizer\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n }\n}`", + "markdownDescription": "Named presets for model configs. Can be used in place of a model name and can inherit from other aliases using an `extends` property.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `{\n \"base\": {\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 0,\n \"topP\": 1\n }\n }\n },\n \"chat-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"includeThoughts\": true\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-base-2.5\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 8192\n }\n }\n }\n },\n \"chat-base-3\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n }\n }\n }\n },\n \"gemini-3-pro-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"gemini-3-flash-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"gemini-3.1-pro-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.1-pro-preview\"\n }\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.1-pro-preview-customtools\"\n }\n },\n \"gemini-3.1-flash-lite-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite-preview\"\n }\n },\n \"gemini-2.5-pro\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"gemini-2.5-flash\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"gemini-3.1-flash-lite\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite\"\n }\n },\n \"gemini-3.5-flash\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3.5-flash\"\n }\n },\n \"gemma-4-31b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-31b-it\"\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-26b-a4b-it\"\n }\n },\n \"gemini-2.5-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-3-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"gemini-3.5-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3.5-flash\"\n }\n },\n \"classifier\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 1024,\n \"thinkingConfig\": {\n \"thinkingBudget\": 512\n }\n }\n }\n },\n \"prompt-completion\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.3,\n \"maxOutputTokens\": 16000,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"fast-ack-helper\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.2,\n \"maxOutputTokens\": 120,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"edit-corrector\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"summarizer-default\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"summarizer-shell\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"web-search\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"googleSearch\": {}\n }\n ]\n }\n }\n },\n \"web-fetch\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"urlContext\": {}\n }\n ]\n }\n }\n },\n \"web-fetch-fallback\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection-double-check\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"llm-edit-fixer\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"next-speaker-checker\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"context-snapshotter\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-compression-3-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"chat-compression-3-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"chat-compression-3.1-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite\"\n }\n },\n \"chat-compression-2.5-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"chat-compression-2.5-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"chat-compression-2.5-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"chat-compression-default\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"agent-history-provider-summarizer\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n }\n}`", "default": { "base": { "modelConfig": { @@ -1620,6 +1696,24 @@ "model": "gemini-3-flash-preview" } }, + "gemini-3.1-pro-preview": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-pro-preview" + } + }, + "gemini-3.1-pro-preview-customtools": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-pro-preview-customtools" + } + }, + "gemini-3.1-flash-lite-preview": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-flash-lite-preview" + } + }, "gemini-2.5-pro": { "extends": "chat-base-2.5", "modelConfig": { @@ -1638,6 +1732,18 @@ "model": "gemini-2.5-flash-lite" } }, + "gemini-3.1-flash-lite": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.1-flash-lite" + } + }, + "gemini-3.5-flash": { + "extends": "chat-base-3", + "modelConfig": { + "model": "gemini-3.5-flash" + } + }, "gemma-4-31b-it": { "extends": "chat-base-3", "modelConfig": { @@ -1662,10 +1768,16 @@ "model": "gemini-3-flash-preview" } }, + "gemini-3.5-flash-base": { + "extends": "base", + "modelConfig": { + "model": "gemini-3.5-flash" + } + }, "classifier": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "maxOutputTokens": 1024, "thinkingConfig": { @@ -1677,7 +1789,7 @@ "prompt-completion": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "temperature": 0.3, "maxOutputTokens": 16000, @@ -1690,7 +1802,7 @@ "fast-ack-helper": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "temperature": 0.2, "maxOutputTokens": 120, @@ -1703,7 +1815,7 @@ "edit-corrector": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "thinkingConfig": { "thinkingBudget": 0 @@ -1714,7 +1826,7 @@ "summarizer-default": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "maxOutputTokens": 2000 } @@ -1723,7 +1835,7 @@ "summarizer-shell": { "extends": "base", "modelConfig": { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "generateContentConfig": { "maxOutputTokens": 2000 } @@ -1800,7 +1912,7 @@ }, "chat-compression-3.1-flash-lite": { "modelConfig": { - "model": "gemini-3.1-flash-lite-preview" + "model": "gemini-3.1-flash-lite" } }, "chat-compression-2.5-pro": { @@ -1859,12 +1971,12 @@ "modelDefinitions": { "title": "Model Definitions", "description": "Registry of model metadata, including tier, family, and features.", - "markdownDescription": "Registry of model metadata, including tier, family, and features.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"gemini-3.1-flash-lite-preview\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-flash-preview\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-2.5-pro\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-31b-it\": {\n \"displayName\": \"gemma-4-31b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"displayName\": \"gemma-4-26b-a4b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto\": {\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"pro\": {\n \"tier\": \"pro\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"flash\": {\n \"tier\": \"flash\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"flash-lite\": {\n \"tier\": \"flash-lite\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-3\": {\n \"displayName\": \"Auto (Gemini 3)\",\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"dialogDescription\": \"Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash\",\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-2.5\": {\n \"displayName\": \"Auto (Gemini 2.5)\",\n \"tier\": \"auto\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"dialogDescription\": \"Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash\",\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n }\n}`", + "markdownDescription": "Registry of model metadata, including tier, family, and features.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"gemini-3.1-flash-lite\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-3\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-flash-preview\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.5-flash\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-3\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-2.5-pro\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-31b-it\": {\n \"displayName\": \"gemma-4-31b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"displayName\": \"gemma-4-26b-a4b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto\": {\n \"displayName\": \"Auto\",\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"pro\": {\n \"tier\": \"pro\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"flash\": {\n \"tier\": \"flash\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"flash-lite\": {\n \"tier\": \"flash-lite\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-3\": {\n \"tier\": \"auto\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": false\n },\n \"auto-gemini-2.5\": {\n \"tier\": \"auto\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": false\n }\n}`", "default": { - "gemini-3.1-flash-lite-preview": { + "gemini-3.1-flash-lite": { "tier": "flash-lite", "family": "gemini-3", - "isPreview": true, + "isPreview": false, "isVisible": true, "features": { "thinking": false, @@ -1911,6 +2023,16 @@ "multimodalToolUse": true } }, + "gemini-3.5-flash": { + "tier": "flash", + "family": "gemini-3", + "isPreview": false, + "isVisible": true, + "features": { + "thinking": false, + "multimodalToolUse": true + } + }, "gemini-2.5-pro": { "tier": "pro", "family": "gemini-2.5", @@ -1964,9 +2086,10 @@ } }, "auto": { + "displayName": "Auto", "tier": "auto", "isPreview": true, - "isVisible": false, + "isVisible": true, "features": { "thinking": true, "multimodalToolUse": false @@ -2000,26 +2123,16 @@ } }, "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 } }, "type": "object", @@ -2030,7 +2143,7 @@ "modelIdResolutions": { "title": "Model ID Resolutions", "description": "Rules for resolving requested model names to concrete model IDs based on context.", - "markdownDescription": "Rules for resolving requested model names to concrete model IDs based on context.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"gemma-4-31b-it\": {\n \"default\": \"gemma-4-31b-it\"\n },\n \"gemma-4-26b-a4b-it\": {\n \"default\": \"gemma-4-26b-a4b-it\"\n },\n \"gemini-3.1-pro-preview\": {\n \"default\": \"gemini-3.1-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n }\n ]\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"default\": \"gemini-3.1-pro-preview-customtools\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n }\n ]\n },\n \"gemini-3-flash-preview\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"gemini-3-pro-preview\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-3\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-2.5\": {\n \"default\": \"gemini-2.5-pro\"\n },\n \"gemini-3.1-flash-lite-preview\": {\n \"default\": \"gemini-3.1-flash-lite-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_1FlashLite\": false\n },\n \"target\": \"gemini-2.5-flash-lite\"\n }\n ]\n },\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"flash-lite\": {\n \"default\": \"gemini-2.5-flash-lite\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_1FlashLite\": true\n },\n \"target\": \"gemini-3.1-flash-lite-preview\"\n }\n ]\n }\n}`", + "markdownDescription": "Rules for resolving requested model names to concrete model IDs based on context.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"gemma-4-31b-it\": {\n \"default\": \"gemma-4-31b-it\"\n },\n \"gemma-4-26b-a4b-it\": {\n \"default\": \"gemma-4-26b-a4b-it\"\n },\n \"gemini-3.1-pro-preview\": {\n \"default\": \"gemini-3.1-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n }\n ]\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"default\": \"gemini-3.1-pro-preview-customtools\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n }\n ]\n },\n \"gemini-3-flash-preview\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false,\n \"useGemini3_5Flash\": true\n },\n \"target\": \"gemini-3.5-flash\"\n },\n {\n \"condition\": {\n \"hasAccessToPreview\": false,\n \"useGemini3_5Flash\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"gemini-3.5-flash\": {\n \"default\": \"gemini-3.5-flash\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_5Flash\": false,\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n },\n {\n \"condition\": {\n \"useGemini3_5Flash\": false\n },\n \"target\": \"gemini-3-flash-preview\"\n }\n ]\n },\n \"gemini-2.5-flash\": {\n \"default\": \"gemini-2.5-flash\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_5Flash\": true\n },\n \"target\": \"gemini-3.5-flash\"\n }\n ]\n },\n \"gemini-3-pro-preview\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"gemini-3.1-flash-lite\": {\n \"default\": \"gemini-3.1-flash-lite\"\n },\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_5Flash\": true\n },\n \"target\": \"gemini-3.5-flash\"\n },\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"flash-lite\": {\n \"default\": \"gemini-3.1-flash-lite\"\n },\n \"auto-gemini-3\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-2.5\": {\n \"default\": \"gemini-2.5-pro\"\n }\n}`", "default": { "gemma-4-31b-it": { "default": "gemma-4-31b-it" @@ -2071,37 +2184,50 @@ "contexts": [ { "condition": { - "hasAccessToPreview": false + "hasAccessToPreview": false, + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, + { + "condition": { + "hasAccessToPreview": false, + "useGemini3_5Flash": false }, "target": "gemini-2.5-flash" } ] }, - "gemini-3-pro-preview": { - "default": "gemini-3-pro-preview", + "gemini-3.5-flash": { + "default": "gemini-3.5-flash", "contexts": [ { "condition": { + "useGemini3_5Flash": false, "hasAccessToPreview": false }, - "target": "gemini-2.5-pro" + "target": "gemini-2.5-flash" }, { "condition": { - "useGemini3_1": true, - "useCustomTools": true + "useGemini3_5Flash": false }, - "target": "gemini-3.1-pro-preview-customtools" - }, - { - "condition": { - "useGemini3_1": true - }, - "target": "gemini-3.1-pro-preview" + "target": "gemini-3-flash-preview" } ] }, - "auto-gemini-3": { + "gemini-2.5-flash": { + "default": "gemini-2.5-flash", + "contexts": [ + { + "condition": { + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + } + ] + }, + "gemini-3-pro-preview": { "default": "gemini-3-pro-preview", "contexts": [ { @@ -2173,23 +2299,18 @@ } ] }, - "auto-gemini-2.5": { - "default": "gemini-2.5-pro" - }, - "gemini-3.1-flash-lite-preview": { - "default": "gemini-3.1-flash-lite-preview", - "contexts": [ - { - "condition": { - "useGemini3_1FlashLite": false - }, - "target": "gemini-2.5-flash-lite" - } - ] + "gemini-3.1-flash-lite": { + "default": "gemini-3.1-flash-lite" }, "flash": { "default": "gemini-3-flash-preview", "contexts": [ + { + "condition": { + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, { "condition": { "hasAccessToPreview": false @@ -2199,15 +2320,34 @@ ] }, "flash-lite": { - "default": "gemini-2.5-flash-lite", + "default": "gemini-3.1-flash-lite" + }, + "auto-gemini-3": { + "default": "gemini-3-pro-preview", "contexts": [ { "condition": { - "useGemini3_1FlashLite": true + "hasAccessToPreview": false }, - "target": "gemini-3.1-flash-lite-preview" + "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" } }, "type": "object", @@ -2218,22 +2358,28 @@ "classifierIdResolutions": { "title": "Classifier ID Resolutions", "description": "Rules for resolving classifier tiers (flash, pro) to concrete model IDs.", - "markdownDescription": "Rules for resolving classifier tiers (flash, pro) to concrete model IDs.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-2.5\",\n \"gemini-2.5-pro\"\n ]\n },\n \"target\": \"gemini-2.5-flash\"\n },\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-3\",\n \"gemini-3-pro-preview\"\n ]\n },\n \"target\": \"gemini-3-flash-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-2.5\",\n \"gemini-2.5-pro\"\n ]\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n }\n}`", + "markdownDescription": "Rules for resolving classifier tiers (flash, pro) to concrete model IDs.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_5Flash\": true\n },\n \"target\": \"gemini-3.5-flash\"\n },\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n },\n {\n \"condition\": {\n \"requestedModels\": [\n \"gemini-2.5-pro\",\n \"auto-gemini-2.5\"\n ]\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"requestedModels\": [\n \"gemini-2.5-pro\",\n \"auto-gemini-2.5\"\n ]\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n }\n}`", "default": { "flash": { "default": "gemini-3-flash-preview", "contexts": [ { "condition": { - "requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"] + "useGemini3_5Flash": true + }, + "target": "gemini-3.5-flash" + }, + { + "condition": { + "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" } ] }, @@ -2242,7 +2388,13 @@ "contexts": [ { "condition": { - "requestedModels": ["auto-gemini-2.5", "gemini-2.5-pro"] + "hasAccessToPreview": false + }, + "target": "gemini-2.5-pro" + }, + { + "condition": { + "requestedModels": ["gemini-2.5-pro", "auto-gemini-2.5"] }, "target": "gemini-2.5-pro" }, @@ -2270,7 +2422,7 @@ "modelChains": { "title": "Model Chains", "description": "Availability policy chains defining fallback behavior for models.", - "markdownDescription": "Availability policy chains defining fallback behavior for models.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"lite\": [\n {\n \"model\": \"gemini-2.5-flash-lite\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-pro\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ]\n}`", + "markdownDescription": "Availability policy chains defining fallback behavior for models.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"lite\": [\n {\n \"model\": \"flash-lite\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-pro\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ]\n}`", "default": { "preview": [ { @@ -2412,7 +2564,7 @@ ], "lite": [ { - "model": "gemini-2.5-flash-lite", + "model": "flash-lite", "actions": { "terminal": "silent", "transient": "silent",