From 3aedbbc0674da31710c807527efb8a65212e8834 Mon Sep 17 00:00:00 2001 From: Adam Weidman <65992621+adamfweidman@users.noreply.github.com> Date: Wed, 29 Apr 2026 16:23:37 -0400 Subject: [PATCH] fix(core): distinguish fallback chains and fix maxAttempts for auto vs explicit model selection (#26163) --- docs/reference/configuration.md | 80 +++- .../autoRoutingFallback.integration.test.ts | 414 ++++++++++++++++++ .../availability/fallbackIntegration.test.ts | 34 ++ .../modelAvailabilityService.test.ts | 6 + .../availability/modelAvailabilityService.ts | 7 +- packages/core/src/availability/modelPolicy.ts | 1 + .../src/availability/policyCatalog.test.ts | 7 +- .../core/src/availability/policyCatalog.ts | 49 ++- .../src/availability/policyHelpers.test.ts | 51 +++ .../core/src/availability/policyHelpers.ts | 28 +- .../core/src/config/defaultModelConfigs.ts | 80 +++- packages/core/src/core/baseLlmClient.test.ts | 55 +-- packages/core/src/core/baseLlmClient.ts | 4 +- packages/core/src/utils/retry.ts | 9 +- schemas/settings.schema.json | 164 ++++++- 15 files changed, 922 insertions(+), 67 deletions(-) create mode 100644 packages/core/src/availability/autoRoutingFallback.integration.test.ts diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 87bbd0756f..7bdd43997e 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -1191,7 +1191,7 @@ their corresponding top-level category object in your `settings.json` file. }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -1199,18 +1199,54 @@ their corresponding top-level category object in your `settings.json` file. { "model": "gemini-3-flash-preview", "isLastResort": true, + "maxAttempts": 10, "actions": { "terminal": "prompt", "transient": "prompt", "not_found": "prompt", "unknown": "prompt" }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } + } + ], + "auto-preview": [ + { + "model": "gemini-3-pro-preview", + "maxAttempts": 3, + "actions": { + "terminal": "prompt", + "transient": "silent", + "not_found": "prompt", + "unknown": "prompt" + }, "stateTransitions": { "terminal": "terminal", "transient": "sticky_retry", "not_found": "terminal", "unknown": "terminal" } + }, + { + "model": "gemini-3-flash-preview", + "isLastResort": true, + "maxAttempts": 10, + "actions": { + "terminal": "prompt", + "transient": "prompt", + "not_found": "prompt", + "unknown": "prompt" + }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } } ], "default": [ @@ -1232,18 +1268,54 @@ their corresponding top-level category object in your `settings.json` file. { "model": "gemini-2.5-flash", "isLastResort": true, + "maxAttempts": 10, "actions": { "terminal": "prompt", "transient": "prompt", "not_found": "prompt", "unknown": "prompt" }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } + } + ], + "auto-default": [ + { + "model": "gemini-2.5-pro", + "maxAttempts": 3, + "actions": { + "terminal": "prompt", + "transient": "silent", + "not_found": "prompt", + "unknown": "prompt" + }, "stateTransitions": { "terminal": "terminal", "transient": "sticky_retry", "not_found": "terminal", "unknown": "terminal" } + }, + { + "model": "gemini-2.5-flash", + "isLastResort": true, + "maxAttempts": 10, + "actions": { + "terminal": "prompt", + "transient": "prompt", + "not_found": "prompt", + "unknown": "prompt" + }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } } ], "lite": [ @@ -1257,7 +1329,7 @@ their corresponding top-level category object in your `settings.json` file. }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -1272,7 +1344,7 @@ their corresponding top-level category object in your `settings.json` file. }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -1288,7 +1360,7 @@ their corresponding top-level category object in your `settings.json` file. }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } diff --git a/packages/core/src/availability/autoRoutingFallback.integration.test.ts b/packages/core/src/availability/autoRoutingFallback.integration.test.ts new file mode 100644 index 0000000000..f4e157503b --- /dev/null +++ b/packages/core/src/availability/autoRoutingFallback.integration.test.ts @@ -0,0 +1,414 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { BaseLlmClient } from '../core/baseLlmClient.js'; +import { FakeContentGenerator } from '../core/fakeContentGenerator.js'; +import { Config } from '../config/config.js'; +import { RetryableQuotaError } from '../utils/googleQuotaErrors.js'; +import { + PREVIEW_GEMINI_MODEL, + PREVIEW_GEMINI_FLASH_MODEL, + PREVIEW_GEMINI_MODEL_AUTO, +} from '../config/models.js'; +import fs from 'node:fs'; +import { AuthType } from '../core/contentGenerator.js'; +import type { FallbackIntent } from '../fallback/types.js'; +import { LlmRole } from '../telemetry/types.js'; +import type { GenerateContentResponse } from '@google/genai'; + +vi.mock('node:fs'); + +describe('Auto Routing Fallback Integration', () => { + let config: Config; + let fakeGenerator: FakeContentGenerator; + let client: BaseLlmClient; + + beforeEach(() => { + vi.useFakeTimers(); + + // Mock fs to avoid real file system access + vi.mocked(fs.existsSync).mockReturnValue(true); + vi.mocked(fs.statSync).mockReturnValue({ + isDirectory: () => true, + } as fs.Stats); + + // Provide a valid dummy sandbox policy for any readFileSync calls for TOML files + vi.mocked(fs.readFileSync).mockImplementation((path) => { + if (typeof path === 'string' && path.endsWith('.toml')) { + return ` + [modes.plan] + network = false + readonly = true + approvedTools = [] + + [modes.default] + network = false + readonly = false + approvedTools = [] + + [modes.accepting_edits] + network = false + readonly = false + approvedTools = [] + `; + } + return ''; // Fallback for other files + }); + + fakeGenerator = new FakeContentGenerator([]); + }); + + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + it('should fallback to Flash after 3 tries and try 10 times for Flash in auto mode', async () => { + // Instantiate real Config in auto mode + config = new Config({ + sessionId: 'test-session', + targetDir: '/test', + debugMode: false, + cwd: '/test', + model: PREVIEW_GEMINI_MODEL_AUTO, // Trigger auto mode + }); + + // Force interactive mode to enable fallback handler in BaseLlmClient + vi.spyOn(config, 'isInteractive').mockReturnValue(true); + + client = new BaseLlmClient( + fakeGenerator, + config, + AuthType.LOGIN_WITH_GOOGLE, + ); + + let attemptsPro = 0; + let attemptsFlash = 0; + + const mockGoogleApiError = { + code: 429, + message: 'Quota exceeded', + details: [], + }; + + // Spy on generateContent to simulate failures + vi.spyOn(fakeGenerator, 'generateContent').mockImplementation( + async (params) => { + if (params.model === PREVIEW_GEMINI_MODEL) { + attemptsPro++; + throw new RetryableQuotaError( + 'Quota exceeded for Pro', + mockGoogleApiError, + 0, + ); + } else if (params.model === PREVIEW_GEMINI_FLASH_MODEL) { + attemptsFlash++; + throw new RetryableQuotaError( + 'Quota exceeded for Flash', + mockGoogleApiError, + 0, + ); + } + throw new Error(`Unexpected model: ${params.model}`); + }, + ); + + // Set a fallback handler that approves the switch (simulating user or auto approval) + config.setFallbackModelHandler( + async (failed, _fallback, _error): Promise => { + if (failed === PREVIEW_GEMINI_FLASH_MODEL) { + return 'stop'; // Stop retrying after Flash fails + } + return 'retry_always'; // Trigger fallback to Flash + }, + ); + + // Call generateContent + const promise = client.generateContent({ + modelConfigKey: { model: PREVIEW_GEMINI_MODEL, isChatModel: true }, + contents: [{ role: 'user', parts: [{ text: 'hi' }] }], + abortSignal: new AbortController().signal, + promptId: 'test-prompt', + role: LlmRole.UTILITY_TOOL, + }); + + await Promise.all([ + expect(promise).rejects.toThrow('Quota exceeded for Flash'), + vi.runAllTimersAsync(), + ]); + + // Verify attempts + expect(attemptsPro).toBe(3); + expect(attemptsFlash).toBe(10); + }); + + it('should try 10 times and prompt user in non-auto mode', async () => { + // Instantiate real Config in non-auto mode + const configNonAuto = new Config({ + sessionId: 'test-session', + targetDir: '/test', + debugMode: false, + cwd: '/test', + model: PREVIEW_GEMINI_MODEL, // Non-auto mode + }); + + // Force interactive mode to enable fallback handler in BaseLlmClient + vi.spyOn(configNonAuto, 'isInteractive').mockReturnValue(true); + + const clientNonAuto = new BaseLlmClient( + fakeGenerator, + configNonAuto, + AuthType.LOGIN_WITH_GOOGLE, + ); + + let attemptsPro = 0; + + const mockGoogleApiError = { + code: 429, + message: 'Quota exceeded', + details: [], + }; + + // Spy on generateContent to simulate failures + vi.spyOn(fakeGenerator, 'generateContent').mockImplementation( + async (params) => { + if (params.model === PREVIEW_GEMINI_MODEL) { + attemptsPro++; + throw new RetryableQuotaError( + 'Quota exceeded for Pro', + mockGoogleApiError, + 0, + ); + } + throw new Error(`Unexpected model: ${params.model}`); + }, + ); + + // Set a fallback handler that returns 'stop' (simulating user stopping or failing to handle) + const handler = vi.fn( + async (_failed, _fallback, _error): Promise => + 'stop', + ); + configNonAuto.setFallbackModelHandler(handler); + + const promise = clientNonAuto.generateContent({ + modelConfigKey: { model: PREVIEW_GEMINI_MODEL, isChatModel: true }, + contents: [{ role: 'user', parts: [{ text: 'hi' }] }], + abortSignal: new AbortController().signal, + promptId: 'test-prompt', + role: LlmRole.UTILITY_TOOL, + maxAttempts: 10, + }); + + await Promise.all([ + expect(promise).rejects.toThrow('Quota exceeded for Pro'), + vi.runAllTimersAsync(), + ]); + + // Verify attempts (should default to 10) + expect(attemptsPro).toBe(10); + + // Verify handler was called once after 10 attempts to prompt user + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith( + PREVIEW_GEMINI_MODEL, + PREVIEW_GEMINI_FLASH_MODEL, + expect.any(RetryableQuotaError), + ); + }); + + it('should fallback to Flash after 3 tries in experimental dynamic mode', async () => { + // Instantiate real Config in auto mode + const configDynamic = new Config({ + sessionId: 'test-session', + targetDir: '/test', + debugMode: false, + cwd: '/test', + model: PREVIEW_GEMINI_MODEL_AUTO, // Trigger auto mode + }); + + // Force interactive mode to enable fallback handler in BaseLlmClient + vi.spyOn(configDynamic, 'isInteractive').mockReturnValue(true); + + // Enable experimental dynamic model configuration + vi.spyOn( + configDynamic, + 'getExperimentalDynamicModelConfiguration', + ).mockReturnValue(true); + + const clientDynamic = new BaseLlmClient( + fakeGenerator, + configDynamic, + AuthType.LOGIN_WITH_GOOGLE, + ); + + let attemptsPro = 0; + let attemptsFlash = 0; + + const mockGoogleApiError = { + code: 429, + message: 'Quota exceeded', + details: [], + }; + + // Spy on generateContent to simulate failures + vi.spyOn(fakeGenerator, 'generateContent').mockImplementation( + async (params) => { + if (params.model === PREVIEW_GEMINI_MODEL) { + attemptsPro++; + throw new RetryableQuotaError( + 'Quota exceeded for Pro', + mockGoogleApiError, + 0, + ); + } else if (params.model === PREVIEW_GEMINI_FLASH_MODEL) { + attemptsFlash++; + throw new RetryableQuotaError( + 'Quota exceeded for Flash', + mockGoogleApiError, + 0, + ); + } + throw new Error(`Unexpected model: ${params.model}`); + }, + ); + + // Set a fallback handler that approves the switch + configDynamic.setFallbackModelHandler( + async (failed, _fallback, _error): Promise => { + if (failed === PREVIEW_GEMINI_FLASH_MODEL) { + return 'stop'; + } + return 'retry_always'; + }, + ); + + const promise = clientDynamic.generateContent({ + modelConfigKey: { model: PREVIEW_GEMINI_MODEL, isChatModel: true }, + contents: [{ role: 'user', parts: [{ text: 'hi' }] }], + abortSignal: new AbortController().signal, + promptId: 'test-prompt', + role: LlmRole.UTILITY_TOOL, + }); + + await Promise.all([ + expect(promise).rejects.toThrow('Quota exceeded for Flash'), + vi.runAllTimersAsync(), + ]); + + // Verify attempts + expect(attemptsPro).toBe(3); + expect(attemptsFlash).toBe(10); + }); + + it('should retry Pro on next turn after successful fallback to Flash', async () => { + // Instantiate real Config in auto mode + config = new Config({ + sessionId: 'test-session', + targetDir: '/test', + debugMode: false, + cwd: '/test', + model: PREVIEW_GEMINI_MODEL_AUTO, // Trigger auto mode + }); + + // Force interactive mode to enable fallback handler in BaseLlmClient + vi.spyOn(config, 'isInteractive').mockReturnValue(true); + + client = new BaseLlmClient( + fakeGenerator, + config, + AuthType.LOGIN_WITH_GOOGLE, + ); + + let attemptsPro = 0; + let attemptsFlash = 0; + + const mockGoogleApiError = { + code: 429, + message: 'Quota exceeded', + details: [], + }; + + // Turn 1: Pro fails, Flash succeeds + vi.spyOn(fakeGenerator, 'generateContent').mockImplementation( + async (params) => { + if (params.model === PREVIEW_GEMINI_MODEL) { + attemptsPro++; + throw new RetryableQuotaError( + 'Quota exceeded for Pro', + mockGoogleApiError, + 0, + ); + } else if (params.model === PREVIEW_GEMINI_FLASH_MODEL) { + attemptsFlash++; + return { + candidates: [ + { + content: { role: 'model', parts: [{ text: 'Flash success' }] }, + }, + ], + } as unknown as GenerateContentResponse; + } + throw new Error(`Unexpected model: ${params.model}`); + }, + ); + + config.setFallbackModelHandler( + async (_failed, _fallback, _error): Promise => + 'retry_always', // Approve switch to Flash + ); + + // Call generateContent for Turn 1 + const promise1 = client.generateContent({ + modelConfigKey: { model: PREVIEW_GEMINI_MODEL, isChatModel: true }, + contents: [{ role: 'user', parts: [{ text: 'hi' }] }], + abortSignal: new AbortController().signal, + promptId: 'test-prompt-1', + role: LlmRole.UTILITY_TOOL, + }); + + await vi.runAllTimersAsync(); + const result1 = await promise1; + + expect(result1.candidates?.[0]?.content?.parts?.[0]?.text).toBe( + 'Flash success', + ); + expect(attemptsPro).toBe(3); + expect(attemptsFlash).toBe(1); + + // Simulate start of next turn + config.getModelAvailabilityService().resetTurn(); + + // Turn 2: Pro should be attempted again! + // Let's make it succeed this time to verify it works! + vi.spyOn(fakeGenerator, 'generateContent').mockImplementation( + async (params) => { + if (params.model === PREVIEW_GEMINI_MODEL) { + return { + candidates: [ + { content: { role: 'model', parts: [{ text: 'Pro success' }] } }, + ], + } as unknown as GenerateContentResponse; + } + throw new Error(`Unexpected model: ${params.model}`); + }, + ); + + const promise2 = client.generateContent({ + modelConfigKey: { model: PREVIEW_GEMINI_MODEL, isChatModel: true }, // Request Pro again + contents: [{ role: 'user', parts: [{ text: 'hello again' }] }], + abortSignal: new AbortController().signal, + promptId: 'test-prompt-2', + role: LlmRole.UTILITY_TOOL, + }); + + const result2 = await promise2; + expect(result2.candidates?.[0]?.content?.parts?.[0]?.text).toBe( + 'Pro success', + ); + }); +}); diff --git a/packages/core/src/availability/fallbackIntegration.test.ts b/packages/core/src/availability/fallbackIntegration.test.ts index 62174c9abb..6c49938ed9 100644 --- a/packages/core/src/availability/fallbackIntegration.test.ts +++ b/packages/core/src/availability/fallbackIntegration.test.ts @@ -77,4 +77,38 @@ describe('Fallback Integration', () => { // 5. Expect it to fallback to Flash (because Gemini 3 uses PREVIEW_CHAIN) expect(result.model).toBe(PREVIEW_GEMINI_FLASH_MODEL); }); + + it('should fallback to Flash after failures and restore Pro on next turn', () => { + const requestedModel = PREVIEW_GEMINI_MODEL; + + // 1. Initial call should return Pro with 3 attempts + const result1 = applyModelSelection(config, { + model: requestedModel, + isChatModel: true, + }); + expect(result1.model).toBe(PREVIEW_GEMINI_MODEL); + expect(result1.maxAttempts).toBe(3); + + // 2. Simulate failure and transition to sticky_retry with consumed=true + availabilityService.markRetryOncePerTurn(PREVIEW_GEMINI_MODEL, 3); + availabilityService.consumeStickyAttempt(PREVIEW_GEMINI_MODEL); + + // 3. Next call in same turn should fallback to Flash + const result2 = applyModelSelection(config, { + model: requestedModel, + isChatModel: true, + }); + expect(result2.model).toBe(PREVIEW_GEMINI_FLASH_MODEL); + + // 4. Reset turn (start of new interaction) + availabilityService.resetTurn(); + + // 5. Next call should restore Pro with 3 attempts + const result3 = applyModelSelection(config, { + model: requestedModel, + isChatModel: true, + }); + expect(result3.model).toBe(PREVIEW_GEMINI_MODEL); + expect(result3.maxAttempts).toBe(3); + }); }); diff --git a/packages/core/src/availability/modelAvailabilityService.test.ts b/packages/core/src/availability/modelAvailabilityService.test.ts index 32b8e000ee..2dcc90f477 100644 --- a/packages/core/src/availability/modelAvailabilityService.test.ts +++ b/packages/core/src/availability/modelAvailabilityService.test.ts @@ -34,6 +34,12 @@ describe('ModelAvailabilityService', () => { expect(service.snapshot(model)).toEqual({ available: true }); }); + it('tracks retry with custom attempts', () => { + service.markRetryOncePerTurn(model, 3); + const selection = service.selectFirstAvailable([model]); + expect(selection.attempts).toBe(3); + }); + it('tracks terminal failures', () => { service.markTerminal(model, 'quota'); expect(service.snapshot(model)).toEqual({ diff --git a/packages/core/src/availability/modelAvailabilityService.ts b/packages/core/src/availability/modelAvailabilityService.ts index 051003f667..9ef83230ec 100644 --- a/packages/core/src/availability/modelAvailabilityService.ts +++ b/packages/core/src/availability/modelAvailabilityService.ts @@ -22,6 +22,7 @@ type HealthState = status: 'sticky_retry'; reason: TurnUnavailabilityReason; consumed: boolean; + attempts: number; }; export interface ModelAvailabilitySnapshot { @@ -52,7 +53,7 @@ export class ModelAvailabilityService { this.clearState(model); } - markRetryOncePerTurn(model: ModelId) { + markRetryOncePerTurn(model: ModelId, attempts: number = 1) { const currentState = this.health.get(model); // Do not override a terminal failure with a transient one. if (currentState?.status === 'terminal') { @@ -70,6 +71,7 @@ export class ModelAvailabilityService { status: 'sticky_retry', reason: 'retry_once_per_turn', consumed, + attempts, }); } @@ -106,7 +108,8 @@ export class ModelAvailabilityService { if (snapshot.available) { const state = this.health.get(model); // A sticky model is being attempted, so note that. - const attempts = state?.status === 'sticky_retry' ? 1 : undefined; + const attempts = + state?.status === 'sticky_retry' ? state.attempts : undefined; return { selectedModel: model, skipped, attempts }; } else { skipped.push({ model, reason: snapshot.reason ?? 'unknown' }); diff --git a/packages/core/src/availability/modelPolicy.ts b/packages/core/src/availability/modelPolicy.ts index 199c13d7a5..46a65f53db 100644 --- a/packages/core/src/availability/modelPolicy.ts +++ b/packages/core/src/availability/modelPolicy.ts @@ -46,6 +46,7 @@ export interface ModelPolicy { actions: ModelPolicyActionMap; stateTransitions: ModelPolicyStateMap; isLastResort?: boolean; + maxAttempts?: number; } /** diff --git a/packages/core/src/availability/policyCatalog.test.ts b/packages/core/src/availability/policyCatalog.test.ts index 1c957b2da6..04e35018d5 100644 --- a/packages/core/src/availability/policyCatalog.test.ts +++ b/packages/core/src/availability/policyCatalog.test.ts @@ -53,8 +53,11 @@ describe('policyCatalog', () => { expect(chain).toHaveLength(2); }); - it('marks preview transients as sticky retries', () => { - const [previewPolicy] = getModelPolicyChain({ previewEnabled: true }); + it('marks preview transients as sticky retries when auto-selected', () => { + const [previewPolicy] = getModelPolicyChain({ + previewEnabled: true, + isAutoSelection: true, + }); expect(previewPolicy.model).toBe(PREVIEW_GEMINI_MODEL); expect(previewPolicy.stateTransitions.transient).toBe('sticky_retry'); }); diff --git a/packages/core/src/availability/policyCatalog.ts b/packages/core/src/availability/policyCatalog.ts index 3c12a4d3cf..a5694e94b8 100644 --- a/packages/core/src/availability/policyCatalog.ts +++ b/packages/core/src/availability/policyCatalog.ts @@ -28,6 +28,7 @@ type PolicyConfig = Omit & { export interface ModelPolicyOptions { previewEnabled: boolean; + isAutoSelection?: boolean; userTier?: UserTierId; useGemini31?: boolean; useGemini31FlashLite?: boolean; @@ -50,15 +51,19 @@ export const SILENT_ACTIONS: ModelPolicyActionMap = { const DEFAULT_STATE: ModelPolicyStateMap = { terminal: 'terminal', - transient: 'sticky_retry', + transient: 'terminal', not_found: 'terminal', unknown: 'terminal', }; -const DEFAULT_CHAIN: ModelPolicyChain = [ - definePolicy({ model: DEFAULT_GEMINI_MODEL }), - definePolicy({ model: DEFAULT_GEMINI_FLASH_MODEL, isLastResort: true }), -]; +const AUTO_ROUTING_OVERRIDES = { + maxAttempts: 3, + actions: { ...DEFAULT_ACTIONS, transient: 'silent' } as ModelPolicyActionMap, + stateTransitions: { + ...DEFAULT_STATE, + transient: 'sticky_retry', + } as ModelPolicyStateMap, +}; const FLASH_LITE_CHAIN: ModelPolicyChain = [ definePolicy({ @@ -82,20 +87,45 @@ const FLASH_LITE_CHAIN: ModelPolicyChain = [ export function getModelPolicyChain( options: ModelPolicyOptions, ): ModelPolicyChain { + const isAuto = options.isAutoSelection ?? false; + if (options.previewEnabled) { - const previewModel = resolveModel( + const proModel = resolveModel( PREVIEW_GEMINI_MODEL, options.useGemini31, options.useGemini31FlashLite, options.useCustomToolModel, ); return [ - definePolicy({ model: previewModel }), - definePolicy({ model: PREVIEW_GEMINI_FLASH_MODEL, isLastResort: true }), + definePolicy({ + model: proModel, + ...(isAuto + ? { + maxAttempts: 3, + actions: { ...DEFAULT_ACTIONS, transient: 'silent' }, + stateTransitions: { ...DEFAULT_STATE, transient: 'sticky_retry' }, + } + : {}), + }), + definePolicy({ + model: PREVIEW_GEMINI_FLASH_MODEL, + isLastResort: true, + maxAttempts: 10, + }), ]; } - return cloneChain(DEFAULT_CHAIN); + return [ + definePolicy({ + model: DEFAULT_GEMINI_MODEL, + ...(isAuto ? AUTO_ROUTING_OVERRIDES : {}), + }), + definePolicy({ + model: DEFAULT_GEMINI_FLASH_MODEL, + isLastResort: true, + maxAttempts: 10, + }), + ]; } export function createSingleModelChain(model: string): ModelPolicyChain { @@ -137,6 +167,7 @@ function definePolicy(config: PolicyConfig): ModelPolicy { return { model: config.model, isLastResort: config.isLastResort, + maxAttempts: config.maxAttempts, actions: { ...DEFAULT_ACTIONS, ...(config.actions ?? {}) }, stateTransitions: { ...DEFAULT_STATE, diff --git a/packages/core/src/availability/policyHelpers.test.ts b/packages/core/src/availability/policyHelpers.test.ts index 42344f9bb9..945de646e0 100644 --- a/packages/core/src/availability/policyHelpers.test.ts +++ b/packages/core/src/availability/policyHelpers.test.ts @@ -9,8 +9,10 @@ import { resolvePolicyChain, buildFallbackPolicyContext, applyModelSelection, + applyAvailabilityTransition, } from './policyHelpers.js'; import { createDefaultPolicy, SILENT_ACTIONS } from './policyCatalog.js'; +import type { RetryAvailabilityContext } from './modelPolicy.js'; import type { Config } from '../config/config.js'; import { DEFAULT_GEMINI_FLASH_LITE_MODEL, @@ -35,6 +37,7 @@ const createMockConfig = (overrides: Partial = {}): Config => { return useGemini31 && authType === AuthType.USE_GEMINI; }, getContentGeneratorConfig: () => ({ authType: undefined }), + getMaxAttemptsPerTurn: () => 3, ...overrides, } as unknown as Config; return config; @@ -201,6 +204,7 @@ describe('policyHelpers', () => { hasAccess: false, }, { name: 'Concrete Model (2.5 Pro)', model: 'gemini-2.5-pro' }, + { name: 'Explicit Gemini 3', model: 'gemini-3-pro-preview' }, { name: 'Custom Model', model: 'my-custom-model' }, { name: 'Wrap Around', @@ -438,4 +442,51 @@ describe('policyHelpers', () => { expect(result.maxAttempts).toBe(1); }); }); + + describe('applyAvailabilityTransition', () => { + it('marks terminal on terminal transition', () => { + const mockService = { markTerminal: vi.fn() }; + const context = { + service: mockService, + policy: { + model: 'test-model', + stateTransitions: { transient: 'terminal' }, + }, + }; + const getContext = () => context as unknown as RetryAvailabilityContext; + + applyAvailabilityTransition(getContext, 'transient'); + + expect(mockService.markTerminal).toHaveBeenCalledWith( + 'test-model', + 'capacity', + ); + }); + + it('marks sticky and consumes on sticky_retry transition', () => { + const mockService = { + markRetryOncePerTurn: vi.fn(), + consumeStickyAttempt: vi.fn(), + }; + const context = { + service: mockService, + policy: { + model: 'test-model', + stateTransitions: { transient: 'sticky_retry' }, + maxAttempts: 3, + }, + }; + const getContext = () => context as unknown as RetryAvailabilityContext; + + applyAvailabilityTransition(getContext, 'transient'); + + expect(mockService.markRetryOncePerTurn).toHaveBeenCalledWith( + 'test-model', + 3, + ); + expect(mockService.consumeStickyAttempt).toHaveBeenCalledWith( + 'test-model', + ); + }); + }); }); diff --git a/packages/core/src/availability/policyHelpers.ts b/packages/core/src/availability/policyHelpers.ts index 033443ad5c..5d65a7598e 100644 --- a/packages/core/src/availability/policyHelpers.ts +++ b/packages/core/src/availability/policyHelpers.ts @@ -77,12 +77,12 @@ export function resolvePolicyChain( chain = config.modelConfigService.resolveChain('lite', context); } else if ( isGemini3Model(resolvedModel, config) || - isAutoModel(preferredModel ?? '', config) || - isAutoModel(configuredModel, config) + isAutoPreferred || + isAutoConfigured ) { // 1. Try to find a chain specifically for the current configured alias if ( - isAutoModel(configuredModel, config) && + isAutoConfigured && config.modelConfigService.getModelChain(configuredModel) ) { chain = config.modelConfigService.resolveChain( @@ -92,13 +92,18 @@ export function resolvePolicyChain( } // 2. Fallback to family-based auto-routing if (!chain) { + const isAutoSelection = isAutoPreferred || isAutoConfigured; const previewEnabled = hasAccessToPreview && (isGemini3Model(resolvedModel, config) || preferredModel === PREVIEW_GEMINI_MODEL_AUTO || configuredModel === PREVIEW_GEMINI_MODEL_AUTO); + const autoPrefix = isAutoSelection ? 'auto-' : ''; const chainKey = previewEnabled ? 'preview' : 'default'; - chain = config.modelConfigService.resolveChain(chainKey, context); + chain = config.modelConfigService.resolveChain( + `${autoPrefix}${chainKey}`, + context, + ); } } if (!chain) { @@ -116,6 +121,7 @@ export function resolvePolicyChain( isAutoPreferred || isAutoConfigured ) { + const isAutoSelection = isAutoPreferred || isAutoConfigured; if (hasAccessToPreview) { const previewEnabled = isGemini3Model(resolvedModel, config) || @@ -123,6 +129,7 @@ export function resolvePolicyChain( configuredModel === PREVIEW_GEMINI_MODEL_AUTO; chain = getModelPolicyChain({ previewEnabled, + isAutoSelection, userTier: config.getUserTier(), useGemini31, useGemini31FlashLite, @@ -133,6 +140,7 @@ export function resolvePolicyChain( // to the stable Gemini 2.5 chain. chain = getModelPolicyChain({ previewEnabled: false, + isAutoSelection, userTier: config.getUserTier(), useGemini31, useGemini31FlashLite, @@ -144,7 +152,6 @@ export function resolvePolicyChain( } chain = applyDynamicSlicing(chain, resolvedModel, wrapsAround); } - // Apply Unified Silent Injection for Plan Mode with defensive checks if (config?.getApprovalMode?.() === ApprovalMode.PLAN) { return chain.map((policy) => ({ @@ -295,10 +302,13 @@ export function applyModelSelection( config.getModelAvailabilityService().consumeStickyAttempt(finalModel); } + const chain = resolvePolicyChain(config, finalModel); + const policy = chain.find((p) => p.model === finalModel); + return { model: finalModel, config: generateContentConfig, - maxAttempts: selection.attempts, + maxAttempts: selection.attempts ?? policy?.maxAttempts, }; } @@ -318,6 +328,10 @@ export function applyAvailabilityTransition( failureKind === 'terminal' ? 'quota' : 'capacity', ); } else if (transition === 'sticky_retry') { - context.service.markRetryOncePerTurn(context.policy.model); + context.service.markRetryOncePerTurn( + context.policy.model, + context.policy.maxAttempts, + ); + context.service.consumeStickyAttempt(context.policy.model); } } diff --git a/packages/core/src/config/defaultModelConfigs.ts b/packages/core/src/config/defaultModelConfigs.ts index d03db00bb4..a4f8cbaf57 100644 --- a/packages/core/src/config/defaultModelConfigs.ts +++ b/packages/core/src/config/defaultModelConfigs.ts @@ -557,7 +557,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { }, stateTransitions: { terminal: 'terminal', - transient: 'sticky_retry', + transient: 'terminal', not_found: 'terminal', unknown: 'terminal', }, @@ -565,12 +565,31 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { { model: 'gemini-3-flash-preview', isLastResort: true, + maxAttempts: 10, actions: { terminal: 'prompt', transient: 'prompt', not_found: 'prompt', unknown: 'prompt', }, + stateTransitions: { + terminal: 'terminal', + transient: 'terminal', + not_found: 'terminal', + unknown: 'terminal', + }, + }, + ], + 'auto-preview': [ + { + model: 'gemini-3-pro-preview', + maxAttempts: 3, + actions: { + terminal: 'prompt', + transient: 'silent', + not_found: 'prompt', + unknown: 'prompt', + }, stateTransitions: { terminal: 'terminal', transient: 'sticky_retry', @@ -578,6 +597,23 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { unknown: 'terminal', }, }, + { + model: 'gemini-3-flash-preview', + isLastResort: true, + maxAttempts: 10, + actions: { + terminal: 'prompt', + transient: 'prompt', + not_found: 'prompt', + unknown: 'prompt', + }, + stateTransitions: { + terminal: 'terminal', + transient: 'terminal', + not_found: 'terminal', + unknown: 'terminal', + }, + }, ], default: [ { @@ -598,12 +634,31 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { { model: 'gemini-2.5-flash', isLastResort: true, + maxAttempts: 10, actions: { terminal: 'prompt', transient: 'prompt', not_found: 'prompt', unknown: 'prompt', }, + stateTransitions: { + terminal: 'terminal', + transient: 'terminal', + not_found: 'terminal', + unknown: 'terminal', + }, + }, + ], + 'auto-default': [ + { + model: 'gemini-2.5-pro', + maxAttempts: 3, + actions: { + terminal: 'prompt', + transient: 'silent', + not_found: 'prompt', + unknown: 'prompt', + }, stateTransitions: { terminal: 'terminal', transient: 'sticky_retry', @@ -611,6 +666,23 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { unknown: 'terminal', }, }, + { + model: 'gemini-2.5-flash', + isLastResort: true, + maxAttempts: 10, + actions: { + terminal: 'prompt', + transient: 'prompt', + not_found: 'prompt', + unknown: 'prompt', + }, + stateTransitions: { + terminal: 'terminal', + transient: 'terminal', + not_found: 'terminal', + unknown: 'terminal', + }, + }, ], lite: [ { @@ -623,7 +695,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { }, stateTransitions: { terminal: 'terminal', - transient: 'sticky_retry', + transient: 'terminal', not_found: 'terminal', unknown: 'terminal', }, @@ -638,7 +710,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { }, stateTransitions: { terminal: 'terminal', - transient: 'sticky_retry', + transient: 'terminal', not_found: 'terminal', unknown: 'terminal', }, @@ -654,7 +726,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = { }, stateTransitions: { terminal: 'terminal', - transient: 'sticky_retry', + transient: 'terminal', not_found: 'terminal', unknown: 'terminal', }, diff --git a/packages/core/src/core/baseLlmClient.test.ts b/packages/core/src/core/baseLlmClient.test.ts index 5bfefa6665..82196ffa84 100644 --- a/packages/core/src/core/baseLlmClient.test.ts +++ b/packages/core/src/core/baseLlmClient.test.ts @@ -43,36 +43,41 @@ vi.mock('../utils/errors.js', async (importOriginal) => { }; }); -vi.mock('../utils/retry.js', () => ({ - retryWithBackoff: vi.fn(async (fn, options) => { - // Default implementation - just call the function - const result = await fn(); +vi.mock('../utils/retry.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + retryWithBackoff: vi.fn(async (fn, options) => { + // Default implementation - just call the function + const result = await fn(); - // If shouldRetryOnContent is provided, test it but don't actually retry - // (unless we want to simulate retry exhaustion for testing) - if (options?.shouldRetryOnContent) { - const shouldRetry = options.shouldRetryOnContent(result); - if (shouldRetry) { - // Check if we need to simulate retry exhaustion (for error testing) - const responseText = result?.candidates?.[0]?.content?.parts?.[0]?.text; - if ( - !responseText || - responseText.trim() === '' || - responseText.includes('{"color": "blue"') - ) { - throw new Error('Retry attempts exhausted for invalid content'); + // If shouldRetryOnContent is provided, test it but don't actually retry + // (unless we want to simulate retry exhaustion for testing) + if (options?.shouldRetryOnContent) { + const shouldRetry = options.shouldRetryOnContent(result); + if (shouldRetry) { + // Check if we need to simulate retry exhaustion (for error testing) + const responseText = + result?.candidates?.[0]?.content?.parts?.[0]?.text; + if ( + !responseText || + responseText.trim() === '' || + responseText.includes('{"color": "blue"') + ) { + throw new Error('Retry attempts exhausted for invalid content'); + } } } - } - const context = options?.getAvailabilityContext?.(); - if (context) { - context.service.markHealthy(context.policy.model); - } + const context = options?.getAvailabilityContext?.(); + if (context) { + context.service.markHealthy(context.policy.model); + } - return result; - }), -})); + return result; + }), + }; +}); const mockGenerateContent = vi.fn(); const mockEmbedContent = vi.fn(); diff --git a/packages/core/src/core/baseLlmClient.ts b/packages/core/src/core/baseLlmClient.ts index 2b03f27b79..6352814f61 100644 --- a/packages/core/src/core/baseLlmClient.ts +++ b/packages/core/src/core/baseLlmClient.ts @@ -339,7 +339,9 @@ export class BaseLlmClient { retryFetchErrors: this.config.getRetryFetchErrors(), onRetry: (attempt, error, delayMs) => { const actualMaxAttempts = - availabilityMaxAttempts ?? maxAttempts ?? DEFAULT_MAX_ATTEMPTS; + getAvailabilityContext()?.policy.maxAttempts ?? + maxAttempts ?? + DEFAULT_MAX_ATTEMPTS; const modelName = getDisplayString(currentModel); const errorType = getRetryErrorType(error); diff --git a/packages/core/src/utils/retry.ts b/packages/core/src/utils/retry.ts index 9478d0f2c2..404b9cf0b2 100644 --- a/packages/core/src/utils/retry.ts +++ b/packages/core/src/utils/retry.ts @@ -249,6 +249,9 @@ export async function retryWithBackoff( ...cleanOptions, }; + const getCurrentMaxAttempts = () => + getAvailabilityContext?.()?.policy.maxAttempts ?? maxAttempts; + let attempt = 0; let currentDelay = initialDelayMs; const throwIfAborted = () => { @@ -257,7 +260,7 @@ export async function retryWithBackoff( } }; - while (attempt < maxAttempts) { + while (attempt < getCurrentMaxAttempts()) { if (signal?.aborted) { throw createAbortError(); } @@ -344,7 +347,7 @@ export async function retryWithBackoff( errorCode !== undefined && errorCode >= 500 && errorCode < 600; if (classifiedError instanceof RetryableQuotaError || is500) { - if (attempt >= maxAttempts) { + if (attempt >= getCurrentMaxAttempts()) { const errorMessage = classifiedError instanceof Error ? classifiedError.message : ''; debugLogger.warn( @@ -405,7 +408,7 @@ export async function retryWithBackoff( // Generic retry logic for other errors if ( - attempt >= maxAttempts || + attempt >= getCurrentMaxAttempts() || // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion !shouldRetryOnError(error as Error, retryFetchErrors) ) { diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 893c1bde6c..03ea0b2fda 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -709,7 +709,7 @@ "modelConfigs": { "title": "Model Configs", "description": "Model configurations.", - "markdownDescription": "Model configurations.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `{\n \"aliases\": {\n \"base\": {\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 0,\n \"topP\": 1\n }\n }\n },\n \"chat-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"includeThoughts\": true\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-base-2.5\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 8192\n }\n }\n }\n },\n \"chat-base-3\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n }\n }\n }\n },\n \"gemini-3-pro-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"gemini-3-flash-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"gemini-2.5-pro\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"gemini-2.5-flash\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"gemma-4-31b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-31b-it\"\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-26b-a4b-it\"\n }\n },\n \"gemini-2.5-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-3-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"classifier\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 1024,\n \"thinkingConfig\": {\n \"thinkingBudget\": 512\n }\n }\n }\n },\n \"prompt-completion\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.3,\n \"maxOutputTokens\": 16000,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"fast-ack-helper\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.2,\n \"maxOutputTokens\": 120,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"edit-corrector\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"summarizer-default\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"summarizer-shell\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"web-search\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"googleSearch\": {}\n }\n ]\n }\n }\n },\n \"web-fetch\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"urlContext\": {}\n }\n ]\n }\n }\n },\n \"web-fetch-fallback\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection-double-check\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"llm-edit-fixer\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"next-speaker-checker\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"chat-compression-3-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"chat-compression-3-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"chat-compression-3.1-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite-preview\"\n }\n },\n \"chat-compression-2.5-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"chat-compression-2.5-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"chat-compression-2.5-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"chat-compression-default\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"agent-history-provider-summarizer\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n }\n },\n \"overrides\": [\n {\n \"match\": {\n \"model\": \"chat-base\",\n \"isRetry\": true\n },\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 1\n }\n }\n }\n ],\n \"modelDefinitions\": {\n \"gemini-3.1-flash-lite-preview\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-flash-preview\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-2.5-pro\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-31b-it\": {\n \"displayName\": \"gemma-4-31b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"displayName\": \"gemma-4-26b-a4b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto\": {\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"pro\": {\n \"tier\": \"pro\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"flash\": {\n \"tier\": \"flash\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"flash-lite\": {\n \"tier\": \"flash-lite\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-3\": {\n \"displayName\": \"Auto (Gemini 3)\",\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"dialogDescription\": \"Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash\",\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-2.5\": {\n \"displayName\": \"Auto (Gemini 2.5)\",\n \"tier\": \"auto\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"dialogDescription\": \"Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash\",\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n }\n },\n \"modelIdResolutions\": {\n \"gemma-4-31b-it\": {\n \"default\": \"gemma-4-31b-it\"\n },\n \"gemma-4-26b-a4b-it\": {\n \"default\": \"gemma-4-26b-a4b-it\"\n },\n \"gemini-3.1-pro-preview\": {\n \"default\": \"gemini-3.1-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n }\n ]\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"default\": \"gemini-3.1-pro-preview-customtools\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n }\n ]\n },\n \"gemini-3-flash-preview\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"gemini-3-pro-preview\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-3\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-2.5\": {\n \"default\": \"gemini-2.5-pro\"\n },\n \"gemini-3.1-flash-lite-preview\": {\n \"default\": \"gemini-3.1-flash-lite-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_1FlashLite\": false\n },\n \"target\": \"gemini-2.5-flash-lite\"\n }\n ]\n },\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"flash-lite\": {\n \"default\": \"gemini-2.5-flash-lite\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_1FlashLite\": true\n },\n \"target\": \"gemini-3.1-flash-lite-preview\"\n }\n ]\n }\n },\n \"classifierIdResolutions\": {\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-2.5\",\n \"gemini-2.5-pro\"\n ]\n },\n \"target\": \"gemini-2.5-flash\"\n },\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-3\",\n \"gemini-3-pro-preview\"\n ]\n },\n \"target\": \"gemini-3-flash-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-2.5\",\n \"gemini-2.5-pro\"\n ]\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n }\n },\n \"modelChains\": {\n \"preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"lite\": [\n {\n \"model\": \"gemini-2.5-flash-lite\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-pro\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ]\n }\n}`", + "markdownDescription": "Model configurations.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `{\n \"aliases\": {\n \"base\": {\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 0,\n \"topP\": 1\n }\n }\n },\n \"chat-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"includeThoughts\": true\n },\n \"temperature\": 1,\n \"topP\": 0.95,\n \"topK\": 64\n }\n }\n },\n \"chat-base-2.5\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 8192\n }\n }\n }\n },\n \"chat-base-3\": {\n \"extends\": \"chat-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingLevel\": \"HIGH\"\n }\n }\n }\n },\n \"gemini-3-pro-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"gemini-3-flash-preview\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"gemini-2.5-pro\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"gemini-2.5-flash\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"extends\": \"chat-base-2.5\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"gemma-4-31b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-31b-it\"\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"extends\": \"chat-base-3\",\n \"modelConfig\": {\n \"model\": \"gemma-4-26b-a4b-it\"\n }\n },\n \"gemini-2.5-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"gemini-3-flash-base\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"classifier\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 1024,\n \"thinkingConfig\": {\n \"thinkingBudget\": 512\n }\n }\n }\n },\n \"prompt-completion\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.3,\n \"maxOutputTokens\": 16000,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"fast-ack-helper\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"temperature\": 0.2,\n \"maxOutputTokens\": 120,\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"edit-corrector\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"thinkingConfig\": {\n \"thinkingBudget\": 0\n }\n }\n }\n },\n \"summarizer-default\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"summarizer-shell\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\",\n \"generateContentConfig\": {\n \"maxOutputTokens\": 2000\n }\n }\n },\n \"web-search\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"googleSearch\": {}\n }\n ]\n }\n }\n },\n \"web-fetch\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"tools\": [\n {\n \"urlContext\": {}\n }\n ]\n }\n }\n },\n \"web-fetch-fallback\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"loop-detection-double-check\": {\n \"extends\": \"base\",\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"llm-edit-fixer\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"next-speaker-checker\": {\n \"extends\": \"gemini-3-flash-base\",\n \"modelConfig\": {}\n },\n \"chat-compression-3-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"chat-compression-3-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n },\n \"chat-compression-3.1-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-3.1-flash-lite-preview\"\n }\n },\n \"chat-compression-2.5-pro\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-pro\"\n }\n },\n \"chat-compression-2.5-flash\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash\"\n }\n },\n \"chat-compression-2.5-flash-lite\": {\n \"modelConfig\": {\n \"model\": \"gemini-2.5-flash-lite\"\n }\n },\n \"chat-compression-default\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-pro-preview\"\n }\n },\n \"agent-history-provider-summarizer\": {\n \"modelConfig\": {\n \"model\": \"gemini-3-flash-preview\"\n }\n }\n },\n \"overrides\": [\n {\n \"match\": {\n \"model\": \"chat-base\",\n \"isRetry\": true\n },\n \"modelConfig\": {\n \"generateContentConfig\": {\n \"temperature\": 1\n }\n }\n }\n ],\n \"modelDefinitions\": {\n \"gemini-3.1-flash-lite-preview\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-pro-preview\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-3-flash-preview\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-3\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": true\n }\n },\n \"gemini-2.5-pro\": {\n \"tier\": \"pro\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash\": {\n \"tier\": \"flash\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemini-2.5-flash-lite\": {\n \"tier\": \"flash-lite\",\n \"family\": \"gemini-2.5\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-31b-it\": {\n \"displayName\": \"gemma-4-31b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"gemma-4-26b-a4b-it\": {\n \"displayName\": \"gemma-4-26b-a4b-it\",\n \"tier\": \"custom\",\n \"family\": \"gemma-4\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto\": {\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"pro\": {\n \"tier\": \"pro\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"flash\": {\n \"tier\": \"flash\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"flash-lite\": {\n \"tier\": \"flash-lite\",\n \"isPreview\": false,\n \"isVisible\": false,\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-3\": {\n \"displayName\": \"Auto (Gemini 3)\",\n \"tier\": \"auto\",\n \"isPreview\": true,\n \"isVisible\": true,\n \"dialogDescription\": \"Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash\",\n \"features\": {\n \"thinking\": true,\n \"multimodalToolUse\": false\n }\n },\n \"auto-gemini-2.5\": {\n \"displayName\": \"Auto (Gemini 2.5)\",\n \"tier\": \"auto\",\n \"isPreview\": false,\n \"isVisible\": true,\n \"dialogDescription\": \"Let Gemini CLI decide the best model for the task: gemini-2.5-pro, gemini-2.5-flash\",\n \"features\": {\n \"thinking\": false,\n \"multimodalToolUse\": false\n }\n }\n },\n \"modelIdResolutions\": {\n \"gemma-4-31b-it\": {\n \"default\": \"gemma-4-31b-it\"\n },\n \"gemma-4-26b-a4b-it\": {\n \"default\": \"gemma-4-26b-a4b-it\"\n },\n \"gemini-3.1-pro-preview\": {\n \"default\": \"gemini-3.1-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n }\n ]\n },\n \"gemini-3.1-pro-preview-customtools\": {\n \"default\": \"gemini-3.1-pro-preview-customtools\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n }\n ]\n },\n \"gemini-3-flash-preview\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"gemini-3-pro-preview\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-3\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n },\n \"auto-gemini-2.5\": {\n \"default\": \"gemini-2.5-pro\"\n },\n \"gemini-3.1-flash-lite-preview\": {\n \"default\": \"gemini-3.1-flash-lite-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_1FlashLite\": false\n },\n \"target\": \"gemini-2.5-flash-lite\"\n }\n ]\n },\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"hasAccessToPreview\": false\n },\n \"target\": \"gemini-2.5-flash\"\n }\n ]\n },\n \"flash-lite\": {\n \"default\": \"gemini-2.5-flash-lite\",\n \"contexts\": [\n {\n \"condition\": {\n \"useGemini3_1FlashLite\": true\n },\n \"target\": \"gemini-3.1-flash-lite-preview\"\n }\n ]\n }\n },\n \"classifierIdResolutions\": {\n \"flash\": {\n \"default\": \"gemini-3-flash-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-2.5\",\n \"gemini-2.5-pro\"\n ]\n },\n \"target\": \"gemini-2.5-flash\"\n },\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-3\",\n \"gemini-3-pro-preview\"\n ]\n },\n \"target\": \"gemini-3-flash-preview\"\n }\n ]\n },\n \"pro\": {\n \"default\": \"gemini-3-pro-preview\",\n \"contexts\": [\n {\n \"condition\": {\n \"requestedModels\": [\n \"auto-gemini-2.5\",\n \"gemini-2.5-pro\"\n ]\n },\n \"target\": \"gemini-2.5-pro\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true,\n \"useCustomTools\": true\n },\n \"target\": \"gemini-3.1-pro-preview-customtools\"\n },\n {\n \"condition\": {\n \"useGemini3_1\": true\n },\n \"target\": \"gemini-3.1-pro-preview\"\n }\n ]\n }\n },\n \"modelChains\": {\n \"preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"lite\": [\n {\n \"model\": \"gemini-2.5-flash-lite\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-pro\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ]\n }\n}`", "default": { "aliases": { "base": { @@ -1370,7 +1370,7 @@ }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -1378,18 +1378,54 @@ { "model": "gemini-3-flash-preview", "isLastResort": true, + "maxAttempts": 10, "actions": { "terminal": "prompt", "transient": "prompt", "not_found": "prompt", "unknown": "prompt" }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } + } + ], + "auto-preview": [ + { + "model": "gemini-3-pro-preview", + "maxAttempts": 3, + "actions": { + "terminal": "prompt", + "transient": "silent", + "not_found": "prompt", + "unknown": "prompt" + }, "stateTransitions": { "terminal": "terminal", "transient": "sticky_retry", "not_found": "terminal", "unknown": "terminal" } + }, + { + "model": "gemini-3-flash-preview", + "isLastResort": true, + "maxAttempts": 10, + "actions": { + "terminal": "prompt", + "transient": "prompt", + "not_found": "prompt", + "unknown": "prompt" + }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } } ], "default": [ @@ -1411,18 +1447,54 @@ { "model": "gemini-2.5-flash", "isLastResort": true, + "maxAttempts": 10, "actions": { "terminal": "prompt", "transient": "prompt", "not_found": "prompt", "unknown": "prompt" }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } + } + ], + "auto-default": [ + { + "model": "gemini-2.5-pro", + "maxAttempts": 3, + "actions": { + "terminal": "prompt", + "transient": "silent", + "not_found": "prompt", + "unknown": "prompt" + }, "stateTransitions": { "terminal": "terminal", "transient": "sticky_retry", "not_found": "terminal", "unknown": "terminal" } + }, + { + "model": "gemini-2.5-flash", + "isLastResort": true, + "maxAttempts": 10, + "actions": { + "terminal": "prompt", + "transient": "prompt", + "not_found": "prompt", + "unknown": "prompt" + }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } } ], "lite": [ @@ -1436,7 +1508,7 @@ }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -1451,7 +1523,7 @@ }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -1467,7 +1539,7 @@ }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -2172,7 +2244,7 @@ "modelChains": { "title": "Model Chains", "description": "Availability policy chains defining fallback behavior for models.", - "markdownDescription": "Availability policy chains defining fallback behavior for models.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"lite\": [\n {\n \"model\": \"gemini-2.5-flash-lite\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-pro\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ]\n}`", + "markdownDescription": "Availability policy chains defining fallback behavior for models.\n\n- Category: `Model`\n- Requires restart: `yes`\n- Default: `{\n \"preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-preview\": [\n {\n \"model\": \"gemini-3-pro-preview\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-3-flash-preview\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"auto-default\": [\n {\n \"model\": \"gemini-2.5-pro\",\n \"maxAttempts\": 3,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"silent\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"sticky_retry\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"isLastResort\": true,\n \"maxAttempts\": 10,\n \"actions\": {\n \"terminal\": \"prompt\",\n \"transient\": \"prompt\",\n \"not_found\": \"prompt\",\n \"unknown\": \"prompt\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ],\n \"lite\": [\n {\n \"model\": \"gemini-2.5-flash-lite\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-flash\",\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n },\n {\n \"model\": \"gemini-2.5-pro\",\n \"isLastResort\": true,\n \"actions\": {\n \"terminal\": \"silent\",\n \"transient\": \"silent\",\n \"not_found\": \"silent\",\n \"unknown\": \"silent\"\n },\n \"stateTransitions\": {\n \"terminal\": \"terminal\",\n \"transient\": \"terminal\",\n \"not_found\": \"terminal\",\n \"unknown\": \"terminal\"\n }\n }\n ]\n}`", "default": { "preview": [ { @@ -2183,6 +2255,41 @@ "not_found": "prompt", "unknown": "prompt" }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } + }, + { + "model": "gemini-3-flash-preview", + "isLastResort": true, + "maxAttempts": 10, + "actions": { + "terminal": "prompt", + "transient": "prompt", + "not_found": "prompt", + "unknown": "prompt" + }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } + } + ], + "auto-preview": [ + { + "model": "gemini-3-pro-preview", + "maxAttempts": 3, + "actions": { + "terminal": "prompt", + "transient": "silent", + "not_found": "prompt", + "unknown": "prompt" + }, "stateTransitions": { "terminal": "terminal", "transient": "sticky_retry", @@ -2193,6 +2300,7 @@ { "model": "gemini-3-flash-preview", "isLastResort": true, + "maxAttempts": 10, "actions": { "terminal": "prompt", "transient": "prompt", @@ -2201,7 +2309,7 @@ }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -2226,18 +2334,54 @@ { "model": "gemini-2.5-flash", "isLastResort": true, + "maxAttempts": 10, "actions": { "terminal": "prompt", "transient": "prompt", "not_found": "prompt", "unknown": "prompt" }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } + } + ], + "auto-default": [ + { + "model": "gemini-2.5-pro", + "maxAttempts": 3, + "actions": { + "terminal": "prompt", + "transient": "silent", + "not_found": "prompt", + "unknown": "prompt" + }, "stateTransitions": { "terminal": "terminal", "transient": "sticky_retry", "not_found": "terminal", "unknown": "terminal" } + }, + { + "model": "gemini-2.5-flash", + "isLastResort": true, + "maxAttempts": 10, + "actions": { + "terminal": "prompt", + "transient": "prompt", + "not_found": "prompt", + "unknown": "prompt" + }, + "stateTransitions": { + "terminal": "terminal", + "transient": "terminal", + "not_found": "terminal", + "unknown": "terminal" + } } ], "lite": [ @@ -2251,7 +2395,7 @@ }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -2266,7 +2410,7 @@ }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" } @@ -2282,7 +2426,7 @@ }, "stateTransitions": { "terminal": "terminal", - "transient": "sticky_retry", + "transient": "terminal", "not_found": "terminal", "unknown": "terminal" }