Skip to content

Commit e7a2d33

Browse files
JaisalJaingemini-code-assist[bot]
authored andcommitted
fix(cli): validate --model argument at startup (#21393)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 4f2f0a7 commit e7a2d33

File tree

4 files changed

+125
-4
lines changed

4 files changed

+125
-4
lines changed

packages/cli/src/config/config.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,7 +1773,7 @@ describe('loadCliConfig model selection', () => {
17731773
});
17741774

17751775
it('always prefers model from argv', async () => {
1776-
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash-preview'];
1776+
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash'];
17771777
const argv = await parseArguments(createTestMergedSettings());
17781778
const config = await loadCliConfig(
17791779
createTestMergedSettings({
@@ -1785,11 +1785,11 @@ describe('loadCliConfig model selection', () => {
17851785
argv,
17861786
);
17871787

1788-
expect(config.getModel()).toBe('gemini-2.5-flash-preview');
1788+
expect(config.getModel()).toBe('gemini-2.5-flash');
17891789
});
17901790

17911791
it('selects the model from argv if provided', async () => {
1792-
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash-preview'];
1792+
process.argv = ['node', 'script.js', '--model', 'gemini-2.5-flash'];
17931793
const argv = await parseArguments(createTestMergedSettings());
17941794
const config = await loadCliConfig(
17951795
createTestMergedSettings({
@@ -1799,7 +1799,7 @@ describe('loadCliConfig model selection', () => {
17991799
argv,
18001800
);
18011801

1802-
expect(config.getModel()).toBe('gemini-2.5-flash-preview');
1802+
expect(config.getModel()).toBe('gemini-2.5-flash');
18031803
});
18041804

18051805
it('selects the default auto model if provided via auto alias', async () => {

packages/cli/src/config/config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
type HierarchicalMemory,
3232
coreEvents,
3333
GEMINI_MODEL_ALIAS_AUTO,
34+
isValidModelOrAlias,
35+
getValidModelsAndAliases,
3436
getAdminErrorMessage,
3537
isHeadlessMode,
3638
Config,
@@ -671,6 +673,18 @@ export async function loadCliConfig(
671673
const specifiedModel =
672674
argv.model || process.env['GEMINI_MODEL'] || settings.model?.name;
673675

676+
// Validate the model if one was explicitly specified
677+
if (specifiedModel && specifiedModel !== GEMINI_MODEL_ALIAS_AUTO) {
678+
if (!isValidModelOrAlias(specifiedModel)) {
679+
const validModels = getValidModelsAndAliases();
680+
681+
throw new FatalConfigError(
682+
`Invalid model: "${specifiedModel}"\n\n` +
683+
`Valid models and aliases:\n${validModels.map((m) => ` - ${m}`).join('\n')}\n\n` +
684+
`Use /model to switch models interactively.`,
685+
);
686+
}
687+
}
674688
const resolvedModel =
675689
specifiedModel === GEMINI_MODEL_ALIAS_AUTO
676690
? defaultModel

packages/core/src/config/models.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
GEMINI_MODEL_ALIAS_PRO,
2323
GEMINI_MODEL_ALIAS_FLASH,
2424
GEMINI_MODEL_ALIAS_AUTO,
25+
GEMINI_MODEL_ALIAS_FLASH_LITE,
2526
PREVIEW_GEMINI_FLASH_MODEL,
2627
PREVIEW_GEMINI_MODEL_AUTO,
2728
DEFAULT_GEMINI_MODEL_AUTO,
@@ -30,6 +31,10 @@ import {
3031
PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL,
3132
isPreviewModel,
3233
isProModel,
34+
isValidModelOrAlias,
35+
getValidModelsAndAliases,
36+
VALID_GEMINI_MODELS,
37+
VALID_ALIASES,
3338
} from './models.js';
3439

3540
describe('isPreviewModel', () => {
@@ -389,3 +394,62 @@ describe('isActiveModel', () => {
389394
).toBe(false);
390395
});
391396
});
397+
398+
describe('isValidModelOrAlias', () => {
399+
it('should return true for valid model names', () => {
400+
expect(isValidModelOrAlias(DEFAULT_GEMINI_MODEL)).toBe(true);
401+
expect(isValidModelOrAlias(PREVIEW_GEMINI_MODEL)).toBe(true);
402+
expect(isValidModelOrAlias(DEFAULT_GEMINI_FLASH_MODEL)).toBe(true);
403+
expect(isValidModelOrAlias(DEFAULT_GEMINI_FLASH_LITE_MODEL)).toBe(true);
404+
expect(isValidModelOrAlias(PREVIEW_GEMINI_FLASH_MODEL)).toBe(true);
405+
expect(isValidModelOrAlias(PREVIEW_GEMINI_3_1_MODEL)).toBe(true);
406+
expect(isValidModelOrAlias(PREVIEW_GEMINI_3_1_CUSTOM_TOOLS_MODEL)).toBe(
407+
true,
408+
);
409+
});
410+
411+
it('should return true for valid aliases', () => {
412+
expect(isValidModelOrAlias(GEMINI_MODEL_ALIAS_AUTO)).toBe(true);
413+
expect(isValidModelOrAlias(GEMINI_MODEL_ALIAS_PRO)).toBe(true);
414+
expect(isValidModelOrAlias(GEMINI_MODEL_ALIAS_FLASH)).toBe(true);
415+
expect(isValidModelOrAlias(GEMINI_MODEL_ALIAS_FLASH_LITE)).toBe(true);
416+
expect(isValidModelOrAlias(PREVIEW_GEMINI_MODEL_AUTO)).toBe(true);
417+
expect(isValidModelOrAlias(DEFAULT_GEMINI_MODEL_AUTO)).toBe(true);
418+
});
419+
420+
it('should return true for custom (non-gemini) models', () => {
421+
expect(isValidModelOrAlias('gpt-4')).toBe(true);
422+
expect(isValidModelOrAlias('claude-3')).toBe(true);
423+
expect(isValidModelOrAlias('my-custom-model')).toBe(true);
424+
});
425+
426+
it('should return false for invalid gemini model names', () => {
427+
expect(isValidModelOrAlias('gemini-4-pro')).toBe(false);
428+
expect(isValidModelOrAlias('gemini-99-flash')).toBe(false);
429+
expect(isValidModelOrAlias('gemini-invalid')).toBe(false);
430+
});
431+
});
432+
433+
describe('getValidModelsAndAliases', () => {
434+
it('should return a sorted array', () => {
435+
const result = getValidModelsAndAliases();
436+
const sorted = [...result].sort();
437+
expect(result).toEqual(sorted);
438+
});
439+
440+
it('should include all valid models and aliases', () => {
441+
const result = getValidModelsAndAliases();
442+
for (const model of VALID_GEMINI_MODELS) {
443+
expect(result).toContain(model);
444+
}
445+
for (const alias of VALID_ALIASES) {
446+
expect(result).toContain(alias);
447+
}
448+
});
449+
450+
it('should not contain duplicates', () => {
451+
const result = getValidModelsAndAliases();
452+
const unique = [...new Set(result)];
453+
expect(result).toEqual(unique);
454+
});
455+
});

packages/core/src/config/models.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ export const GEMINI_MODEL_ALIAS_PRO = 'pro';
3232
export const GEMINI_MODEL_ALIAS_FLASH = 'flash';
3333
export const GEMINI_MODEL_ALIAS_FLASH_LITE = 'flash-lite';
3434

35+
export const VALID_ALIASES = new Set([
36+
GEMINI_MODEL_ALIAS_AUTO,
37+
GEMINI_MODEL_ALIAS_PRO,
38+
GEMINI_MODEL_ALIAS_FLASH,
39+
GEMINI_MODEL_ALIAS_FLASH_LITE,
40+
PREVIEW_GEMINI_MODEL_AUTO,
41+
DEFAULT_GEMINI_MODEL_AUTO,
42+
]);
43+
3544
export const DEFAULT_GEMINI_EMBEDDING_MODEL = 'gemini-embedding-001';
3645

3746
// Cap the thinking at 8192 to prevent run-away thinking loops.
@@ -283,3 +292,37 @@ export function isActiveModel(
283292
);
284293
}
285294
}
295+
296+
/**
297+
* Checks if the model name is valid (either a valid model or a valid alias).
298+
*
299+
* @param model The model name to check.
300+
* @returns True if the model is valid.
301+
*/
302+
export function isValidModelOrAlias(model: string): boolean {
303+
// Check if it's a valid alias
304+
if (VALID_ALIASES.has(model)) {
305+
return true;
306+
}
307+
308+
// Check if it's a valid model name
309+
if (VALID_GEMINI_MODELS.has(model)) {
310+
return true;
311+
}
312+
313+
// Allow custom models (non-gemini models)
314+
if (!model.startsWith('gemini-')) {
315+
return true;
316+
}
317+
318+
return false;
319+
}
320+
321+
/**
322+
* Gets a list of all valid model names and aliases for error messages.
323+
*
324+
* @returns Array of valid model names and aliases.
325+
*/
326+
export function getValidModelsAndAliases(): string[] {
327+
return [...new Set([...VALID_ALIASES, ...VALID_GEMINI_MODELS])].sort();
328+
}

0 commit comments

Comments
 (0)