Add ModelChain support to ModelConfigService and make ModelDialog dynamic (#22914)

This commit is contained in:
kevinjwang1
2026-03-19 15:22:26 -07:00
committed by GitHub
parent 0e66f545ca
commit 06a7873c51
11 changed files with 1014 additions and 18 deletions
+5
View File
@@ -994,6 +994,10 @@ export class Config implements McpContext, AgentLoopContext {
...DEFAULT_MODEL_CONFIGS.classifierIdResolutions,
...modelConfigServiceConfig.classifierIdResolutions,
};
const mergedModelChains = {
...DEFAULT_MODEL_CONFIGS.modelChains,
...modelConfigServiceConfig.modelChains,
};
modelConfigServiceConfig = {
// Preserve other user settings like customAliases
@@ -1007,6 +1011,7 @@ export class Config implements McpContext, AgentLoopContext {
modelDefinitions: mergedModelDefinitions,
modelIdResolutions: mergedModelIdResolutions,
classifierIdResolutions: mergedClassifierIdResolutions,
modelChains: mergedModelChains,
};
}
+145 -1
View File
@@ -251,6 +251,13 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
],
modelDefinitions: {
// Concrete Models
'gemini-3.1-flash-lite-preview': {
tier: 'flash-lite',
family: 'gemini-3',
isPreview: true,
isVisible: true,
features: { thinking: false, multimodalToolUse: true },
},
'gemini-3.1-pro-preview': {
tier: 'pro',
family: 'gemini-3',
@@ -331,7 +338,7 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
isPreview: true,
isVisible: true,
dialogDescription:
'Let Gemini CLI decide the best model for the task: gemini-3.1-pro, gemini-3-flash',
'Let Gemini CLI decide the best model for the task: gemini-3-pro, gemini-3-flash',
features: { thinking: true, multimodalToolUse: false },
},
'auto-gemini-2.5': {
@@ -345,6 +352,27 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
},
},
modelIdResolutions: {
'gemini-3.1-pro-preview': {
default: 'gemini-3.1-pro-preview',
contexts: [
{ condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' },
],
},
'gemini-3.1-pro-preview-customtools': {
default: 'gemini-3.1-pro-preview-customtools',
contexts: [
{ condition: { hasAccessToPreview: false }, target: 'gemini-2.5-pro' },
],
},
'gemini-3-flash-preview': {
default: 'gemini-3-flash-preview',
contexts: [
{
condition: { hasAccessToPreview: false },
target: 'gemini-2.5-flash',
},
],
},
'gemini-3-pro-preview': {
default: 'gemini-3-pro-preview',
contexts: [
@@ -451,4 +479,120 @@ export const DEFAULT_MODEL_CONFIGS: ModelConfigServiceConfig = {
],
},
},
modelChains: {
preview: [
{
model: 'gemini-3-pro-preview',
actions: {
terminal: 'prompt',
transient: 'prompt',
not_found: 'prompt',
unknown: 'prompt',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
{
model: 'gemini-3-flash-preview',
isLastResort: true,
actions: {
terminal: 'prompt',
transient: 'prompt',
not_found: 'prompt',
unknown: 'prompt',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
],
default: [
{
model: 'gemini-2.5-pro',
actions: {
terminal: 'prompt',
transient: 'prompt',
not_found: 'prompt',
unknown: 'prompt',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
{
model: 'gemini-2.5-flash',
isLastResort: true,
actions: {
terminal: 'prompt',
transient: 'prompt',
not_found: 'prompt',
unknown: 'prompt',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
],
lite: [
{
model: 'gemini-2.5-flash-lite',
actions: {
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
{
model: 'gemini-2.5-flash',
actions: {
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
{
model: 'gemini-2.5-pro',
isLastResort: true,
actions: {
terminal: 'silent',
transient: 'silent',
not_found: 'silent',
unknown: 'silent',
},
stateTransitions: {
terminal: 'terminal',
transient: 'terminal',
not_found: 'terminal',
unknown: 'terminal',
},
},
],
},
};
-8
View File
@@ -190,14 +190,6 @@ describe('Dynamic Configuration Parity', () => {
}
});
it('supportsModernFeatures should match legacy behavior', () => {
for (const model of modelsToTest) {
const legacy = supportsModernFeatures(model);
const dynamic = supportsModernFeatures(model);
expect(dynamic).toBe(legacy);
}
});
it('supportsMultimodalFunctionResponse should match legacy behavior', () => {
for (const model of modelsToTest) {
const legacy = supportsMultimodalFunctionResponse(model, legacyConfig);
+14 -1
View File
@@ -102,11 +102,24 @@ export function resolveModel(
config?: ModelCapabilityContext,
): string {
if (config?.getExperimentalDynamicModelConfiguration?.() === true) {
return config.modelConfigService.resolveModelId(requestedModel, {
const resolved = config.modelConfigService.resolveModelId(requestedModel, {
useGemini3_1,
useCustomTools: useCustomToolModel,
hasAccessToPreview,
});
if (!hasAccessToPreview && isPreviewModel(resolved, config)) {
// Fallback for unknown preview models.
if (resolved.includes('flash-lite')) {
return DEFAULT_GEMINI_FLASH_LITE_MODEL;
}
if (resolved.includes('flash')) {
return DEFAULT_GEMINI_FLASH_MODEL;
}
return DEFAULT_GEMINI_MODEL;
}
return resolved;
}
let resolved: string;