fix(availability): keep Auto chains self-contained on quota exhaustion

When Flash quota was exhausted in Auto (Gemini 3) or Auto (Gemini 2.5),
the policy chain ran out of fallbacks and the CLI surfaced the "Select
Model" dialog, breaking the Auto contract — the user shouldn't have to
pick a model manually mid-task.

- Add Flash-Lite as last-resort in both preview and default chains
  (preview: gemini-3.1-flash-lite-preview, default: gemini-2.5-flash-lite)
- Mark every policy in both chains with SILENT_ACTIONS so fallbacks stay
  internal to Auto mode instead of prompting the user
- Mirror the same shape in DYNAMIC modelChains.preview/default for
  legacy/dynamic parity (existing parity test enforces this)
- Regenerate resolved-aliases golden files that were stale after earlier
  web-search/web-fetch/web-fetch-fallback flash-lite-base switches
This commit is contained in:
kazuki 2026-04-20 19:57:48 +09:00
parent 6060ca2bc1
commit 8283b33194
15 changed files with 4045 additions and 1117 deletions

33
manifest.toml Normal file
View file

@ -0,0 +1,33 @@
# Auto-generated by airis init
# Edit this file to configure your workspace
version = 1
mode = "docker-first"
[project]
id = "gemini-cli"
description = "Google Gemini CLI fork (PR #25684 — Auto mode self-contained fallback)"
[workspace]
name = "gemini-cli"
[packages]
workspaces = ["packages/*"]
[packages.catalog]
"typescript" = "^5.9.3"
"eslint" = "^9.39.4"
"prettier" = "^3.8.3"
"vitest" = "^3.2.4"
[guards]
deny = ["npm", "yarn", "pnpm"]
[commands]
install = "docker compose run --rm node pnpm install"
dev = "docker compose up"
build = "docker compose run --rm node pnpm build"
test = "docker compose run --rm node pnpm test"
[versioning]
strategy = "conventional-commits"

