mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 18:14:29 -07:00
fix(acp) refactor(core,cli): centralize model discovery logic in ModelConfigService (#24392)
This commit is contained in:
@@ -2683,6 +2683,10 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
return this.modelRouterService;
|
||||
}
|
||||
|
||||
getModelConfigService(): ModelConfigService {
|
||||
return this.modelConfigService;
|
||||
}
|
||||
|
||||
getModelAvailabilityService(): ModelAvailabilityService {
|
||||
return this.modelAvailabilityService;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ export * from './scheduler/tool-executor.js';
|
||||
export * from './scheduler/policy.js';
|
||||
export * from './core/recordingContentGenerator.js';
|
||||
|
||||
// Export Routing
|
||||
export * from './routing/routingStrategy.js';
|
||||
export * from './routing/modelRouterService.js';
|
||||
|
||||
export * from './fallback/types.js';
|
||||
export * from './fallback/handler.js';
|
||||
|
||||
@@ -132,6 +136,7 @@ export * from './services/FolderTrustDiscoveryService.js';
|
||||
export * from './services/chatRecordingService.js';
|
||||
export * from './services/fileSystemService.js';
|
||||
export * from './services/sandboxedFileSystemService.js';
|
||||
export * from './services/modelConfigService.js';
|
||||
export * from './sandbox/windows/WindowsSandboxManager.js';
|
||||
export * from './services/sessionSummaryUtils.js';
|
||||
export * from './context/contextManager.js';
|
||||
|
||||
@@ -1018,4 +1018,41 @@ describe('ModelConfigService', () => {
|
||||
expect(retry.generateContentConfig.temperature).toBe(1.0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvailableModelOptions', () => {
|
||||
it('should filter out Pro models when hasAccessToProModel is false', () => {
|
||||
const config: ModelConfigServiceConfig = {
|
||||
modelDefinitions: {
|
||||
'gemini-3-pro': { isVisible: true, tier: 'pro' },
|
||||
'gemini-3-flash': { isVisible: true, tier: 'flash' },
|
||||
},
|
||||
};
|
||||
const service = new ModelConfigService(config);
|
||||
const options = service.getAvailableModelOptions({
|
||||
hasAccessToProModel: false,
|
||||
});
|
||||
|
||||
expect(options.map((o) => o.modelId)).not.toContain('gemini-3-pro');
|
||||
expect(options.map((o) => o.modelId)).toContain('gemini-3-flash');
|
||||
});
|
||||
|
||||
it('should include Pro models when hasAccessToProModel is true or undefined', () => {
|
||||
const config: ModelConfigServiceConfig = {
|
||||
modelDefinitions: {
|
||||
'gemini-3-pro': { isVisible: true, tier: 'pro' },
|
||||
},
|
||||
};
|
||||
const service = new ModelConfigService(config);
|
||||
|
||||
const optionsWithTrue = service.getAvailableModelOptions({
|
||||
hasAccessToProModel: true,
|
||||
});
|
||||
expect(optionsWithTrue.map((o) => o.modelId)).toContain('gemini-3-pro');
|
||||
|
||||
const optionsWithUndefined = service.getAvailableModelOptions({});
|
||||
expect(optionsWithUndefined.map((o) => o.modelId)).toContain(
|
||||
'gemini-3-pro',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
|
||||
import type { GenerateContentConfig } from '@google/genai';
|
||||
import type { ModelPolicy } from '../availability/modelPolicy.js';
|
||||
import {
|
||||
getDisplayString,
|
||||
PREVIEW_GEMINI_3_1_MODEL,
|
||||
PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL,
|
||||
isProModel,
|
||||
} from '../config/models.js';
|
||||
|
||||
// The primary key for the ModelConfig is the model string. However, we also
|
||||
// support a secondary key to limit the override scope, typically an agent name.
|
||||
@@ -93,6 +99,7 @@ export interface ResolutionContext {
|
||||
useGemini3_1FlashLite?: boolean;
|
||||
useCustomTools?: boolean;
|
||||
hasAccessToPreview?: boolean;
|
||||
hasAccessToProModel?: boolean;
|
||||
requestedModel?: string;
|
||||
}
|
||||
|
||||
@@ -135,6 +142,78 @@ export class ModelConfigService {
|
||||
// TODO(12597): Process config to build a typed alias hierarchy.
|
||||
constructor(private readonly config: ModelConfigServiceConfig) {}
|
||||
|
||||
/**
|
||||
* Returns a standardized list of available model options based on the resolution context.
|
||||
* This logic is shared across the TUI and ACP mode.
|
||||
*/
|
||||
getAvailableModelOptions(context: ResolutionContext): Array<{
|
||||
modelId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
tier: string;
|
||||
}> {
|
||||
const definitions = this.config.modelDefinitions ?? {};
|
||||
const shouldShowPreviewModels = context.hasAccessToPreview ?? false;
|
||||
const useGemini31 = context.useGemini3_1 ?? false;
|
||||
const useGemini31FlashLite = context.useGemini3_1FlashLite ?? false;
|
||||
|
||||
const mainOptions = Object.entries(definitions)
|
||||
.filter(([_, m]) => {
|
||||
if (m.isVisible !== true) return false;
|
||||
if (m.isPreview && !shouldShowPreviewModels) return false;
|
||||
if (m.tier !== 'auto') return false;
|
||||
return true;
|
||||
})
|
||||
.map(([id, m]) => ({
|
||||
modelId: id,
|
||||
name: m.displayName ?? getDisplayString(id),
|
||||
description:
|
||||
id === 'auto-gemini-3' && useGemini31
|
||||
? (m.dialogDescription ?? '').replace(
|
||||
'gemini-3-pro',
|
||||
'gemini-3.1-pro',
|
||||
)
|
||||
: (m.dialogDescription ?? ''),
|
||||
tier: m.tier ?? 'auto',
|
||||
}));
|
||||
|
||||
const manualOptions = Object.entries(definitions)
|
||||
.filter(([id, m]) => {
|
||||
if (m.isVisible !== true) return false;
|
||||
if (m.isPreview && !shouldShowPreviewModels) return false;
|
||||
if (m.tier === 'auto') return false;
|
||||
if (context.hasAccessToProModel === false && isProModel(id))
|
||||
return false;
|
||||
if (id === PREVIEW_GEMINI_3_1_MODEL && !useGemini31) return false;
|
||||
if (id === PREVIEW_GEMINI_3_1_FLASH_LITE_MODEL && !useGemini31FlashLite)
|
||||
return false;
|
||||
return true;
|
||||
})
|
||||
.map(([id, m]) => {
|
||||
const resolvedId = this.resolveModelId(id, context);
|
||||
const titleId = this.resolveModelId(id, {
|
||||
useGemini3_1: useGemini31,
|
||||
useGemini3_1FlashLite: useGemini31FlashLite,
|
||||
});
|
||||
return {
|
||||
modelId: resolvedId,
|
||||
name: m.displayName ?? getDisplayString(titleId),
|
||||
description: m.dialogDescription ?? '',
|
||||
tier: m.tier ?? 'custom',
|
||||
};
|
||||
});
|
||||
|
||||
// Deduplicate manual options
|
||||
const seen = new Set<string>();
|
||||
const uniqueManualOptions = manualOptions.filter((option) => {
|
||||
if (seen.has(option.modelId)) return false;
|
||||
seen.add(option.modelId);
|
||||
return true;
|
||||
});
|
||||
|
||||
return [...mainOptions, ...uniqueManualOptions];
|
||||
}
|
||||
|
||||
getModelDefinition(modelId: string): ModelDefinition | undefined {
|
||||
const definition = this.config.modelDefinitions?.[modelId];
|
||||
if (definition) {
|
||||
|
||||
Reference in New Issue
Block a user