Update models menu dialog to support manual model selection and group… (#80)

* Update models menu dialog to support manual model selection and group by model family

* fix tests

* update codebase investigator model setting

* fix test

* fix test

* update generated golden file

* regenerate scheme and doc for settings

* use Preview Auto if previewFeatures is set to true
This commit is contained in:
Sehoon Shon
2025-12-11 09:57:27 -05:00
committed by Tommaso Sciortino
parent c3f6e7132b
commit 17bf02b901
26 changed files with 461 additions and 535 deletions
+80 -184
View File
@@ -7,220 +7,116 @@
import { describe, it, expect } from 'vitest';
import {
getEffectiveModel,
isGemini2Model,
DEFAULT_GEMINI_MODEL,
PREVIEW_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
PREVIEW_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
GEMINI_MODEL_ALIAS_PRO,
GEMINI_MODEL_ALIAS_FLASH,
GEMINI_MODEL_ALIAS_FLASH_LITE,
PREVIEW_GEMINI_MODEL_AUTO,
DEFAULT_GEMINI_MODEL_AUTO,
} from './models.js';
describe('getEffectiveModel', () => {
describe('When NOT in fallback mode', () => {
const isInFallbackMode = false;
const useFallbackModel = false;
it('should return the Pro model when Pro is requested', () => {
it('should return the Preview Pro model when auto-preview is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_MODEL,
false,
PREVIEW_GEMINI_MODEL_AUTO,
useFallbackModel,
);
expect(model).toBe(PREVIEW_GEMINI_MODEL);
});
it('should return the Default Pro model when auto-default is requested', () => {
const model = getEffectiveModel(
DEFAULT_GEMINI_MODEL_AUTO,
useFallbackModel,
);
expect(model).toBe(DEFAULT_GEMINI_MODEL);
});
it('should return the Flash model when Flash is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_MODEL,
false,
it('should return the requested model as-is for explicit specific models', () => {
expect(getEffectiveModel(DEFAULT_GEMINI_MODEL, useFallbackModel)).toBe(
DEFAULT_GEMINI_MODEL,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the Lite model when Lite is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
expect(
getEffectiveModel(DEFAULT_GEMINI_FLASH_MODEL, useFallbackModel),
).toBe(DEFAULT_GEMINI_FLASH_MODEL);
expect(
getEffectiveModel(DEFAULT_GEMINI_FLASH_LITE_MODEL, useFallbackModel),
).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return a custom model name when requested', () => {
const customModel = 'custom-model-v1';
const model = getEffectiveModel(isInFallbackMode, customModel, false);
const model = getEffectiveModel(customModel, useFallbackModel);
expect(model).toBe(customModel);
});
describe('with preview features', () => {
it('should return the preview model when pro alias is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_PRO,
true,
);
expect(model).toBe(PREVIEW_GEMINI_MODEL);
});
it('should return the default pro model when pro alias is requested and preview is off', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_PRO,
false,
);
expect(model).toBe(DEFAULT_GEMINI_MODEL);
});
it('should return the flash model when flash is requested and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_FLASH,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the flash model when lite is requested and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_FLASH_LITE,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return the flash model when the flash model name is explicitly requested and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the lite model when the lite model name is requested and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return the default gemini model when the model is explicitly set and preview is on', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_MODEL);
});
});
});
describe('When IN fallback mode', () => {
const isInFallbackMode = true;
const useFallbackModel = true;
it('should downgrade the Pro model to the Flash model', () => {
it('should return the Preview Flash model when auto-preview is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
PREVIEW_GEMINI_MODEL_AUTO,
useFallbackModel,
);
expect(model).toBe(PREVIEW_GEMINI_FLASH_MODEL);
});
it('should return the Default Flash model when auto-default is requested', () => {
const model = getEffectiveModel(
DEFAULT_GEMINI_MODEL_AUTO,
useFallbackModel,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the requested model as-is for explicit specific models', () => {
expect(getEffectiveModel(DEFAULT_GEMINI_MODEL, useFallbackModel)).toBe(
DEFAULT_GEMINI_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
expect(
getEffectiveModel(DEFAULT_GEMINI_FLASH_MODEL, useFallbackModel),
).toBe(DEFAULT_GEMINI_FLASH_MODEL);
expect(
getEffectiveModel(DEFAULT_GEMINI_FLASH_LITE_MODEL, useFallbackModel),
).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should return the Flash model when Flash is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should HONOR the Lite model when Lite is requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
false,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should HONOR any model with "lite" in its name', () => {
const customLiteModel = 'gemini-2.5-custom-lite-vNext';
const model = getEffectiveModel(isInFallbackMode, customLiteModel, false);
expect(model).toBe(customLiteModel);
});
it('should downgrade any other custom model to the Flash model', () => {
const customModel = 'custom-model-v1-unlisted';
const model = getEffectiveModel(isInFallbackMode, customModel, false);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
describe('with preview features', () => {
it('should downgrade the Pro alias to the Flash model', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_PRO,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the Flash alias when requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_FLASH,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the Lite alias when requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
GEMINI_MODEL_ALIAS_FLASH_LITE,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should downgrade the default Gemini model to the Flash model', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the default Flash model when requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return the default Lite model when requested', () => {
const model = getEffectiveModel(
isInFallbackMode,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
true,
);
expect(model).toBe(DEFAULT_GEMINI_FLASH_LITE_MODEL);
});
it('should downgrade any other custom model to the Flash model', () => {
const customModel = 'custom-model-v1-unlisted';
const model = getEffectiveModel(isInFallbackMode, customModel, true);
expect(model).toBe(DEFAULT_GEMINI_FLASH_MODEL);
});
it('should return custom model name as-is', () => {
const customModel = 'custom-model-v1';
const model = getEffectiveModel(customModel, useFallbackModel);
expect(model).toBe(customModel);
});
});
});
describe('isGemini2Model', () => {
it('should return true for gemini-2.5-pro', () => {
expect(isGemini2Model('gemini-2.5-pro')).toBe(true);
});
it('should return true for gemini-2.5-flash', () => {
expect(isGemini2Model('gemini-2.5-flash')).toBe(true);
});
it('should return true for gemini-2.0-flash', () => {
expect(isGemini2Model('gemini-2.0-flash')).toBe(true);
});
it('should return false for gemini-1.5-pro', () => {
expect(isGemini2Model('gemini-1.5-pro')).toBe(false);
});
it('should return false for gemini-3-pro', () => {
expect(isGemini2Model('gemini-3-pro')).toBe(false);
});
it('should return false for arbitrary strings', () => {
expect(isGemini2Model('gpt-4')).toBe(false);
});
});
+39 -54
View File
@@ -10,83 +10,68 @@ export const DEFAULT_GEMINI_MODEL = 'gemini-2.5-pro';
export const DEFAULT_GEMINI_FLASH_MODEL = 'gemini-2.5-flash';
export const DEFAULT_GEMINI_FLASH_LITE_MODEL = 'gemini-2.5-flash-lite';
export const DEFAULT_GEMINI_MODEL_AUTO = 'auto';
export const VALID_GEMINI_MODELS = new Set([
PREVIEW_GEMINI_MODEL,
DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_FLASH_MODEL,
DEFAULT_GEMINI_FLASH_LITE_MODEL,
]);
// Model aliases for user convenience.
export const GEMINI_MODEL_ALIAS_PRO = 'pro';
export const GEMINI_MODEL_ALIAS_FLASH = 'flash';
export const GEMINI_MODEL_ALIAS_FLASH_LITE = 'flash-lite';
export const PREVIEW_GEMINI_MODEL_AUTO = 'auto-gemini-3';
export const DEFAULT_GEMINI_MODEL_AUTO = 'auto-gemini-2.5';
export const DEFAULT_GEMINI_EMBEDDING_MODEL = 'gemini-embedding-001';
// Cap the thinking at 8192 to prevent run-away thinking loops.
export const DEFAULT_THINKING_MODE = 8192;
/**
* Resolves the requested model alias (e.g., 'auto', 'pro', 'flash', 'flash-lite')
* to a concrete model name, considering preview features.
*
* @param requestedModel The model alias or concrete model name requested by the user.
* @param previewFeaturesEnabled A boolean indicating if preview features are enabled.
* @returns The resolved concrete model name.
*/
export function resolveModel(
requestedModel: string,
previewFeaturesEnabled: boolean | undefined,
): string {
switch (requestedModel) {
case DEFAULT_GEMINI_MODEL_AUTO:
case GEMINI_MODEL_ALIAS_PRO: {
return previewFeaturesEnabled
? PREVIEW_GEMINI_MODEL
: DEFAULT_GEMINI_MODEL;
}
case GEMINI_MODEL_ALIAS_FLASH: {
return DEFAULT_GEMINI_FLASH_MODEL;
}
case GEMINI_MODEL_ALIAS_FLASH_LITE: {
return DEFAULT_GEMINI_FLASH_LITE_MODEL;
}
default: {
return requestedModel;
}
}
}
/**
* Determines the effective model to use, applying fallback logic if necessary.
*
* When fallback mode is active, this function enforces the use of the standard
* fallback model. However, it makes an exception for "lite" models (any model
* with "lite" in its name), allowing them to be used to preserve cost savings.
* This ensures that "pro" models are always downgraded, while "lite" model
* requests are honored.
* fallback model.
*
* @param isInFallbackMode Whether the application is in fallback mode.
* @param requestedModel The model that was originally requested.
* @param previewFeaturesEnabled A boolean indicating if preview features are enabled.
* @param isInFallbackMode Whether the application is in fallback mode.
* @returns The effective model name.
*/
export function getEffectiveModel(
isInFallbackMode: boolean,
requestedModel: string,
previewFeaturesEnabled: boolean | undefined,
useFallbackModel: boolean,
): string {
const resolvedModel = resolveModel(requestedModel, previewFeaturesEnabled);
// If we are not in fallback mode, simply use the resolved model.
if (!isInFallbackMode) {
return resolvedModel;
if (!useFallbackModel) {
switch (requestedModel) {
case PREVIEW_GEMINI_MODEL_AUTO:
return PREVIEW_GEMINI_MODEL;
case DEFAULT_GEMINI_MODEL_AUTO:
return DEFAULT_GEMINI_MODEL;
default:
return requestedModel;
}
}
// If a "lite" model is requested, honor it. This allows for variations of
// lite models without needing to list them all as constants.
if (resolvedModel.includes('lite')) {
return resolvedModel;
// Fallback model for corresponding model family. We are doing fallback only
// for Auto modes
switch (requestedModel) {
case PREVIEW_GEMINI_MODEL_AUTO:
return PREVIEW_GEMINI_FLASH_MODEL;
case DEFAULT_GEMINI_MODEL_AUTO:
return DEFAULT_GEMINI_FLASH_MODEL;
default:
return requestedModel;
}
}
// Default fallback for Gemini CLI.
return DEFAULT_GEMINI_FLASH_MODEL;
export function getDisplayString(model: string) {
switch (model) {
case PREVIEW_GEMINI_MODEL_AUTO:
return 'Auto (Gemini 3)';
case DEFAULT_GEMINI_MODEL_AUTO:
return 'Auto (Gemini 2.5)';
default:
return model;
}
}
/**