Add a context percentage threshold setting for auto compression (#5721)

This commit is contained in:
Jacob MacDonald
2025-08-07 07:34:40 -07:00
committed by GitHub
parent 36750ca49b
commit 6ae75c9f32
8 changed files with 219 additions and 7 deletions

View File

@@ -1123,3 +1123,42 @@ describe('loadCliConfig with includeDirectories', () => {
);
});
});
describe('loadCliConfig chatCompression', () => {
const originalArgv = process.argv;
const originalEnv = { ...process.env };
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
process.env.GEMINI_API_KEY = 'test-api-key';
});
afterEach(() => {
process.argv = originalArgv;
process.env = originalEnv;
vi.restoreAllMocks();
});
it('should pass chatCompression settings to the core config', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments();
const settings: Settings = {
chatCompression: {
contextPercentageThreshold: 0.5,
},
};
const config = await loadCliConfig(settings, [], 'test-session', argv);
expect(config.getChatCompression()).toEqual({
contextPercentageThreshold: 0.5,
});
});
it('should have undefined chatCompression if not in settings', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments();
const settings: Settings = {};
const config = await loadCliConfig(settings, [], 'test-session', argv);
expect(config.getChatCompression()).toBeUndefined();
});
});

View File

@@ -482,6 +482,7 @@ export async function loadCliConfig(
summarizeToolOutput: settings.summarizeToolOutput,
ideMode,
ideModeFeature,
chatCompression: settings.chatCompression,
folderTrustFeature,
});
}

View File

