feat: Add flash lite utility fallback chain (#17056)

This commit is contained in:
Adam Weidman
2026-01-20 14:43:43 -05:00
committed by GitHub
parent e5745f16cb
commit a16d5983cf
5 changed files with 101 additions and 11 deletions

View File

@@ -11,6 +11,7 @@ import type {
ModelPolicyStateMap,
} from './modelPolicy.js';
import {
DEFAULT_GEMINI_FLASH_LITE_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_MODEL,
PREVIEW_GEMINI_FLASH_MODEL,
@@ -36,6 +37,13 @@ const DEFAULT_ACTIONS: ModelPolicyActionMap = {
unknown: 'prompt',
};
const SILENT_ACTIONS: ModelPolicyActionMap = {
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
};
const DEFAULT_STATE: ModelPolicyStateMap = {
terminal: 'terminal',
transient: 'terminal',
@@ -53,6 +61,22 @@ const PREVIEW_CHAIN: ModelPolicyChain = [
definePolicy({ model: PREVIEW_GEMINI_FLASH_MODEL, isLastResort: true }),
];
const FLASH_LITE_CHAIN: ModelPolicyChain = [
definePolicy({
model: DEFAULT_GEMINI_FLASH_LITE_MODEL,
actions: SILENT_ACTIONS,
}),
definePolicy({
model: DEFAULT_GEMINI_FLASH_MODEL,
actions: SILENT_ACTIONS,
}),
definePolicy({
model: DEFAULT_GEMINI_MODEL,
isLastResort: true,
actions: SILENT_ACTIONS,
}),
];
/**
* Returns the default ordered model policy chain for the user.
*/
@@ -70,6 +94,10 @@ export function createSingleModelChain(model: string): ModelPolicyChain {
return [definePolicy({ model, isLastResort: true })];
}
export function getFlashLitePolicyChain(): ModelPolicyChain {
return cloneChain(FLASH_LITE_CHAIN);
}
/**
* Provides a default policy scaffold for models not present in the catalog.
*/

View File

@@ -12,7 +12,10 @@ import {
} from './policyHelpers.js';
import { createDefaultPolicy } from './policyCatalog.js';
import type { Config } from '../config/config.js';
import { DEFAULT_GEMINI_MODEL_AUTO } from '../config/models.js';
import {
DEFAULT_GEMINI_FLASH_LITE_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
} from '../config/models.js';
const createMockConfig = (overrides: Partial<Config> = {}): Config =>
({
@@ -53,6 +56,26 @@ describe('policyHelpers', () => {
expect(chain[1]?.model).toBe('gemini-2.5-flash');
});
it('uses auto chain when preferred model is auto', () => {
const config = createMockConfig({
getModel: () => 'gemini-2.5-pro',
});
const chain = resolvePolicyChain(config, DEFAULT_GEMINI_MODEL_AUTO);
expect(chain).toHaveLength(2);
expect(chain[0]?.model).toBe('gemini-2.5-pro');
expect(chain[1]?.model).toBe('gemini-2.5-flash');
});
it('uses auto chain when configured model is auto even if preferred is concrete', () => {
const config = createMockConfig({
getModel: () => DEFAULT_GEMINI_MODEL_AUTO,
});
const chain = resolvePolicyChain(config, 'gemini-2.5-pro');
expect(chain).toHaveLength(2);
expect(chain[0]?.model).toBe('gemini-2.5-pro');
expect(chain[1]?.model).toBe('gemini-2.5-flash');
});
it('starts chain from preferredModel when model is "auto"', () => {
const config = createMockConfig({
getModel: () => DEFAULT_GEMINI_MODEL_AUTO,
@@ -62,6 +85,28 @@ describe('policyHelpers', () => {
expect(chain[0]?.model).toBe('gemini-2.5-flash');
});
it('returns flash-lite chain when preferred model is flash-lite', () => {
const config = createMockConfig({
getModel: () => DEFAULT_GEMINI_MODEL_AUTO,
});
const chain = resolvePolicyChain(config, DEFAULT_GEMINI_FLASH_LITE_MODEL);
expect(chain).toHaveLength(3);
expect(chain[0]?.model).toBe('gemini-2.5-flash-lite');
expect(chain[1]?.model).toBe('gemini-2.5-flash');
expect(chain[2]?.model).toBe('gemini-2.5-pro');
});
it('returns flash-lite chain when configured model is flash-lite', () => {
const config = createMockConfig({
getModel: () => DEFAULT_GEMINI_FLASH_LITE_MODEL,
});
const chain = resolvePolicyChain(config);
expect(chain).toHaveLength(3);
expect(chain[0]?.model).toBe('gemini-2.5-flash-lite');
expect(chain[1]?.model).toBe('gemini-2.5-flash');
expect(chain[2]?.model).toBe('gemini-2.5-pro');
});
it('wraps around the chain when wrapsAround is true', () => {
const config = createMockConfig({
getModel: () => DEFAULT_GEMINI_MODEL_AUTO,

View File

@@ -17,11 +17,13 @@ import {
createDefaultPolicy,
createSingleModelChain,
getModelPolicyChain,
getFlashLitePolicyChain,
} from './policyCatalog.js';
import {
DEFAULT_GEMINI_FLASH_LITE_MODEL,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL_AUTO,
PREVIEW_GEMINI_MODEL_AUTO,
isAutoModel,
resolveModel,
} from '../config/models.js';
import type { ModelSelectionResult } from './modelAvailabilityService.js';
@@ -38,24 +40,30 @@ export function resolvePolicyChain(
): ModelPolicyChain {
const modelFromConfig =
preferredModel ?? config.getActiveModel?.() ?? config.getModel();
const configuredModel = config.getModel();
let chain;
const resolvedModel = resolveModel(modelFromConfig);
const isAutoPreferred = preferredModel ? isAutoModel(preferredModel) : false;
const isAutoConfigured = isAutoModel(configuredModel);
if (
config.getModel() === PREVIEW_GEMINI_MODEL_AUTO ||
config.getModel() === DEFAULT_GEMINI_MODEL_AUTO
) {
if (resolvedModel === DEFAULT_GEMINI_FLASH_LITE_MODEL) {
chain = getFlashLitePolicyChain();
} else if (isAutoPreferred || isAutoConfigured) {
const previewEnabled =
preferredModel === PREVIEW_GEMINI_MODEL_AUTO ||
configuredModel === PREVIEW_GEMINI_MODEL_AUTO;
chain = getModelPolicyChain({
previewEnabled: config.getModel() === PREVIEW_GEMINI_MODEL_AUTO,
previewEnabled,
userTier: config.getUserTier(),
});
} else {
chain = createSingleModelChain(modelFromConfig);
}
const activeModel = resolveModel(modelFromConfig);
const activeIndex = chain.findIndex((policy) => policy.model === activeModel);
const activeIndex = chain.findIndex(
(policy) => policy.model === resolvedModel,
);
if (activeIndex !== -1) {
return wrapsAround
? [...chain.slice(activeIndex), ...chain.slice(0, activeIndex)]
@@ -64,7 +72,7 @@ export function resolvePolicyChain(
// If the user specified a model not in the default chain, we assume they want
// *only* that model. We do not fallback to the default chain.
return [createDefaultPolicy(activeModel, { isLastResort: true })];
return [createDefaultPolicy(resolvedModel, { isLastResort: true })];
}
/**