mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-20 11:00:40 -07:00
feat: automatic /model persistence across Gemini CLI sessions (#13199)
Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
This commit is contained in:
@@ -25,13 +25,11 @@ import { ExtensionManager } from './extension-manager.js';
|
||||
import { RESUME_LATEST } from '../utils/sessionUtils.js';
|
||||
|
||||
vi.mock('./trustedFolders.js', () => ({
|
||||
isWorkspaceTrusted: vi
|
||||
.fn()
|
||||
.mockReturnValue({ isTrusted: true, source: 'file' }), // Default to trusted
|
||||
isWorkspaceTrusted: vi.fn(() => ({ isTrusted: true, source: 'file' })), // Default to trusted
|
||||
}));
|
||||
|
||||
vi.mock('./sandboxConfig.js', () => ({
|
||||
loadSandboxConfig: vi.fn().mockResolvedValue(undefined),
|
||||
loadSandboxConfig: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock('fs', async (importOriginal) => {
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
PREVIEW_GEMINI_MODEL_AUTO,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Settings } from './settings.js';
|
||||
import { saveModelChange, loadSettings } from './settings.js';
|
||||
|
||||
import { loadSandboxConfig } from './sandboxConfig.js';
|
||||
import { resolvePath } from '../utils/resolvePath.js';
|
||||
@@ -387,6 +388,8 @@ export async function loadCliConfig(
|
||||
): Promise<Config> {
|
||||
const debugMode = isDebugMode(argv);
|
||||
|
||||
const loadedSettings = loadSettings(cwd);
|
||||
|
||||
if (argv.sandbox) {
|
||||
process.env['GEMINI_SANDBOX'] = 'true';
|
||||
}
|
||||
@@ -693,6 +696,7 @@ export async function loadCliConfig(
|
||||
// TODO: loading of hooks based on workspace trust
|
||||
enableHooks: settings.tools?.enableHooks ?? false,
|
||||
hooks: settings.hooks || {},
|
||||
onModelChange: (model: string) => saveModelChange(loadedSettings, model),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -874,3 +874,18 @@ export function saveSettings(settingsFile: SettingsFile): void {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function saveModelChange(
|
||||
loadedSettings: LoadedSettings,
|
||||
model: string,
|
||||
): void {
|
||||
try {
|
||||
loadedSettings.setValue(SettingScope.User, 'model.name', model);
|
||||
} catch (error) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
'There was an error saving your preferred model.',
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@ describe('<ModelDialog />', () => {
|
||||
it('renders the initial "main" view correctly', () => {
|
||||
const { lastFrame } = renderComponent();
|
||||
expect(lastFrame()).toContain('Select Model');
|
||||
expect(lastFrame()).toContain(
|
||||
'Applies to this session and future Gemini CLI sessions.',
|
||||
);
|
||||
expect(lastFrame()).toContain('Auto');
|
||||
expect(lastFrame()).toContain('Manual');
|
||||
});
|
||||
|
||||
@@ -204,7 +204,6 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
)}
|
||||
{subheader && <Text>{subheader}</Text>}
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<DescriptiveRadioButtonSelect
|
||||
items={options}
|
||||
@@ -215,7 +214,12 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
</Box>
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color={theme.text.secondary}>
|
||||
{'To use a specific Gemini model on startup, use the --model flag.'}
|
||||
Applies to this session and future Gemini CLI sessions.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color={theme.text.secondary}>
|
||||
{'> To use a specific Gemini model on startup, use the --model flag.'}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
|
||||
@@ -1629,6 +1629,18 @@ describe('Config getHooks', () => {
|
||||
expect(config.getModel()).toBe(originalModel);
|
||||
expect(config.getActiveModel()).toBe(originalModel);
|
||||
});
|
||||
|
||||
it('should call onModelChange when a new model is set', () => {
|
||||
const onModelChange = vi.fn();
|
||||
const config = new Config({
|
||||
...baseParams,
|
||||
onModelChange,
|
||||
});
|
||||
|
||||
config.setModel(DEFAULT_GEMINI_MODEL);
|
||||
|
||||
expect(onModelChange).toHaveBeenCalledWith(DEFAULT_GEMINI_MODEL);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -337,6 +337,7 @@ export interface ConfigParameters {
|
||||
previewFeatures?: boolean;
|
||||
enableAgents?: boolean;
|
||||
experimentalJitContext?: boolean;
|
||||
onModelChange?: (model: string) => void;
|
||||
}
|
||||
|
||||
export class Config {
|
||||
@@ -457,6 +458,7 @@ export class Config {
|
||||
private experiments: Experiments | undefined;
|
||||
private experimentsPromise: Promise<void> | undefined;
|
||||
private hookSystem?: HookSystem;
|
||||
private readonly onModelChange: ((model: string) => void) | undefined;
|
||||
|
||||
private readonly enableAgents: boolean;
|
||||
|
||||
@@ -623,6 +625,7 @@ export class Config {
|
||||
this.disableYoloMode = params.disableYoloMode ?? false;
|
||||
this.hooks = params.hooks;
|
||||
this.experiments = params.experiments;
|
||||
this.onModelChange = params.onModelChange;
|
||||
|
||||
if (params.contextFileName) {
|
||||
setGeminiMdFilename(params.contextFileName);
|
||||
@@ -879,6 +882,9 @@ export class Config {
|
||||
// When the user explicitly sets a model, that becomes the active model.
|
||||
this._activeModel = newModel;
|
||||
coreEvents.emitModelChanged(newModel);
|
||||
if (this.onModelChange) {
|
||||
this.onModelChange(newModel);
|
||||
}
|
||||
}
|
||||
this.modelAvailabilityService.reset();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user