@@ -113,6 +113,7 @@ describe('Settings Loading and Merging', () => {
customThemes: {},
mcpServers: {},
includeDirectories: [],
chatCompression: {},
});
expect(settings.errors.length).toBe(0);
});
@@ -147,6 +148,7 @@ describe('Settings Loading and Merging', () => {
customThemes: {},
mcpServers: {},
includeDirectories: [],
chatCompression: {},
});
});
@@ -181,6 +183,7 @@ describe('Settings Loading and Merging', () => {
customThemes: {},
mcpServers: {},
includeDirectories: [],
chatCompression: {},
});
});
@@ -213,6 +216,7 @@ describe('Settings Loading and Merging', () => {
customThemes: {},
mcpServers: {},
includeDirectories: [],
chatCompression: {},
});
});
@@ -251,6 +255,7 @@ describe('Settings Loading and Merging', () => {
customThemes: {},
mcpServers: {},
includeDirectories: [],
chatCompression: {},
});
});
@@ -301,6 +306,7 @@ describe('Settings Loading and Merging', () => {
customThemes: {},
mcpServers: {},
includeDirectories: [],
chatCompression: {},
});
});
@@ -622,6 +628,116 @@ describe('Settings Loading and Merging', () => {
expect(settings.merged.mcpServers).toEqual({});
});
it('should merge chatCompression settings, with workspace taking precedence', () => {
(mockFsExistsSync as Mock).mockReturnValue(true);
const userSettingsContent = {
chatCompression: { contextPercentageThreshold: 0.5 },
};
const workspaceSettingsContent = {
chatCompression: { contextPercentageThreshold: 0.8 },
};
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === USER_SETTINGS_PATH)
return JSON.stringify(userSettingsContent);
if (p === MOCK_WORKSPACE_SETTINGS_PATH)
return JSON.stringify(workspaceSettingsContent);
return '{}';
},
);
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(settings.user.settings.chatCompression).toEqual({
contextPercentageThreshold: 0.5,
});
expect(settings.workspace.settings.chatCompression).toEqual({
contextPercentageThreshold: 0.8,
});
expect(settings.merged.chatCompression).toEqual({
contextPercentageThreshold: 0.8,
});
});
it('should handle chatCompression when only in user settings', () => {
(mockFsExistsSync as Mock).mockImplementation(
(p: fs.PathLike) => p === USER_SETTINGS_PATH,
);
const userSettingsContent = {
chatCompression: { contextPercentageThreshold: 0.5 },
};
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === USER_SETTINGS_PATH)
return JSON.stringify(userSettingsContent);
return '{}';
},
);
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(settings.merged.chatCompression).toEqual({
contextPercentageThreshold: 0.5,
});
});
it('should have chatCompression as an empty object if not in any settings file', () => {
(mockFsExistsSync as Mock).mockReturnValue(false); // No settings files exist
(fs.readFileSync as Mock).mockReturnValue('{}');
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(settings.merged.chatCompression).toEqual({});
});
it('should ignore chatCompression if contextPercentageThreshold is invalid', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
(mockFsExistsSync as Mock).mockImplementation(
(p: fs.PathLike) => p === USER_SETTINGS_PATH,
);
const userSettingsContent = {
chatCompression: { contextPercentageThreshold: 1.5 },
};
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === USER_SETTINGS_PATH)
return JSON.stringify(userSettingsContent);
return '{}';
},
);
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(settings.merged.chatCompression).toBeUndefined();
expect(warnSpy).toHaveBeenCalledWith(
'Invalid value for chatCompression.contextPercentageThreshold: "1.5". Please use a value between 0 and 1. Using default compression settings.',
);
warnSpy.mockRestore();
});
it('should deep merge chatCompression settings', () => {
(mockFsExistsSync as Mock).mockReturnValue(true);
const userSettingsContent = {
chatCompression: { contextPercentageThreshold: 0.5 },
};
const workspaceSettingsContent = {
chatCompression: {},
};
(fs.readFileSync as Mock).mockImplementation(
(p: fs.PathOrFileDescriptor) => {
if (p === USER_SETTINGS_PATH)
return JSON.stringify(userSettingsContent);
if (p === MOCK_WORKSPACE_SETTINGS_PATH)
return JSON.stringify(workspaceSettingsContent);
return '{}';
},
);
const settings = loadSettings(MOCK_WORKSPACE_DIR);
expect(settings.merged.chatCompression).toEqual({
contextPercentageThreshold: 0.5,
});
});
it('should merge includeDirectories from all scopes', () => {
(mockFsExistsSync as Mock).mockReturnValue(true);
const systemSettingsContent = {
@@ -695,6 +811,7 @@ describe('Settings Loading and Merging', () => {
customThemes: {},
mcpServers: {},
includeDirectories: [],
chatCompression: {},
});
// Check that error objects are populated in settings.errors
@@ -1132,6 +1249,7 @@ describe('Settings Loading and Merging', () => {
customThemes: {},
mcpServers: {},
includeDirectories: [],
chatCompression: {},
});
});
});

View File

@@ -13,6 +13,7 @@ import {
GEMINI_CONFIG_DIR as GEMINI_DIR,
getErrorMessage,
BugCommandSettings,
ChatCompressionSettings,
TelemetrySettings,
AuthType,
} from '@google/gemini-cli-core';
@@ -134,6 +135,8 @@ export interface Settings {
includeDirectories?: string[];
loadMemoryFromIncludeDirectories?: boolean;
chatCompression?: ChatCompressionSettings;
}
export interface SettingsError {
@@ -194,6 +197,11 @@ export class LoadedSettings {
...(user.includeDirectories || []),
...(workspace.includeDirectories || []),
],
chatCompression: {
...(system.chatCompression || {}),
...(user.chatCompression || {}),
...(workspace.chatCompression || {}),
},
};
}
@@ -482,6 +490,19 @@ export function loadSettings(workspaceDir: string): LoadedSettings {
settingsErrors,
);
// Validate chatCompression settings
const chatCompression = loadedSettings.merged.chatCompression;
const threshold = chatCompression?.contextPercentageThreshold;
if (
threshold != null &&
(typeof threshold !== 'number' || threshold < 0 || threshold > 1)
) {
console.warn(
`Invalid value for chatCompression.contextPercentageThreshold: "${threshold}". Please use a value between 0 and 1. Using default compression settings.`,
);
delete loadedSettings.merged.chatCompression;
}
// Load environment with merged settings
loadEnvironment(loadedSettings.merged);