2502
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.40.0-nightly.20260414.g5b1f7375a",
"version": "0.40.0-pr25684-c3cd0c7",
"engines": {
"node": ">=20.0.0"
},
@ -94,52 +94,52 @@
],
"devDependencies": {
"@agentclientprotocol/sdk": "^0.16.1",
"read-package-up": "^11.0.0",
"@octokit/rest": "^22.0.0",
"@octokit/rest": "^22.0.1",
"@types/marked": "^5.0.2",
"@types/mime-types": "^3.0.1",
"@types/minimatch": "^5.1.2",
"@types/mock-fs": "^4.13.4",
"@types/prompts": "^2.4.9",
"@types/proper-lockfile": "^4.1.4",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/shell-quote": "^1.7.5",
"@types/ws": "^8.18.1",
"@vitest/coverage-v8": "^3.1.1",
"@vitest/eslint-plugin": "^1.3.4",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/eslint-plugin": "^1.6.16",
"asciichart": "^1.5.25",
"cross-env": "^7.0.3",
"depcheck": "^1.4.7",
"domexception": "^4.0.0",
"esbuild": "^0.25.0",
"esbuild": "^0.25.12",
"esbuild-plugin-wasm": "^1.1.0",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-headers": "^1.3.3",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-headers": "^1.3.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"glob": "^12.0.0",
"globals": "^16.0.0",
"google-artifactregistry-auth": "^3.4.0",
"globals": "^16.5.0",
"google-artifactregistry-auth": "^3.5.0",
"husky": "^9.1.7",
"json": "^11.0.0",
"lint-staged": "^16.1.6",
"memfs": "^4.42.0",
"lint-staged": "^16.4.0",
"memfs": "^4.57.2",
"mnemonist": "^0.40.3",
"mock-fs": "^5.5.0",
"msw": "^2.10.4",
"msw": "^2.13.4",
"npm-run-all": "^4.1.5",
"prettier": "^3.5.3",
"react-devtools-core": "^6.1.2",
"react-dom": "^19.2.0",
"semver": "^7.7.2",
"strip-ansi": "^7.1.2",
"prettier": "^3.8.3",
"react-devtools-core": "^6.1.5",
"react-dom": "^19.2.5",
"read-package-up": "^11.0.0",
"semver": "^7.7.4",
"strip-ansi": "^7.2.0",
"ts-prune": "^0.10.3",
"tsx": "^4.20.3",
"typescript": "^5.8.3",
"typescript-eslint": "^8.30.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.58.2",
"vitest": "^3.2.4",
"yargs": "^17.7.2"
},
@ -149,7 +149,7 @@
"node-fetch-native": "^1.6.7",
"proper-lockfile": "^4.1.2",
"punycode": "^2.3.1",
"simple-git": "^3.28.0"
"simple-git": "^3.36.0"
},
"optionalDependencies": {
"@github/keytar": "^7.10.6",
@ -159,7 +159,7 @@
"@lydell/node-pty-linux-x64": "1.1.0",
"@lydell/node-pty-win32-arm64": "1.1.0",
"@lydell/node-pty-win32-x64": "1.1.0",
"node-pty": "^1.0.0"
"node-pty": "^1.1.0"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [

View file

@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli",
"version": "0.40.0-nightly.20260414.g5b1f7375a",
"version": "0.40.0-pr25684-c3cd0c7",
"description": "Gemini CLI",
"license": "Apache-2.0",
"repository": {

View file

@ -168,6 +168,156 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 4`] = `
"
ERROR Cannot read properties of null (reading 'useContext')
/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/react/cjs/react.development.js:1212:25
1209: console.error(
1210 "Calling useContext(Context.Consumer) is not supported and will cause bugs. Did you
: mean to call useContext(Context) instead?"
1211: );
1212: return dispatcher.useContext(Context);
1213: };
1214: exports.useDebugValue = function (value, formatterFn) {
1215: return resolveDispatcher().useDebugValue(value, formatterFn);
-process.env.NODE_ENV.exports
.useContext (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/react/cjs/re
act.development.js:1212:25)
- useSettingsStore (src/ui/contexts/SettingsContext.tsx:44:17)
- VimModeProvider (src/ui/contexts/VimModeContext.tsx:27:36)
-Object.react-stack-botto
m-frame (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reco
nciler@0.32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconci
ler.development.js:15859:20)
-renderWithHook
s (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.3
2.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:3
221:22)
-updateFunctionCompo
nent (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconcile
r@0.32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.develo
pment.js:6475:19)
-beginWork
(/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.32.0_r
eact@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:8009:18)
-runWithFiberInD
EV (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.
32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js
:1738:13)
-performUnitOfWo
rk (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.
32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js
:12834:22)
-workLoopSync
(/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.32.
0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:12644
:41)
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 5`] = `
"
ERROR Cannot read properties of null (reading 'useContext')
/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/react/cjs/react.development.js:1212:25
1209: console.error(
1210 "Calling useContext(Context.Consumer) is not supported and will cause bugs. Did you
: mean to call useContext(Context) instead?"
1211: );
1212: return dispatcher.useContext(Context);
1213: };
1214: exports.useDebugValue = function (value, formatterFn) {
1215: return resolveDispatcher().useDebugValue(value, formatterFn);
-process.env.NODE_ENV.exports
.useContext (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/react/cjs/re
act.development.js:1212:25)
- useSettingsStore (src/ui/contexts/SettingsContext.tsx:44:17)
- VimModeProvider (src/ui/contexts/VimModeContext.tsx:27:36)
-Object.react-stack-botto
m-frame (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reco
nciler@0.32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconci
ler.development.js:15859:20)
-renderWithHook
s (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.3
2.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:3
221:22)
-updateFunctionCompo
nent (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconcile
r@0.32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.develo
pment.js:6475:19)
-beginWork
(/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.32.0_r
eact@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:8009:18)
-runWithFiberInD
EV (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.
32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js
:1738:13)
-performUnitOfWo
rk (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.
32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js
:12834:22)
-workLoopSync
(/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.32.
0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:12644
:41)
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 6`] = `
"
ERROR Cannot read properties of null (reading 'useContext')
/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/react/cjs/react.development.js:1212:25
1209: console.error(
1210 "Calling useContext(Context.Consumer) is not supported and will cause bugs. Did you
: mean to call useContext(Context) instead?"
1211: );
1212: return dispatcher.useContext(Context);
1213: };
1214: exports.useDebugValue = function (value, formatterFn) {
1215: return resolveDispatcher().useDebugValue(value, formatterFn);
-process.env.NODE_ENV.exports
.useContext (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/react/cjs/re
act.development.js:1212:25)
- useSettingsStore (src/ui/contexts/SettingsContext.tsx:44:17)
- VimModeProvider (src/ui/contexts/VimModeContext.tsx:27:36)
-Object.react-stack-botto
m-frame (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reco
nciler@0.32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconci
ler.development.js:15859:20)
-renderWithHook
s (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.3
2.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:3
221:22)
-updateFunctionCompo
nent (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconcile
r@0.32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.develo
pment.js:6475:19)
-beginWork
(/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.32.0_r
eact@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:8009:18)
-runWithFiberInD
EV (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.
32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js
:1738:13)
-performUnitOfWo
rk (/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.
32.0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js
:12834:22)
-workLoopSync
(/Users/kazuki/github/kazukinakai/gemini-cli/node_modules/.pnpm/react-reconciler@0.32.
0_react@19.2.5/node_modules/react-reconciler/cjs/react-reconciler.development.js:12644
:41)
"
`;
exports[`InputPrompt > multiline rendering > should correctly render multiline input including blank lines 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > hello │

View file

@ -1,6 +1,6 @@
{
"name": "@google/gemini-cli-core",
"version": "0.40.0-nightly.20260414.g5b1f7375a",
"version": "0.40.0-pr25684-c3cd0c7",
"description": "Gemini CLI Core",
"license": "Apache-2.0",
"repository": {

View file

@ -18,10 +18,11 @@ import {
} from '../config/models.js';
describe('policyCatalog', () => {
it('returns preview chain when preview enabled', () => {
it('returns preview chain with flash-lite as last-resort when preview enabled', () => {
const chain = getModelPolicyChain({ previewEnabled: true });
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_MODEL);
expect(chain).toHaveLength(2);
expect(chain).toHaveLength(3);
expect(chain[2]?.isLastResort).toBe(true);
});
it('returns Gemini 3.1 chain when useGemini31 is true', () => {
@ -31,7 +32,7 @@ describe('policyCatalog', () => {
useGemini31FlashLite: false,
});
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_MODEL);
expect(chain).toHaveLength(2);
expect(chain).toHaveLength(3);
expect(chain[1]?.model).toBe('gemini-3-flash-preview');
});
@ -43,14 +44,15 @@ describe('policyCatalog', () => {
useCustomToolModel: true,
});
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
expect(chain).toHaveLength(2);
expect(chain).toHaveLength(3);
expect(chain[1]?.model).toBe('gemini-3-flash-preview');
});
it('returns default chain when preview disabled', () => {
const chain = getModelPolicyChain({ previewEnabled: false });
expect(chain[0]?.model).toBe(DEFAULT_GEMINI_MODEL);
expect(chain).toHaveLength(2);
expect(chain).toHaveLength(3);
expect(chain[2]?.isLastResort).toBe(true);
});
it('marks preview transients as sticky retries', () => {
@ -59,18 +61,37 @@ describe('policyCatalog', () => {
expect(previewPolicy.stateTransitions.transient).toBe('terminal');
});
it('applies default actions and state transitions for unspecified kinds', () => {
const [previewPolicy] = getModelPolicyChain({ previewEnabled: true });
expect(previewPolicy.stateTransitions.not_found).toBe('terminal');
expect(previewPolicy.stateTransitions.unknown).toBe('terminal');
expect(previewPolicy.actions.unknown).toBe('prompt');
it('seeds terminal state transitions for default chain policies', () => {
const [defaultPolicy] = getModelPolicyChain({ previewEnabled: false });
expect(defaultPolicy.stateTransitions.not_found).toBe('terminal');
expect(defaultPolicy.stateTransitions.unknown).toBe('terminal');
});
it('marks every preview chain policy as silent so Auto mode does not surface fallback dialogs', () => {
const chain = getModelPolicyChain({ previewEnabled: true });
for (const policy of chain) {
expect(policy.actions.terminal).toBe('silent');
expect(policy.actions.transient).toBe('silent');
expect(policy.actions.not_found).toBe('silent');
expect(policy.actions.unknown).toBe('silent');
}
});
it('marks every default chain policy as silent so Auto 2.5 stays self-contained on quota exhaustion', () => {
const chain = getModelPolicyChain({ previewEnabled: false });
for (const policy of chain) {
expect(policy.actions.terminal).toBe('silent');
expect(policy.actions.transient).toBe('silent');
expect(policy.actions.not_found).toBe('silent');
expect(policy.actions.unknown).toBe('silent');
}
});
it('clones policy maps so edits do not leak between calls', () => {
const firstCall = getModelPolicyChain({ previewEnabled: false });
firstCall[0].actions.terminal = 'silent';
firstCall[0].actions.terminal = 'prompt';
const secondCall = getModelPolicyChain({ previewEnabled: false });
expect(secondCall[0].actions.terminal).toBe('prompt');
expect(secondCall[0].actions.terminal).toBe('silent');
});
it('passes when there is exactly one last-resort policy', () => {

View file

@ -57,8 +57,16 @@ const DEFAULT_STATE: ModelPolicyStateMap = {
};
const DEFAULT_CHAIN: ModelPolicyChain = [
definePolicy({ model: DEFAULT_GEMINI_MODEL }),
definePolicy({ model: DEFAULT_GEMINI_FLASH_MODEL, isLastResort: true }),
definePolicy({ model: DEFAULT_GEMINI_MODEL, actions: SILENT_ACTIONS }),
definePolicy({
model: DEFAULT_GEMINI_FLASH_MODEL,
actions: SILENT_ACTIONS,
}),
definePolicy({
model: DEFAULT_GEMINI_FLASH_LITE_MODEL,
isLastResort: true,
actions: SILENT_ACTIONS,
}),
];
const FLASH_LITE_CHAIN: ModelPolicyChain = [
@ -94,9 +102,16 @@ export function getModelPolicyChain(
? PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL
: DEFAULT_GEMINI_FLASH_LITE_MODEL;
return [
definePolicy({ model: previewModel }),
definePolicy({ model: PREVIEW_GEMINI_FLASH_MODEL }),
definePolicy({ model: flashLiteModel, isLastResort: true }),
definePolicy({ model: previewModel, actions: SILENT_ACTIONS }),
definePolicy({
model: PREVIEW_GEMINI_FLASH_MODEL,
actions: SILENT_ACTIONS,
}),
definePolicy({
model: flashLiteModel,
isLastResort: true,
actions: SILENT_ACTIONS,
}),
];
}

View file

@ -65,10 +65,13 @@ describe('policyHelpers', () => {
});
const chain = resolvePolicyChain(config);
// Expect default chain [Pro, Flash]
expect(chain).toHaveLength(2);
// Expect default chain [Pro, Flash, Flash-Lite] so Auto 2.5 stays
// self-contained when quota is exhausted.
expect(chain).toHaveLength(3);
expect(chain[0]?.model).toBe('gemini-2.5-pro');
expect(chain[1]?.model).toBe('gemini-2.5-flash');
expect(chain[2]?.model).toBe('gemini-2.5-flash-lite');
expect(chain[2]?.isLastResort).toBe(true);
});
it('uses auto chain when preferred model is auto', () => {
@ -76,9 +79,10 @@ describe('policyHelpers', () => {
getModel: () => 'gemini-2.5-pro',
});
const chain = resolvePolicyChain(config, DEFAULT_GEMINI_MODEL_AUTO);
expect(chain).toHaveLength(2);
expect(chain).toHaveLength(3);
expect(chain[0]?.model).toBe('gemini-2.5-pro');
expect(chain[1]?.model).toBe('gemini-2.5-flash');
expect(chain[2]?.model).toBe('gemini-2.5-flash-lite');
});
it('uses auto chain when configured model is auto even if preferred is concrete', () => {
@ -86,18 +90,22 @@ describe('policyHelpers', () => {
getModel: () => DEFAULT_GEMINI_MODEL_AUTO,
});
const chain = resolvePolicyChain(config, 'gemini-2.5-pro');
expect(chain).toHaveLength(2);
expect(chain).toHaveLength(3);
expect(chain[0]?.model).toBe('gemini-2.5-pro');
expect(chain[1]?.model).toBe('gemini-2.5-flash');
expect(chain[2]?.model).toBe('gemini-2.5-flash-lite');
});
it('starts chain from preferredModel when model is "auto"', () => {
it('starts chain from preferredModel when model is "auto" and wraps around', () => {
const config = createMockConfig({
getModel: () => DEFAULT_GEMINI_MODEL_AUTO,
});
const chain = resolvePolicyChain(config, 'gemini-2.5-flash');
expect(chain).toHaveLength(1);
// Now expects length 3 because auto always wraps around.
expect(chain).toHaveLength(3);
expect(chain[0]?.model).toBe('gemini-2.5-flash');
expect(chain[1]?.model).toBe('gemini-2.5-flash-lite');
expect(chain[2]?.model).toBe('gemini-2.5-pro');
});
it('returns flash-lite chain when preferred model is flash-lite', () => {
@ -127,9 +135,10 @@ describe('policyHelpers', () => {
getModel: () => DEFAULT_GEMINI_MODEL_AUTO,
});
const chain = resolvePolicyChain(config, 'gemini-2.5-flash', true);
expect(chain).toHaveLength(2);
expect(chain).toHaveLength(3);
expect(chain[0]?.model).toBe('gemini-2.5-flash');
expect(chain[1]?.model).toBe('gemini-2.5-pro');
expect(chain[1]?.model).toBe('gemini-2.5-flash-lite');
expect(chain[2]?.model).toBe('gemini-2.5-pro');
});
it('proactively returns Gemini 2.5 chain if Gemini 3 requested but user lacks access', () => {
@ -139,10 +148,11 @@ describe('policyHelpers', () => {
});
const chain = resolvePolicyChain(config);
// Should downgrade to [Pro 2.5, Flash 2.5]
expect(chain).toHaveLength(2);
// Should downgrade to [Pro 2.5, Flash 2.5, Flash-Lite 2.5]
expect(chain).toHaveLength(3);
expect(chain[0]?.model).toBe('gemini-2.5-pro');
expect(chain[1]?.model).toBe('gemini-2.5-flash');
expect(chain[2]?.model).toBe('gemini-2.5-flash-lite');
});
it('returns Gemini 3.1 Pro chain when launched and auto-gemini-3 requested', () => {
@ -173,9 +183,10 @@ describe('policyHelpers', () => {
});
const chain = resolvePolicyChain(config);
expect(chain).toHaveLength(2);
expect(chain).toHaveLength(3);
expect(chain[0]?.actions).toEqual(SILENT_ACTIONS);
expect(chain[1]?.actions).toEqual(SILENT_ACTIONS);
expect(chain[2]?.actions).toEqual(SILENT_ACTIONS);
});
});

View file

@ -23,6 +23,7 @@ import {
import {
DEFAULT_GEMINI_FLASH_LITE_MODEL,
DEFAULT_GEMINI_MODEL,
GEMINI_MODEL_ALIAS_AUTO,
PREVIEW_GEMINI_MODEL_AUTO,
isAutoModel,
isGemini3Model,
@ -65,6 +66,10 @@ export function resolvePolicyChain(
: false;
const isAutoConfigured = isAutoModel(configuredModel, config);
// Auto-mode should always allow circular fallback (wrapsAround) to ensure
// that we try every available model in the tiered chain before giving up.
const shouldWrapAround = wrapsAround || isAutoPreferred || isAutoConfigured;
// --- DYNAMIC PATH ---
if (config.getExperimentalDynamicModelConfiguration?.() === true) {
const context = {
@ -96,7 +101,8 @@ export function resolvePolicyChain(
hasAccessToPreview &&
(isGemini3Model(resolvedModel, config) ||
preferredModel === PREVIEW_GEMINI_MODEL_AUTO ||
configuredModel === PREVIEW_GEMINI_MODEL_AUTO);
configuredModel === PREVIEW_GEMINI_MODEL_AUTO ||
configuredModel === GEMINI_MODEL_ALIAS_AUTO);
const chainKey = previewEnabled ? 'preview' : 'default';
chain = config.modelConfigService.resolveChain(chainKey, context);
}
@ -105,7 +111,7 @@ export function resolvePolicyChain(
// No matching modelChains found, default to single model chain
chain = createSingleModelChain(modelFromConfig);
}
chain = applyDynamicSlicing(chain, resolvedModel, wrapsAround);
chain = applyDynamicSlicing(chain, resolvedModel, shouldWrapAround);
} else {
// --- LEGACY PATH ---
@ -120,7 +126,8 @@ export function resolvePolicyChain(
const previewEnabled =
isGemini3Model(resolvedModel, config) ||
preferredModel === PREVIEW_GEMINI_MODEL_AUTO ||
configuredModel === PREVIEW_GEMINI_MODEL_AUTO;
configuredModel === PREVIEW_GEMINI_MODEL_AUTO ||
configuredModel === GEMINI_MODEL_ALIAS_AUTO;
chain = getModelPolicyChain({
previewEnabled,
userTier: config.getUserTier(),
@ -142,7 +149,7 @@ export function resolvePolicyChain(
} else {
chain = createSingleModelChain(modelFromConfig);
}
chain = applyDynamicSlicing(chain, resolvedModel, wrapsAround);
chain = applyDynamicSlicing(chain, resolvedModel, shouldWrapAround);
}
// Apply Unified Silent Injection for Plan Mode with defensive checks

View file

@ -519,10 +519,10 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
{
model: 'gemini-3-pro-preview',
actions: {
terminal: 'prompt',
transient: 'prompt',
not_found: 'prompt',
unknown: 'prompt',
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',
@ -533,12 +533,27 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
{
model: 'gemini-3-flash-preview',
actions: {
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
{
model: 'gemini-3.1-flash-lite-preview',
isLastResort: true,
actions: {
terminal: 'prompt',
transient: 'prompt',
not_found: 'prompt',
unknown: 'prompt',
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',
@ -552,10 +567,10 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
{
model: 'gemini-2.5-pro',
actions: {
terminal: 'prompt',
transient: 'prompt',
not_found: 'prompt',
unknown: 'prompt',
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',
@ -566,12 +581,27 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
{
model: 'gemini-2.5-flash',
actions: {
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
{
model: 'gemini-2.5-flash-lite',
isLastResort: true,
actions: {
terminal: 'prompt',
transient: 'prompt',
not_found: 'prompt',
unknown: 'prompt',
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',

View file

@ -227,7 +227,8 @@ export function resolveClassifierModel(
}
if (
requestedModel === PREVIEW_GEMINI_MODEL_AUTO ||
requestedModel === PREVIEW_GEMINI_MODEL
requestedModel === PREVIEW_GEMINI_MODEL ||
requestedModel === GEMINI_MODEL_ALIAS_AUTO
) {
return PREVIEW_GEMINI_FLASH_MODEL;
}

View file

@ -111,6 +111,13 @@
"topP": 1
}
},
"gemini-3-flash-lite-base": {
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1
}
},
"classifier": {
"model": "gemini-2.5-flash-lite",
"generateContentConfig": {
@ -171,7 +178,7 @@
}
},
"web-search": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@ -183,7 +190,7 @@
}
},
"web-fetch": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@ -195,14 +202,14 @@
}
},
"web-fetch-fallback": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1
}
},
"loop-detection": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1
@ -216,14 +223,14 @@
}
},
"llm-edit-fixer": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1
}
},
"next-speaker-checker": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1

View file

@ -111,6 +111,13 @@
"topP": 1
}
},
"gemini-3-flash-lite-base": {
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1
}
},
"classifier": {
"model": "gemini-2.5-flash-lite",
"generateContentConfig": {
@ -171,7 +178,7 @@
}
},
"web-search": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@ -183,7 +190,7 @@
}
},
"web-fetch": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1,
@ -195,14 +202,14 @@
}
},
"web-fetch-fallback": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1
}
},
"loop-detection": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1
@ -216,14 +223,14 @@
}
},
"llm-edit-fixer": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1
}
},
"next-speaker-checker": {
"model": "gemini-3-flash-preview",
"model": "gemini-3.1-flash-lite-preview",
"generateContentConfig": {
"temperature": 0,
"topP": 1