mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-26 04:54:25 -07:00
feat(core): implement silent fallback for Plan Mode model routing (#25317)
This commit is contained in:
@@ -445,6 +445,10 @@ on the current phase of your task:
|
|||||||
switches to a high-speed **Flash** model. This provides a faster, more
|
switches to a high-speed **Flash** model. This provides a faster, more
|
||||||
responsive experience during the implementation of the plan.
|
responsive experience during the implementation of the plan.
|
||||||
|
|
||||||
|
If the high-reasoning model is unavailable or you don't have access to it,
|
||||||
|
Gemini CLI automatically and silently falls back to a faster model to ensure
|
||||||
|
your workflow isn't interrupted.
|
||||||
|
|
||||||
This behavior is enabled by default to provide the best balance of quality and
|
This behavior is enabled by default to provide the best balance of quality and
|
||||||
performance. You can disable this automatic switching in your settings:
|
performance. You can disable this automatic switching in your settings:
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const DEFAULT_ACTIONS: ModelPolicyActionMap = {
|
|||||||
unknown: 'prompt',
|
unknown: 'prompt',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SILENT_ACTIONS: ModelPolicyActionMap = {
|
export const SILENT_ACTIONS: ModelPolicyActionMap = {
|
||||||
terminal: 'silent',
|
terminal: 'silent',
|
||||||
transient: 'silent',
|
transient: 'silent',
|
||||||
not_found: 'silent',
|
not_found: 'silent',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
buildFallbackPolicyContext,
|
buildFallbackPolicyContext,
|
||||||
applyModelSelection,
|
applyModelSelection,
|
||||||
} from './policyHelpers.js';
|
} from './policyHelpers.js';
|
||||||
import { createDefaultPolicy } from './policyCatalog.js';
|
import { createDefaultPolicy, SILENT_ACTIONS } from './policyCatalog.js';
|
||||||
import type { Config } from '../config/config.js';
|
import type { Config } from '../config/config.js';
|
||||||
import {
|
import {
|
||||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
import { AuthType } from '../core/contentGenerator.js';
|
import { AuthType } from '../core/contentGenerator.js';
|
||||||
import { ModelConfigService } from '../services/modelConfigService.js';
|
import { ModelConfigService } from '../services/modelConfigService.js';
|
||||||
import { DEFAULT_MODEL_CONFIGS } from '../config/defaultModelConfigs.js';
|
import { DEFAULT_MODEL_CONFIGS } from '../config/defaultModelConfigs.js';
|
||||||
|
import { ApprovalMode } from '../policy/types.js';
|
||||||
|
|
||||||
const createMockConfig = (overrides: Partial<Config> = {}): Config => {
|
const createMockConfig = (overrides: Partial<Config> = {}): Config => {
|
||||||
const config = {
|
const config = {
|
||||||
@@ -164,6 +165,18 @@ describe('policyHelpers', () => {
|
|||||||
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
|
expect(chain[0]?.model).toBe(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL);
|
||||||
expect(chain[1]?.model).toBe('gemini-3-flash-preview');
|
expect(chain[1]?.model).toBe('gemini-3-flash-preview');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('applies SILENT_ACTIONS when ApprovalMode is PLAN', () => {
|
||||||
|
const config = createMockConfig({
|
||||||
|
getApprovalMode: () => ApprovalMode.PLAN,
|
||||||
|
getModel: () => DEFAULT_GEMINI_MODEL_AUTO,
|
||||||
|
});
|
||||||
|
const chain = resolvePolicyChain(config);
|
||||||
|
|
||||||
|
expect(chain).toHaveLength(2);
|
||||||
|
expect(chain[0]?.actions).toEqual(SILENT_ACTIONS);
|
||||||
|
expect(chain[1]?.actions).toEqual(SILENT_ACTIONS);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('resolvePolicyChain behavior is identical between dynamic and legacy implementations', () => {
|
describe('resolvePolicyChain behavior is identical between dynamic and legacy implementations', () => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
createSingleModelChain,
|
createSingleModelChain,
|
||||||
getModelPolicyChain,
|
getModelPolicyChain,
|
||||||
getFlashLitePolicyChain,
|
getFlashLitePolicyChain,
|
||||||
|
SILENT_ACTIONS,
|
||||||
} from './policyCatalog.js';
|
} from './policyCatalog.js';
|
||||||
import {
|
import {
|
||||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||||
@@ -29,6 +30,7 @@ import {
|
|||||||
} from '../config/models.js';
|
} from '../config/models.js';
|
||||||
import type { ModelSelectionResult } from './modelAvailabilityService.js';
|
import type { ModelSelectionResult } from './modelAvailabilityService.js';
|
||||||
import type { ModelConfigKey } from '../services/modelConfigService.js';
|
import type { ModelConfigKey } from '../services/modelConfigService.js';
|
||||||
|
import { ApprovalMode } from '../policy/types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the active policy chain for the given config, ensuring the
|
* Resolves the active policy chain for the given config, ensuring the
|
||||||
@@ -43,7 +45,7 @@ export function resolvePolicyChain(
|
|||||||
preferredModel ?? config.getActiveModel?.() ?? config.getModel();
|
preferredModel ?? config.getActiveModel?.() ?? config.getModel();
|
||||||
const configuredModel = config.getModel();
|
const configuredModel = config.getModel();
|
||||||
|
|
||||||
let chain;
|
let chain: ModelPolicyChain | undefined;
|
||||||
const useGemini31 = config.getGemini31LaunchedSync?.() ?? false;
|
const useGemini31 = config.getGemini31LaunchedSync?.() ?? false;
|
||||||
const useGemini31FlashLite =
|
const useGemini31FlashLite =
|
||||||
config.getGemini31FlashLiteLaunchedSync?.() ?? false;
|
config.getGemini31FlashLiteLaunchedSync?.() ?? false;
|
||||||
@@ -103,45 +105,55 @@ export function resolvePolicyChain(
|
|||||||
// No matching modelChains found, default to single model chain
|
// No matching modelChains found, default to single model chain
|
||||||
chain = createSingleModelChain(modelFromConfig);
|
chain = createSingleModelChain(modelFromConfig);
|
||||||
}
|
}
|
||||||
return applyDynamicSlicing(chain, resolvedModel, wrapsAround);
|
chain = applyDynamicSlicing(chain, resolvedModel, wrapsAround);
|
||||||
}
|
|
||||||
|
|
||||||
// --- LEGACY PATH ---
|
|
||||||
|
|
||||||
if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) {
|
|
||||||
chain = getFlashLitePolicyChain();
|
|
||||||
} else if (
|
|
||||||
isGemini3Model(resolvedModel, config) ||
|
|
||||||
isAutoPreferred ||
|
|
||||||
isAutoConfigured
|
|
||||||
) {
|
|
||||||
if (hasAccessToPreview) {
|
|
||||||
const previewEnabled =
|
|
||||||
isGemini3Model(resolvedModel, config) ||
|
|
||||||
preferredModel === PREVIEW_GEMINI_MODEL_AUTO ||
|
|
||||||
configuredModel === PREVIEW_GEMINI_MODEL_AUTO;
|
|
||||||
chain = getModelPolicyChain({
|
|
||||||
previewEnabled,
|
|
||||||
userTier: config.getUserTier(),
|
|
||||||
useGemini31,
|
|
||||||
useGemini31FlashLite,
|
|
||||||
useCustomToolModel,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// User requested Gemini 3 but has no access. Proactively downgrade
|
|
||||||
// to the stable Gemini 2.5 chain.
|
|
||||||
chain = getModelPolicyChain({
|
|
||||||
previewEnabled: false,
|
|
||||||
userTier: config.getUserTier(),
|
|
||||||
useGemini31,
|
|
||||||
useGemini31FlashLite,
|
|
||||||
useCustomToolModel,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
chain = createSingleModelChain(modelFromConfig);
|
// --- LEGACY PATH ---
|
||||||
|
|
||||||
|
if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) {
|
||||||
|
chain = getFlashLitePolicyChain();
|
||||||
|
} else if (
|
||||||
|
isGemini3Model(resolvedModel, config) ||
|
||||||
|
isAutoPreferred ||
|
||||||
|
isAutoConfigured
|
||||||
|
) {
|
||||||
|
if (hasAccessToPreview) {
|
||||||
|
const previewEnabled =
|
||||||
|
isGemini3Model(resolvedModel, config) ||
|
||||||
|
preferredModel === PREVIEW_GEMINI_MODEL_AUTO ||
|
||||||
|
configuredModel === PREVIEW_GEMINI_MODEL_AUTO;
|
||||||
|
chain = getModelPolicyChain({
|
||||||
|
previewEnabled,
|
||||||
|
userTier: config.getUserTier(),
|
||||||
|
useGemini31,
|
||||||
|
useGemini31FlashLite,
|
||||||
|
useCustomToolModel,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// User requested Gemini 3 but has no access. Proactively downgrade
|
||||||
|
// to the stable Gemini 2.5 chain.
|
||||||
|
chain = getModelPolicyChain({
|
||||||
|
previewEnabled: false,
|
||||||
|
userTier: config.getUserTier(),
|
||||||
|
useGemini31,
|
||||||
|
useGemini31FlashLite,
|
||||||
|
useCustomToolModel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chain = createSingleModelChain(modelFromConfig);
|
||||||
|
}
|
||||||
|
chain = applyDynamicSlicing(chain, resolvedModel, wrapsAround);
|
||||||
}
|
}
|
||||||
return applyDynamicSlicing(chain, resolvedModel, wrapsAround);
|
|
||||||
|
// Apply Unified Silent Injection for Plan Mode with defensive checks
|
||||||
|
if (config?.getApprovalMode?.() === ApprovalMode.PLAN) {
|
||||||
|
return chain.map((policy) => ({
|
||||||
|
...policy,
|
||||||
|
actions: { ...SILENT_ACTIONS },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user