From bb4341f6793d59cf21f01ea93a98dc61dbe8de16 Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Fri, 13 Feb 2026 10:27:03 -0800 Subject: [PATCH 1/2] feat(core): fallback to chat-base when using unrecognized models for chat Previously, passing an unknown model to the CLI via `-m custom-model` would result in an empty generation configuration, stripping standard interactive chat defaults like `temperature: 1`, `topP: 0.95` and `topK: 64`. This change updates the model configuration service to fallback to the `chat-base` configuration whenever `-m` provides an unrecognized custom model for the primary interactive chat session. Other internal background model queries (e.g., summarizer) will remain unaffected. --- packages/core/src/core/client.ts | 5 +- .../src/services/modelConfigService.test.ts | 65 +++++++++++++++++++ .../core/src/services/modelConfigService.ts | 21 ++++++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 6b6bdecfbca..fb9edaa7a58 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -656,7 +656,10 @@ export class GeminiClient { } // availability logic - const modelConfigKey: ModelConfigKey = { model: modelToUse }; + const modelConfigKey: ModelConfigKey = { + model: modelToUse, + isChatModel: true, + }; const { model: finalModel } = applyModelSelection( this.config, modelConfigKey, diff --git a/packages/core/src/services/modelConfigService.test.ts b/packages/core/src/services/modelConfigService.test.ts index c9ddda2e2be..767cb2ecfdd 100644 --- a/packages/core/src/services/modelConfigService.test.ts +++ b/packages/core/src/services/modelConfigService.test.ts @@ -729,6 +729,71 @@ describe('ModelConfigService', () => { }); }); + describe('fallback behavior', () => { + it('should fallback to chat-base if the requested model is completely unknown', () => { + const config: ModelConfigServiceConfig = { + aliases: { + 'chat-base': { + modelConfig: { + model: 'default-fallback-model', + generateContentConfig: { + temperature: 0.99, + }, + }, + }, + }, + }; + const service = new ModelConfigService(config); + const resolved = service.getResolvedConfig({ + model: 'my-custom-model', + isChatModel: true, + }); + + // It preserves the requested model name, but inherits the config from chat-base + expect(resolved.model).toBe('my-custom-model'); + expect(resolved.generateContentConfig).toEqual({ + temperature: 0.99, + }); + }); + + it('should return empty config if requested model is unknown and chat-base is not defined', () => { + const config: ModelConfigServiceConfig = { + aliases: {}, + }; + const service = new ModelConfigService(config); + const resolved = service.getResolvedConfig({ + model: 'my-custom-model', + isChatModel: true, + }); + + expect(resolved.model).toBe('my-custom-model'); + expect(resolved.generateContentConfig).toEqual({}); + }); + + it('should NOT fallback to chat-base if the requested model is completely unknown but isChatModel is false', () => { + const config: ModelConfigServiceConfig = { + aliases: { + 'chat-base': { + modelConfig: { + model: 'default-fallback-model', + generateContentConfig: { + temperature: 0.99, + }, + }, + }, + }, + }; + const service = new ModelConfigService(config); + const resolved = service.getResolvedConfig({ + model: 'my-custom-model', + isChatModel: false, + }); + + expect(resolved.model).toBe('my-custom-model'); + expect(resolved.generateContentConfig).toEqual({}); + }); + }); + describe('unrecognized models', () => { it('should apply overrides to unrecognized model names', () => { const unregisteredModelName = 'my-unregistered-model-v1'; diff --git a/packages/core/src/services/modelConfigService.ts b/packages/core/src/services/modelConfigService.ts index c43cbdcc91c..5142411be79 100644 --- a/packages/core/src/services/modelConfigService.ts +++ b/packages/core/src/services/modelConfigService.ts @@ -26,6 +26,10 @@ export interface ModelConfigKey { // This allows overrides to specify different settings (e.g., higher temperature) // specifically for retry scenarios. isRetry?: boolean; + + // Indicates whether this request originates from the primary interactive chat model. + // Enables the default fallback configuration to `chat-base` when unknown. + isChatModel?: boolean; } export interface ModelConfig { @@ -122,6 +126,7 @@ export class ModelConfigService { const { aliasChain, baseModel, resolvedConfig } = this.resolveAliasChain( context.model, allAliases, + context.isChatModel, ); const modelToLevel = this.buildModelLevelMap(aliasChain, baseModel); @@ -159,6 +164,7 @@ export class ModelConfigService { private resolveAliasChain( requestedModel: string, allAliases: Record, + isChatModel?: boolean, ): { aliasChain: string[]; baseModel: string | undefined; @@ -206,6 +212,21 @@ export class ModelConfigService { }; } + if (isChatModel) { + const fallbackAlias = 'chat-base'; + if (allAliases[fallbackAlias]) { + const fallbackResolution = this.resolveAliasChain( + fallbackAlias, + allAliases, + ); + return { + aliasChain: [...fallbackResolution.aliasChain, requestedModel], + baseModel: requestedModel, + resolvedConfig: fallbackResolution.resolvedConfig, + }; + } + } + return { aliasChain: [requestedModel], baseModel: requestedModel, From 919983ad81012d3a5440349f7c0c0742f852f252 Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Fri, 13 Feb 2026 10:41:35 -0800 Subject: [PATCH 2/2] fix(core): update tests to pass isChatModel and fix modelConfigKey type --- packages/core/src/core/client.test.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 900abac5918..185019434be 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -892,7 +892,7 @@ ${JSON.stringify( // Assert expect(ideContextStore.get).toHaveBeenCalled(); expect(mockTurnRunFn).toHaveBeenCalledWith( - { model: 'default-routed-model' }, + { model: 'default-routed-model', isChatModel: true }, initialRequest, expect.any(AbortSignal), undefined, @@ -1722,7 +1722,7 @@ ${JSON.stringify( expect(mockConfig.getModelRouterService).toHaveBeenCalled(); expect(mockRouterService.route).toHaveBeenCalled(); expect(mockTurnRunFn).toHaveBeenCalledWith( - { model: 'routed-model' }, + { model: 'routed-model', isChatModel: true }, [{ text: 'Hi' }], expect.any(AbortSignal), undefined, @@ -1740,7 +1740,7 @@ ${JSON.stringify( expect(mockRouterService.route).toHaveBeenCalledTimes(1); expect(mockTurnRunFn).toHaveBeenCalledWith( - { model: 'routed-model' }, + { model: 'routed-model', isChatModel: true }, [{ text: 'Hi' }], expect.any(AbortSignal), undefined, @@ -1758,7 +1758,7 @@ ${JSON.stringify( expect(mockRouterService.route).toHaveBeenCalledTimes(1); // Should stick to the first model expect(mockTurnRunFn).toHaveBeenCalledWith( - { model: 'routed-model' }, + { model: 'routed-model', isChatModel: true }, [{ text: 'Continue' }], expect.any(AbortSignal), undefined, @@ -1776,7 +1776,7 @@ ${JSON.stringify( expect(mockRouterService.route).toHaveBeenCalledTimes(1); expect(mockTurnRunFn).toHaveBeenCalledWith( - { model: 'routed-model' }, + { model: 'routed-model', isChatModel: true }, [{ text: 'Hi' }], expect.any(AbortSignal), undefined, @@ -1798,7 +1798,7 @@ ${JSON.stringify( expect(mockRouterService.route).toHaveBeenCalledTimes(2); // Should use the newly routed model expect(mockTurnRunFn).toHaveBeenCalledWith( - { model: 'new-routed-model' }, + { model: 'new-routed-model', isChatModel: true }, [{ text: 'A new topic' }], expect.any(AbortSignal), undefined, @@ -1826,7 +1826,7 @@ ${JSON.stringify( expect(mockRouterService.route).toHaveBeenCalledTimes(1); expect(mockTurnRunFn).toHaveBeenNthCalledWith( 1, - { model: 'original-model' }, + { model: 'original-model', isChatModel: true }, [{ text: 'Hi' }], expect.any(AbortSignal), undefined, @@ -1849,7 +1849,7 @@ ${JSON.stringify( expect(mockRouterService.route).toHaveBeenCalledTimes(2); expect(mockTurnRunFn).toHaveBeenNthCalledWith( 2, - { model: 'fallback-model' }, + { model: 'fallback-model', isChatModel: true }, [{ text: 'Continue' }], expect.any(AbortSignal), undefined, @@ -1935,7 +1935,7 @@ ${JSON.stringify( // First call with original request expect(mockTurnRunFn).toHaveBeenNthCalledWith( 1, - { model: 'default-routed-model' }, + { model: 'default-routed-model', isChatModel: true }, initialRequest, expect.any(AbortSignal), undefined, @@ -1944,7 +1944,7 @@ ${JSON.stringify( // Second call with "Please continue." expect(mockTurnRunFn).toHaveBeenNthCalledWith( 2, - { model: 'default-routed-model' }, + { model: 'default-routed-model', isChatModel: true }, [{ text: 'System: Please continue.' }], expect.any(AbortSignal), undefined,