Compare commits

...

3 Commits

Author SHA1 Message Date
Sri Pasumarthi eb42ff6086 feat: promote Gemini 3.1 Flash Lite to GA and support Gemini 3.5 Flash 2026-06-11 13:33:53 -07:00
davidapierce a57f33acbc Update default flash lite tool model 2026-06-04 15:40:49 -07:00
gemini-cli-robot f54f63cd4c chore(release): v0.42.0-nightly.20260512.gc987b9939 2026-05-12 20:11:07 +00:00
86 changed files with 2748 additions and 694 deletions
+135 -59
View File
@@ -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": {
@@ -563,6 +581,18 @@ their corresponding top-level category object in your `settings.json` file.
"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": {
@@ -587,10 +617,16 @@ 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": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"maxOutputTokens": 1024,
"thinkingConfig": {
@@ -602,7 +638,7 @@ their corresponding top-level category object in your `settings.json` file.
"prompt-completion": {
"extends": "base",
"modelConfig": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0.3,
"maxOutputTokens": 16000,
@@ -615,7 +651,7 @@ their corresponding top-level category object in your `settings.json` file.
"fast-ack-helper": {
"extends": "base",
"modelConfig": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0.2,
"maxOutputTokens": 120,
@@ -628,7 +664,7 @@ their corresponding top-level category object in your `settings.json` file.
"edit-corrector": {
"extends": "base",
"modelConfig": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"thinkingConfig": {
"thinkingBudget": 0
@@ -639,7 +675,7 @@ their corresponding top-level category object in your `settings.json` file.
"summarizer-default": {
"extends": "base",
"modelConfig": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"maxOutputTokens": 2000
}
@@ -648,7 +684,7 @@ their corresponding top-level category object in your `settings.json` file.
"summarizer-shell": {
"extends": "base",
"modelConfig": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"maxOutputTokens": 2000
}
@@ -725,7 +761,7 @@ their corresponding top-level category object in your `settings.json` file.
},
"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": {
@@ -779,10 +815,10 @@ their corresponding top-level category object in your `settings.json` file.
```json
{
"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,
@@ -829,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",
@@ -882,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
@@ -918,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
}
}
```
@@ -990,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": [
{
@@ -1092,23 +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"
},
"flash": {
"default": "gemini-3-flash-preview",
"contexts": [
{
"condition": {
"useGemini3_5Flash": true
},
"target": "gemini-3.5-flash"
},
{
"condition": {
"hasAccessToPreview": false
@@ -1118,15 +1163,34 @@ their corresponding top-level category object in your `settings.json` file.
]
},
"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"
}
}
```
@@ -1145,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"
}
]
},
@@ -1162,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"
},
@@ -1333,7 +1409,7 @@ their corresponding top-level category object in your `settings.json` file.
],
"lite": [
{
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"actions": {
"terminal": "silent",
"transient": "silent",
+5
View File
@@ -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();
+24 -4
View File
@@ -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)
+1 -3
View File
@@ -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
+41 -12
View File
@@ -1,12 +1,12 @@
{
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"workspaces": [
"packages/*"
],
@@ -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"
}
@@ -18077,7 +18104,7 @@
},
"packages/a2a-server": {
"name": "@google/gemini-cli-a2a-server",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
"@google-cloud/storage": "^7.16.0",
@@ -18206,7 +18233,7 @@
},
"packages/cli": {
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"license": "Apache-2.0",
"dependencies": {
"@agentclientprotocol/sdk": "^0.16.1",
@@ -18354,7 +18381,7 @@
},
"packages/core": {
"name": "@google/gemini-cli-core",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"license": "Apache-2.0",
"dependencies": {
"@a2a-js/sdk": "0.3.11",
@@ -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"
},
@@ -18665,7 +18694,7 @@
},
"packages/devtools": {
"name": "@google/gemini-cli-devtools",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"license": "Apache-2.0",
"dependencies": {
"ws": "^8.16.0"
@@ -18680,7 +18709,7 @@
},
"packages/sdk": {
"name": "@google/gemini-cli-sdk",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -18711,7 +18740,7 @@
},
"packages/test-utils": {
"name": "@google/gemini-cli-test-utils",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"license": "Apache-2.0",
"dependencies": {
"@google/gemini-cli-core": "file:../core",
@@ -18743,7 +18772,7 @@
},
"packages/vscode-ide-companion": {
"name": "gemini-cli-vscode-ide-companion",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.23.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"engines": {
"node": ">=20.0.0"
},
@@ -14,7 +14,7 @@
"url": "git+https://github.com/google-gemini/gemini-cli.git"
},
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.42.0-nightly.20260428.g59b2dea0e"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.42.0-nightly.20260512.gc987b9939"
},
"scripts": {
"start": "cross-env NODE_ENV=development node scripts/start.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-a2a-server",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"description": "Gemini CLI A2A Server",
"repository": {
"type": "git",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"description": "Gemini CLI",
"license": "Apache-2.0",
"repository": {
@@ -27,7 +27,7 @@
"dist"
],
"config": {
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.42.0-nightly.20260428.g59b2dea0e"
"sandboxImageUri": "us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.42.0-nightly.20260512.gc987b9939"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.16.1",
+6 -11
View File
@@ -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-preview',
name: 'gemini-3.1-flash-lite-preview',
}),
]),
);
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 () => {
+28 -31
View File
@@ -10,8 +10,7 @@ import {
type ToolCallConfirmationDetails,
Kind,
ApprovalMode,
DEFAULT_GEMINI_MODEL_AUTO,
PREVIEW_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
@@ -19,10 +18,11 @@ import {
PREVIEW_GEMINI_MODEL,
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
PREVIEW_GEMINI_FLASH_MODEL,
PREVIEW_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: PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
title: getDisplayString(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL),
value: PREVIEW_GEMINI_FLASH_LITE_MODEL,
title: getDisplayString(PREVIEW_GEMINI_FLASH_LITE_MODEL, config),
});
}
@@ -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,
PREVIEW_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<typeof import('@google/gemini-cli-core')>();
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,7 +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',
PREVIEW_GEMINI_FLASH_LITE_MODEL: 'none',
};
});
@@ -62,7 +67,6 @@ describe('<ModelDialog />', () => {
getHasAccessToPreviewModel: () => boolean;
getIdeMode: () => boolean;
getGemini31LaunchedSync: () => boolean;
getGemini31FlashLiteLaunchedSync: () => boolean;
getProModelNoAccess: () => Promise<boolean>;
getProModelNoAccessSync: () => boolean;
getExperimentalGemma: () => boolean;
@@ -83,7 +87,6 @@ describe('<ModelDialog />', () => {
getHasAccessToPreviewModel: mockGetHasAccessToPreviewModel,
getIdeMode: () => false,
getGemini31LaunchedSync: mockGetGemini31LaunchedSync,
getGemini31FlashLiteLaunchedSync: mockGetGemini31FlashLiteLaunchedSync,
getProModelNoAccess: mockGetProModelNoAccess,
getProModelNoAccessSync: mockGetProModelNoAccessSync,
getExperimentalGemma: () => false,
@@ -93,17 +96,15 @@ describe('<ModelDialog />', () => {
beforeEach(() => {
vi.resetAllMocks();
mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO);
mockGetModel.mockReturnValue(GEMINI_MODEL_ALIAS_AUTO);
mockGetHasAccessToPreviewModel.mockReturnValue(false);
mockGetGemini31LaunchedSync.mockReturnValue(false);
mockGetGemini31FlashLiteLaunchedSync.mockReturnValue(false);
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;
});
});
@@ -153,17 +154,13 @@ describe('<ModelDialog />', () => {
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(
PREVIEW_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();
@@ -234,7 +231,7 @@ describe('<ModelDialog />', () => {
await waitFor(() => {
expect(mockSetModel).toHaveBeenCalledWith(
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
true, // Session only by default
);
expect(mockOnClose).toHaveBeenCalled();
@@ -292,7 +289,7 @@ describe('<ModelDialog />', () => {
await waitFor(() => {
expect(mockSetModel).toHaveBeenCalledWith(
DEFAULT_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_AUTO,
false, // Persist enabled
);
expect(mockOnClose).toHaveBeenCalled();
@@ -355,7 +352,7 @@ describe('<ModelDialog />', () => {
mockGetModel.mockReturnValue(DEFAULT_GEMINI_MODEL);
mockGetDisplayString.mockImplementation((val: string) => {
if (val === DEFAULT_GEMINI_MODEL) return 'My Custom Model Display';
if (val === 'auto-gemini-2.5') return 'Auto (Gemini 2.5)';
if (val === 'auto') return 'Auto';
return val;
});
const { lastFrame, unmount } = await renderComponent();
@@ -369,9 +366,9 @@ describe('<ModelDialog />', () => {
mockGetHasAccessToPreviewModel.mockReturnValue(true);
});
it('shows Auto (Preview) in main view when access is granted', async () => {
it('shows Auto in main view when access is granted', async () => {
const { lastFrame, unmount } = await renderComponent();
expect(lastFrame()).toContain('Auto (Preview)');
expect(lastFrame()).toContain('Auto');
unmount();
});
@@ -449,7 +446,7 @@ describe('<ModelDialog />', () => {
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);
@@ -468,7 +465,8 @@ describe('<ModelDialog />', () => {
await waitUntilReady();
const output = lastFrame();
expect(output).toContain(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL);
expect(output).not.toContain(PREVIEW_GEMINI_FLASH_LITE_MODEL);
expect(output).toContain(DEFAULT_GEMINI_FLASH_LITE_MODEL);
unmount();
});
});
+68 -49
View File
@@ -13,12 +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,
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,
@@ -27,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';
@@ -63,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;
@@ -87,16 +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,
PREVIEW_GEMINI_FLASH_MODEL,
];
PREVIEW_GEMINI_FLASH_LITE_MODEL,
previewFlashModel,
].filter((m) => m !== 'none');
if (manualModels.includes(preferredModel)) {
return preferredModel;
}
@@ -121,7 +125,6 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
},
{ isActive: true },
);
const mainOptions = useMemo(() => {
// --- DYNAMIC PATH ---
if (
@@ -132,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,
@@ -161,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',
@@ -177,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,
]);
@@ -208,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,
@@ -225,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,
},
];
@@ -248,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,
},
);
@@ -271,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: PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
title: getDisplayString(PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL),
key: PREVIEW_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,
});
}
@@ -301,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<string>();
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(() => {
@@ -353,6 +353,49 @@ describe('<ModelStatsDisplay />', () => {
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(
{
@@ -299,7 +299,7 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
},
...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
@@ -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'.
@@ -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
@@ -131,6 +131,33 @@ describe('<StatsDisplay />', () => {
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: {
@@ -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<ModelUsageTableProps> = ({ 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(),
@@ -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`] = `
"
▝▜▄ ▗█▀▀▜▙▝█▛▀▀▌▜██▖▟██▘▜█▘▜██▖▝█▛▝█▛
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="88" viewBox="0 0 920 88">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="88" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#4796e4" textLength="9" lengthAdjust="spacingAndGlyphs">L</text>
<text x="27" y="19" fill="#6688d9" textLength="9" lengthAdjust="spacingAndGlyphs">i</text>
<text x="36" y="19" fill="#847ace" textLength="9" lengthAdjust="spacingAndGlyphs">n</text>
<text x="45" y="19" fill="#9974b4" textLength="9" lengthAdjust="spacingAndGlyphs">e</text>
<text x="63" y="19" fill="#c3677f" textLength="9" lengthAdjust="spacingAndGlyphs">1</text>
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="36" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Line 2</text>
<text x="711" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -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 │
@@ -11,6 +11,17 @@ exports[`<Checklist /> > renders expanded view correctly 1`] = `
"
`;
exports[`<Checklist /> > renders expanded view correctly 2`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Test List 1/3 completed (toggle me)
✓ Task 1
» Task 2
☐ Task 3
✗ Task 4
"
`;
exports[`<Checklist /> > renders summary view correctly (collapsed) 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Test List 1/3 completed (toggle me) » Task 2
@@ -5,6 +5,11 @@ exports[`<ContextSummaryDisplay /> > should not render empty parts 1`] = `
"
`;
exports[`<ContextSummaryDisplay /> > should not render empty parts 2`] = `
" 1 open file (F4 to view)
"
`;
exports[`<ContextSummaryDisplay /> > 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
"
@@ -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 │
@@ -215,3 +215,26 @@ exports[`<ModelStatsDisplay /> > should render "no API calls" message when there
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`<ModelStatsDisplay /> > 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 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -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
@@ -292,3 +292,30 @@ exports[`<StatsDisplay /> > renders role breakdown correctly under models 1`] =
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`<StatsDisplay /> > 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 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -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`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
@@ -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
"
`;
@@ -41,6 +41,27 @@ exports[`<ToolStatsDisplay /> > should display stats for multiple tools correctl
"
`;
exports[`<ToolStatsDisplay /> > 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[`<ToolStatsDisplay /> > should handle large values without wrapping or overlapping 1`] = `
"╭────────────────────────────────────────────────────────────────────╮
│ │
@@ -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
"
@@ -16,6 +16,13 @@ exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > renders pending st
"
`;
exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > renders pending state with renderMarkdown=true 2`] = `
"✦ Test bold and code markdown
1 const x = 1;
"
`;
exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=false '(raw markdown with syntax highlightin…' 1`] = `
"✦ Test **bold** and \`code\` markdown
@@ -40,3 +47,12 @@ exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > wraps long lines c
truncation
"
`;
exports[`<GeminiMessage /> - Raw Markdown Display Snapshots > wraps long lines correctly in raw markdown mode 2`] = `
"✦ This is a long
line that should
wrap correctly
without
truncation
"
`;
@@ -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
@@ -16,6 +16,14 @@ exports[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderM
"
`;
exports[`<ToolMessage /> - 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[`<ToolMessage /> - Raw Markdown Display Snapshots > renders with renderMarkdown=true, useAlternateBuffer=false '(constrained height, regular buffer -…' 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ ✓ test-tool A tool for testing │
@@ -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
@@ -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!
@@ -9,6 +9,15 @@ Note: Newest last, oldest first
"
`;
exports[`<ChatList /> > handles invalid date formats gracefully 2`] = `
"List of saved conversations:
- bad-date-chat (Invalid Date)
Note: Newest last, oldest first
"
`;
exports[`<ChatList /> > renders correctly with a list of chats 1`] = `
"List of saved conversations:
@@ -30,6 +30,20 @@ BackgroundTaskDisplay
Notifications
CopyModeWarning
Composer
ExitWarning
"
`;
exports[`<DefaultAppLayout /> > shows BackgroundTaskDisplay when StreamingState is NOT WaitingForConfirmation 2`] = `
"MainContent
BackgroundTaskDisplay
Notifications
CopyModeWarning
Composer
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"description": "Gemini CLI Core",
"license": "Apache-2.0",
"repository": {
@@ -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;
@@ -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);
@@ -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({
@@ -30,14 +30,17 @@ const createMockConfig = (overrides: Partial<Config> = {}): 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),
});
+13 -22
View File
@@ -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 {
@@ -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();
});
});
});
+10 -3
View File
@@ -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)) {
@@ -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 =
+55 -16
View File
@@ -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');
});
});
+63 -37
View File
@@ -81,14 +81,11 @@ import { tokenLimit } from '../core/tokenLimits.js';
import {
DEFAULT_GEMINI_EMBEDDING_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
isAutoModel,
isPreviewModel,
isGemini2Model,
PREVIEW_GEMINI_FLASH_MODEL,
PREVIEW_GEMINI_MODEL,
PREVIEW_GEMINI_MODEL_AUTO,
resolveModel,
} from './models.js';
import { shouldAttemptBrowserLaunch } from '../utils/browser.js';
@@ -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<boolean> {
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<void> {
+103 -50
View File
@@ -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: {
@@ -89,6 +107,18 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
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: {
@@ -115,10 +145,16 @@ 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: {
model: 'gemini-2.5-flash-lite',
model: 'flash-lite',
generateContentConfig: {
maxOutputTokens: 1024,
thinkingConfig: {
@@ -130,7 +166,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
'prompt-completion': {
extends: 'base',
modelConfig: {
model: 'gemini-2.5-flash-lite',
model: 'flash-lite',
generateContentConfig: {
temperature: 0.3,
maxOutputTokens: 16000,
@@ -143,7 +179,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
'fast-ack-helper': {
extends: 'base',
modelConfig: {
model: 'gemini-2.5-flash-lite',
model: 'flash-lite',
generateContentConfig: {
temperature: 0.2,
maxOutputTokens: 120,
@@ -156,7 +192,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
'edit-corrector': {
extends: 'base',
modelConfig: {
model: 'gemini-2.5-flash-lite',
model: 'flash-lite',
generateContentConfig: {
thinkingConfig: {
thinkingBudget: 0,
@@ -167,7 +203,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
'summarizer-default': {
extends: 'base',
modelConfig: {
model: 'gemini-2.5-flash-lite',
model: 'flash-lite',
generateContentConfig: {
maxOutputTokens: 2000,
},
@@ -176,7 +212,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
'summarizer-shell': {
extends: 'base',
modelConfig: {
model: 'gemini-2.5-flash-lite',
model: 'flash-lite',
generateContentConfig: {
maxOutputTokens: 2000,
},
@@ -246,7 +282,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
'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': {
@@ -287,10 +323,10 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
],
modelDefinitions: {
// Concrete Models
'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, multimodalToolUse: true },
},
@@ -322,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',
@@ -362,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: {
@@ -386,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: {
@@ -432,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' },
@@ -493,21 +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',
},
flash: {
default: 'gemini-3-flash-preview',
contexts: [
{ condition: { useGemini3_5Flash: true }, target: 'gemini-3.5-flash' },
{
condition: { hasAccessToPreview: false },
target: 'gemini-2.5-flash',
@@ -515,28 +554,38 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
],
},
'flash-lite': {
default: 'gemini-2.5-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: true },
target: 'gemini-3.1-flash-lite-preview',
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',
},
],
},
@@ -544,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',
},
{
@@ -699,7 +752,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
],
lite: [
{
model: 'gemini-2.5-flash-lite',
model: 'flash-lite',
actions: {
terminal: 'silent',
transient: 'silent',
+449 -71
View File
@@ -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,7 +30,7 @@ import {
DEFAULT_GEMINI_MODEL_AUTO,
isActiveModel,
PREVIEW_GEMINI_3_1_MODEL,
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
PREVIEW_GEMINI_FLASH_LITE_MODEL,
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
isPreviewModel,
isProModel,
@@ -66,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,
},
];
@@ -104,7 +98,6 @@ describe('Dynamic Configuration Parity', () => {
const legacy = resolveModel(
model,
flags.useGemini3_1,
flags.useGemini3_1FlashLite,
flags.useCustomToolModel,
hasAccess,
mockLegacyConfig,
@@ -112,7 +105,6 @@ describe('Dynamic Configuration Parity', () => {
const dynamic = resolveModel(
model,
flags.useGemini3_1,
flags.useGemini3_1FlashLite,
flags.useCustomToolModel,
hasAccess,
mockDynamicConfig,
@@ -151,7 +143,6 @@ describe('Dynamic Configuration Parity', () => {
anchor,
tier,
flags.useGemini3_1,
flags.useGemini3_1FlashLite,
flags.useCustomToolModel,
hasAccess,
mockLegacyConfig,
@@ -160,7 +151,6 @@ describe('Dynamic Configuration Parity', () => {
anchor,
tier,
flags.useGemini3_1,
flags.useGemini3_1FlashLite,
flags.useCustomToolModel,
hasAccess,
mockDynamicConfig,
@@ -228,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', () => {
@@ -357,9 +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 PREVIEW_GEMINI_FLASH_LITE_MODEL for PREVIEW_GEMINI_FLASH_LITE_MODEL', () => {
expect(getDisplayString(PREVIEW_GEMINI_FLASH_LITE_MODEL)).toBe(
PREVIEW_GEMINI_FLASH_LITE_MODEL,
);
});
@@ -404,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);
});
@@ -418,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(PREVIEW_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', () => {
@@ -451,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,
);
});
});
});
@@ -581,7 +582,6 @@ describe('resolveClassifierModel', () => {
PREVIEW_GEMINI_MODEL_AUTO,
GEMINI_MODEL_ALIAS_PRO,
true,
false,
true,
),
).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
@@ -598,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', () => {
@@ -624,37 +621,48 @@ describe('isActiveModel', () => {
expect(isActiveModel(DEFAULT_GEMINI_MODEL, true)).toBe(true);
});
it('should return true for PREVIEW_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(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(PREVIEW_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,
);
@@ -667,8 +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);
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);
});
});
});
+147 -37
View File
@@ -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,18 +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 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';
@@ -69,16 +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,
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.
@@ -92,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)
@@ -119,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)) {
@@ -140,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
@@ -158,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
? PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL
: DEFAULT_GEMINI_FLASH_LITE_MODEL;
resolved = DEFAULT_GEMINI_FLASH_LITE_MODEL;
break;
}
default: {
@@ -173,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:
@@ -190,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;
}
@@ -199,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.
@@ -213,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(
@@ -224,9 +309,9 @@ export function resolveClassifierModel(
requestedModel,
{
useGemini3_1,
useGemini3_1FlashLite,
useCustomTools: useCustomToolModel,
hasAccessToPreview,
useGemini3_5Flash,
},
);
}
@@ -236,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,
);
}
@@ -266,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:
@@ -277,11 +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 PREVIEW_GEMINI_FLASH_LITE_MODEL:
return PREVIEW_GEMINI_FLASH_LITE_MODEL;
default:
return model;
}
@@ -298,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
@@ -311,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
);
}
@@ -345,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'
@@ -380,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-')
@@ -453,18 +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) {
return useGemini3_1FlashLite;
if (model === PREVIEW_GEMINI_FLASH_LITE_MODEL) {
return false;
}
if (useGemini3_1) {
if (model === PREVIEW_GEMINI_MODEL) {
@@ -482,3 +588,7 @@ export function isActiveModel(
);
}
}
export const CCPA_AI_MODEL_MAPPINGS: Record<string, string> = {
[DEFAULT_GEMINI_3_5_FLASH_MODEL]: SECONDARY_GEMINI_3_5_FLASH_MODEL,
};
@@ -30,7 +30,7 @@ import {
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';
@@ -106,14 +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 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';
}
+1 -1
View File
@@ -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,
);
}
+191 -2
View File
@@ -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', () => {
+11 -8
View File
@@ -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,
);
@@ -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',
+3 -6
View File
@@ -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
@@ -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',
@@ -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,
);
});
});
@@ -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<string, string>,
) {}
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<T extends { model?: string }>(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<GenerateContentResponse> {
return this.wrapped.generateContent(
this.mapModel(request),
userPromptId,
role,
);
}
generateContentStream(
request: GenerateContentParameters,
userPromptId: string,
role: LlmRole,
): Promise<AsyncGenerator<GenerateContentResponse>> {
return this.wrapped.generateContentStream(
this.mapModel(request),
userPromptId,
role,
);
}
countTokens(request: CountTokensParameters): Promise<CountTokensResponse> {
return this.wrapped.countTokens(this.mapModel(request));
}
embedContent(request: EmbedContentParameters): Promise<EmbedContentResponse> {
return this.wrapped.embedContent(this.mapModel(request));
}
}
+4 -9
View File
@@ -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'");
@@ -77,6 +77,7 @@ const createMockConfig = (overrides: Partial<Config> = {}): Config =>
getModel: vi.fn(() => MOCK_PRO_MODEL),
getUserTier: vi.fn(() => undefined),
isInteractive: vi.fn(() => false),
getHasAccessToPreviewModel: vi.fn(() => true),
...overrides,
}) as unknown as Config;
+2 -2
View File
@@ -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;
@@ -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);
});
});
@@ -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,
@@ -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);
});
});
});
@@ -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,
),
);
@@ -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,
@@ -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);
@@ -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);
});
});
@@ -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 {
@@ -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);
});
});
});
@@ -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,
),
);
@@ -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,
@@ -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 = {
@@ -9,8 +9,8 @@ import type { ModelPolicy } from '../availability/modelPolicy.js';
import {
getDisplayString,
PREVIEW_GEMINI_3_1_MODEL,
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
isProModel,
getAutoModelDescription,
} from '../config/models.js';
// The primary key for the ModelConfig is the model string. However, we also
@@ -97,6 +97,7 @@ export interface ModelResolution {
export interface ResolutionContext {
useGemini3_1?: boolean;
useGemini3_1FlashLite?: boolean;
useGemini3_5Flash?: boolean;
useCustomTools?: boolean;
hasAccessToPreview?: boolean;
hasAccessToProModel?: boolean;
@@ -107,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. */
@@ -155,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]) => {
@@ -164,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]) => {
@@ -185,15 +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 && !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,
@@ -248,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':
@@ -337,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.
*
@@ -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": {
@@ -97,6 +133,30 @@
"topK": 64
}
},
"gemini-3.1-flash-lite": {
"model": "gemini-3.1-flash-lite",
"generateContentConfig": {
"temperature": 1,
"topP": 0.95,
"thinkingConfig": {
"includeThoughts": true,
"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
}
},
"gemma-4-31b-it": {
"model": "gemma-4-31b-it",
"generateContentConfig": {
@@ -135,8 +195,15 @@
"topP": 1
}
},
"gemini-3.5-flash-base": {
"model": "gemini-3.5-flash",
"generateContentConfig": {
"temperature": 0,
"topP": 1
}
},
"classifier": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@@ -147,7 +214,7 @@
}
},
"prompt-completion": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0.3,
"topP": 1,
@@ -158,7 +225,7 @@
}
},
"fast-ack-helper": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0.2,
"topP": 1,
@@ -169,7 +236,7 @@
}
},
"edit-corrector": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@@ -179,7 +246,7 @@
}
},
"summarizer-default": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@@ -187,7 +254,7 @@
}
},
"summarizer-shell": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@@ -273,7 +340,7 @@
"generateContentConfig": {}
},
"chat-compression-3.1-flash-lite": {
"model": "gemini-3.1-flash-lite-preview",
"model": "gemini-3.1-flash-lite",
"generateContentConfig": {}
},
"chat-compression-2.5-pro": {
@@ -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": {
@@ -97,6 +133,30 @@
"topK": 64
}
},
"gemini-3.1-flash-lite": {
"model": "gemini-3.1-flash-lite",
"generateContentConfig": {
"temperature": 1,
"topP": 0.95,
"thinkingConfig": {
"includeThoughts": true,
"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
}
},
"gemma-4-31b-it": {
"model": "gemma-4-31b-it",
"generateContentConfig": {
@@ -135,8 +195,15 @@
"topP": 1
}
},
"gemini-3.5-flash-base": {
"model": "gemini-3.5-flash",
"generateContentConfig": {
"temperature": 0,
"topP": 1
}
},
"classifier": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@@ -147,7 +214,7 @@
}
},
"prompt-completion": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0.3,
"topP": 1,
@@ -158,7 +225,7 @@
}
},
"fast-ack-helper": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0.2,
"topP": 1,
@@ -169,7 +236,7 @@
}
},
"edit-corrector": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@@ -179,7 +246,7 @@
}
},
"summarizer-default": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@@ -187,7 +254,7 @@
}
},
"summarizer-shell": {
"model": "gemini-2.5-flash-lite",
"model": "flash-lite",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@@ -273,7 +340,7 @@
"generateContentConfig": {}
},
"chat-compression-3.1-flash-lite": {
"model": "gemini-3.1-flash-lite-preview",
"model": "gemini-3.1-flash-lite",
"generateContentConfig": {}
},
"chat-compression-2.5-pro": {
+17 -11
View File
@@ -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
}
}
}
}
+4
View File
@@ -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;
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-devtools",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"license": "Apache-2.0",
"type": "module",
"main": "dist/src/index.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-sdk",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"description": "Gemini CLI SDK",
"license": "Apache-2.0",
"repository": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-test-utils",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "gemini-cli-vscode-ide-companion",
"displayName": "Gemini CLI Companion",
"description": "Enable Gemini CLI with direct access to your IDE workspace.",
"version": "0.42.0-nightly.20260428.g59b2dea0e",
"version": "0.42.0-nightly.20260512.gc987b9939",
"publisher": "google",
"icon": "assets/icon.png",
"repository": {
File diff suppressed because one or more lines are too long