From 42c2e1b2176eec7a2208fdec22c40e76c430b8a5 Mon Sep 17 00:00:00 2001 From: joshualitt Date: Fri, 21 Nov 2025 16:13:10 -0800 Subject: [PATCH] feat(core): Add support for custom aliases for model configs. (#13546) --- docs/get-started/configuration.md | 5 + packages/cli/src/config/settingsSchema.ts | 10 ++ .../src/services/modelConfigService.test.ts | 121 ++++++++++++++++++ .../core/src/services/modelConfigService.ts | 9 +- schemas/settings.schema.json | 8 ++ 5 files changed, 151 insertions(+), 2 deletions(-) diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index ecbedc5c37..101c003358 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -494,6 +494,11 @@ their corresponding top-level category object in your `settings.json` file. } ``` +- **`modelConfigs.customAliases`** (object): + - **Description:** Custom named presets for model configs. These are merged + with (and override) the built-in aliases. + - **Default:** `{}` + - **`modelConfigs.overrides`** (array): - **Description:** Apply specific configuration overrides based on matches, with a primary key of model (or alias). The most specific match will be diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 2eb7958460..5222ed32f4 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -729,6 +729,16 @@ const SETTINGS_SCHEMA = { 'Named presets for model configs. Can be used in place of a model name and can inherit from other aliases using an `extends` property.', showInDialog: false, }, + customAliases: { + type: 'object', + label: 'Custom Model Config Aliases', + category: 'Model', + requiresRestart: false, + default: {}, + description: + 'Custom named presets for model configs. These are merged with (and override) the built-in aliases.', + showInDialog: false, + }, overrides: { type: 'array', label: 'Model Config Overrides', diff --git a/packages/core/src/services/modelConfigService.test.ts b/packages/core/src/services/modelConfigService.test.ts index ba5773b906..0ec08c4535 100644 --- a/packages/core/src/services/modelConfigService.test.ts +++ b/packages/core/src/services/modelConfigService.test.ts @@ -576,4 +576,125 @@ describe('ModelConfigService', () => { }); }); }); + + describe('custom aliases', () => { + it('should resolve a custom alias', () => { + const config: ModelConfigServiceConfig = { + aliases: {}, + customAliases: { + 'my-custom-alias': { + modelConfig: { + model: 'gemini-custom', + generateContentConfig: { + temperature: 0.9, + }, + }, + }, + }, + overrides: [], + }; + const service = new ModelConfigService(config); + const resolved = service.getResolvedConfig({ model: 'my-custom-alias' }); + + expect(resolved.model).toBe('gemini-custom'); + expect(resolved.generateContentConfig).toEqual({ + temperature: 0.9, + }); + }); + + it('should allow custom aliases to override built-in aliases', () => { + const config: ModelConfigServiceConfig = { + aliases: { + 'standard-alias': { + modelConfig: { + model: 'gemini-standard', + generateContentConfig: { + temperature: 0.5, + }, + }, + }, + }, + customAliases: { + 'standard-alias': { + modelConfig: { + model: 'gemini-custom-override', + generateContentConfig: { + temperature: 0.1, + }, + }, + }, + }, + overrides: [], + }; + const service = new ModelConfigService(config); + const resolved = service.getResolvedConfig({ model: 'standard-alias' }); + + expect(resolved.model).toBe('gemini-custom-override'); + expect(resolved.generateContentConfig).toEqual({ + temperature: 0.1, + }); + }); + }); + + describe('unrecognized models', () => { + it('should apply overrides to unrecognized model names', () => { + const unregisteredModelName = 'my-unregistered-model-v1'; + const config: ModelConfigServiceConfig = { + aliases: {}, // No aliases defined + overrides: [ + { + match: { model: unregisteredModelName }, + modelConfig: { + generateContentConfig: { + temperature: 0.01, + }, + }, + }, + ], + }; + const service = new ModelConfigService(config); + + // Request the unregistered model directly + const resolved = service.getResolvedConfig({ + model: unregisteredModelName, + }); + + // It should preserve the model name and apply the override + expect(resolved.model).toBe(unregisteredModelName); + expect(resolved.generateContentConfig).toEqual({ + temperature: 0.01, + }); + }); + + it('should apply scoped overrides to unrecognized model names', () => { + const unregisteredModelName = 'my-unregistered-model-v1'; + const config: ModelConfigServiceConfig = { + aliases: {}, + overrides: [ + { + match: { + model: unregisteredModelName, + overrideScope: 'special-agent', + }, + modelConfig: { + generateContentConfig: { + temperature: 0.99, + }, + }, + }, + ], + }; + const service = new ModelConfigService(config); + + const resolved = service.getResolvedConfig({ + model: unregisteredModelName, + overrideScope: 'special-agent', + }); + + expect(resolved.model).toBe(unregisteredModelName); + expect(resolved.generateContentConfig).toEqual({ + temperature: 0.99, + }); + }); + }); }); diff --git a/packages/core/src/services/modelConfigService.ts b/packages/core/src/services/modelConfigService.ts index eb9d8750a1..9c7a5f5a02 100644 --- a/packages/core/src/services/modelConfigService.ts +++ b/packages/core/src/services/modelConfigService.ts @@ -43,6 +43,7 @@ export interface ModelConfigAlias { export interface ModelConfigServiceConfig { aliases?: Record; + customAliases?: Record; overrides?: ModelConfigOverride[]; } @@ -104,8 +105,12 @@ export class ModelConfigService { generateContentConfig: GenerateContentConfig; } { const config = this.config || {}; - const { aliases = {}, overrides = [] } = config; - const allAliases = { ...aliases, ...this.runtimeAliases }; + const { aliases = {}, customAliases = {}, overrides = [] } = config; + const allAliases = { + ...aliases, + ...customAliases, + ...this.runtimeAliases, + }; let baseModel: string | undefined = context.model; let resolvedConfig: GenerateContentConfig = {}; diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index f3301802b0..a874852120 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -794,6 +794,14 @@ "type": "object", "additionalProperties": true }, + "customAliases": { + "title": "Custom Model Config Aliases", + "description": "Custom named presets for model configs. These are merged with (and override) the built-in aliases.", + "markdownDescription": "Custom named presets for model configs. These are merged with (and override) the built-in aliases.\n\n- Category: `Model`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "additionalProperties": true + }, "overrides": { "title": "Model Config Overrides", "description": "Apply specific configuration overrides based on matches, with a primary key of model (or alias). The most specific match will be used.",