From b0ffa3b51ea057fe2d71d6cdcf3bdf7525771789 Mon Sep 17 00:00:00 2001 From: Adib234 <30782825+Adib234@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:11:15 -0400 Subject: [PATCH] fix(core): handle non-string model flags in resolution (#26069) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- packages/cli/src/config/config.test.ts | 94 +++++++++++++++++++++++++ packages/cli/src/config/config.ts | 9 ++- packages/core/src/config/models.test.ts | 16 +++++ packages/core/src/config/models.ts | 13 +++- 4 files changed, 128 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index f7d3bbbcd3..0ee7a42ec9 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -804,6 +804,100 @@ describe('loadCliConfig', () => { vi.restoreAllMocks(); }); + describe('Model resolution', () => { + it('should handle multiple --model flags by taking the last one', async () => { + const argv = { + query: undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + model: ['gemini-1.5-pro', 'gemini-2.0-flash'] as any, + sandbox: undefined, + debug: false, + prompt: undefined, + promptInteractive: undefined, + yolo: undefined, + approvalMode: undefined, + policy: undefined, + adminPolicy: undefined, + allowedMcpServerNames: undefined, + allowedTools: undefined, + extensions: undefined, + listExtensions: false, + listSessions: false, + deleteSession: undefined, + screenReader: undefined, + isCommand: false, + rawOutput: false, + acceptRawOutputRisk: false, + startupMessages: [], + resume: undefined, + includeDirectories: [], + useWriteTodos: false, + outputFormat: undefined, + fakeResponses: undefined, + recordResponses: undefined, + skipTrust: false, + }; + + const settings = createTestMergedSettings(); + const config = await loadCliConfig( + settings, + 'test-session', + argv as unknown as CliArgs, + { + cwd: process.cwd(), + }, + ); + + expect(config.getModel()).toBe('gemini-2.0-flash'); + }); + + it('should handle non-string model flags by coercing to string', async () => { + const argv = { + query: undefined, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + model: true as any, + sandbox: undefined, + debug: false, + prompt: undefined, + promptInteractive: undefined, + yolo: undefined, + approvalMode: undefined, + policy: undefined, + adminPolicy: undefined, + allowedMcpServerNames: undefined, + allowedTools: undefined, + extensions: undefined, + listExtensions: false, + listSessions: false, + deleteSession: undefined, + screenReader: undefined, + isCommand: false, + rawOutput: false, + acceptRawOutputRisk: false, + startupMessages: [], + resume: undefined, + includeDirectories: [], + useWriteTodos: false, + outputFormat: undefined, + fakeResponses: undefined, + recordResponses: undefined, + skipTrust: false, + }; + + const settings = createTestMergedSettings(); + const config = await loadCliConfig( + settings, + 'test-session', + argv as unknown as CliArgs, + { + cwd: process.cwd(), + }, + ); + + expect(config.getModel()).toBe('true'); + }); + }); + describe('Proxy configuration', () => { const originalProxyEnv: { [key: string]: string | undefined } = {}; const proxyEnvVars = [ diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 6147ba0b40..bd1f616c8c 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -841,9 +841,16 @@ export async function loadCliConfig( ); const defaultModel = PREVIEW_GEMINI_MODEL_AUTO; - const specifiedModel = + const rawModel = argv.model || process.env['GEMINI_MODEL'] || settings.model?.name; + // Ensure specifiedModel is a string (e.g. if yargs parsed multiple --model as an array) + const specifiedModel = Array.isArray(rawModel) + ? String(rawModel.at(-1) ?? '').trim() || '' + : rawModel === undefined + ? undefined + : String(rawModel ?? '').trim() || ''; + const resolvedModel = specifiedModel === GEMINI_MODEL_ALIAS_AUTO ? defaultModel diff --git a/packages/core/src/config/models.test.ts b/packages/core/src/config/models.test.ts index 155b7f509b..51846262dc 100644 --- a/packages/core/src/config/models.test.ts +++ b/packages/core/src/config/models.test.ts @@ -273,6 +273,13 @@ describe('isCustomModel', () => { expect(isCustomModel(GEMINI_MODEL_ALIAS_AUTO)).toBe(false); expect(isCustomModel(GEMINI_MODEL_ALIAS_PRO)).toBe(false); }); + + it('should not throw if the model is an array (e.g. from yargs)', () => { + // @ts-expect-error - testing invalid runtime input + expect(() => isCustomModel(['gemini-2.0-flash', 'gpt-4'])).not.toThrow(); + // @ts-expect-error - testing invalid runtime input + expect(isCustomModel(['gemini-2.0-flash', 'gpt-4'])).toBe(true); // last one is custom + }); }); describe('supportsModernFeatures', () => { @@ -431,6 +438,15 @@ describe('resolveModel', () => { const model = resolveModel(customModel); expect(model).toBe(customModel); }); + + it('should handle non-string inputs gracefully', () => { + // @ts-expect-error - testing invalid runtime input + expect(resolveModel(['a', 'b'])).toBe('b'); + // @ts-expect-error - testing invalid runtime input + expect(resolveModel(true)).toBe('true'); + // @ts-expect-error - testing invalid runtime input + expect(resolveModel(null)).toBe(''); + }); }); describe('hasAccessToPreview logic', () => { diff --git a/packages/core/src/config/models.ts b/packages/core/src/config/models.ts index 6dd32ab920..6e936182cd 100644 --- a/packages/core/src/config/models.ts +++ b/packages/core/src/config/models.ts @@ -109,8 +109,15 @@ export function resolveModel( hasAccessToPreview: boolean = true, config?: ModelCapabilityContext, ): string { + // Defensive check against non-string inputs at runtime + const normalizedModel = Array.isArray(requestedModel) + ? String(requestedModel.at(-1) ?? '').trim() || '' + : typeof requestedModel !== 'string' + ? String(requestedModel ?? '').trim() || '' + : requestedModel.trim() || ''; + if (config?.getExperimentalDynamicModelConfiguration?.() === true) { - const resolved = config.modelConfigService.resolveModelId(requestedModel, { + const resolved = config.modelConfigService.resolveModelId(normalizedModel, { useGemini3_1, useGemini3_1FlashLite, useCustomTools: useCustomToolModel, @@ -132,7 +139,7 @@ export function resolveModel( } let resolved: string; - switch (requestedModel) { + switch (normalizedModel) { case PREVIEW_GEMINI_MODEL: case PREVIEW_GEMINI_MODEL_AUTO: case GEMINI_MODEL_ALIAS_AUTO: @@ -161,7 +168,7 @@ export function resolveModel( break; } default: { - resolved = requestedModel; + resolved = normalizedModel; break; } }