diff --git a/docs/get-started/configuration-v1.md b/docs/get-started/configuration-v1.md index 4c00b00f4d..866028a975 100644 --- a/docs/get-started/configuration-v1.md +++ b/docs/get-started/configuration-v1.md @@ -473,21 +473,6 @@ a few things you can try in order of recommendation: "loadMemoryFromIncludeDirectories": true ``` -- **`chatCompression`** (object): - - **Description:** Controls the settings for chat history compression, both - automatic and when manually invoked through the /compress command. - - **Properties:** - - **`contextPercentageThreshold`** (number): A value between 0 and 1 that - specifies the token threshold for compression as a percentage of the - model's total token limit. For example, a value of `0.6` will trigger - compression when the chat history exceeds 60% of the token limit. - - **Example:** - ```json - "chatCompression": { - "contextPercentageThreshold": 0.6 - } - ``` - - **`showLineNumbers`** (boolean): - **Description:** Controls whether line numbers are displayed in code blocks in the CLI output. diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index 1ee9eb7468..ec944d2c1e 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -245,13 +245,13 @@ their corresponding top-level category object in your `settings.json` file. example `{"run_shell_command": {"tokenBudget": 2000}}` - **Default:** `undefined` -- **`model.chatCompression.contextPercentageThreshold`** (number): +- **`model.compressionThreshold`** (number): - **Description:** Sets the threshold for chat history compression as a - percentage of the model's total token limit. This is a value between 0 and 1 + fraction of the model's total token limit. This is a value between 0 and 1 that applies to both automatic compression and the manual `/compress` command. For example, a value of `0.6` will trigger compression when the chat history exceeds 60% of the token limit. - - **Default:** `0.7` + - **Default:** `0.2` - **`model.skipNextSpeakerCheck`** (boolean): - **Description:** Skip the next speaker check. diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index c6e8a71458..99dd616703 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -1545,7 +1545,7 @@ describe('loadCliConfig with includeDirectories', () => { }); }); -describe('loadCliConfig chatCompression', () => { +describe('loadCliConfig compressionThreshold', () => { beforeEach(() => { vi.resetAllMocks(); vi.mocked(os.homedir).mockReturnValue('/mock/home/user'); @@ -1558,28 +1558,24 @@ describe('loadCliConfig chatCompression', () => { vi.restoreAllMocks(); }); - it('should pass chatCompression settings to the core config', async () => { + it('should pass settings to the core config', async () => { process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); const settings: Settings = { model: { - chatCompression: { - contextPercentageThreshold: 0.5, - }, + compressionThreshold: 0.5, }, }; const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.getChatCompression()).toEqual({ - contextPercentageThreshold: 0.5, - }); + expect(config.getCompressionThreshold()).toBe(0.5); }); - it('should have undefined chatCompression if not in settings', async () => { + it('should have undefined compressionThreshold if not in settings', async () => { process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); const settings: Settings = {}; const config = await loadCliConfig(settings, 'test-session', argv); - expect(config.getChatCompression()).toBeUndefined(); + expect(config.getCompressionThreshold()).toBeUndefined(); }); }); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index a5a2a1e58d..d1888ea190 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -685,7 +685,7 @@ export async function loadCliConfig( noBrowser: !!process.env['NO_BROWSER'], summarizeToolOutput: settings.model?.summarizeToolOutput, ideMode, - chatCompression: settings.model?.chatCompression, + compressionThreshold: settings.model?.compressionThreshold, folderTrust, interactive, trustedFolder, diff --git a/packages/cli/src/config/settings.test.ts b/packages/cli/src/config/settings.test.ts index 6ca94c14c3..ed4b219041 100644 --- a/packages/cli/src/config/settings.test.ts +++ b/packages/cli/src/config/settings.test.ts @@ -1104,15 +1104,15 @@ describe('Settings Loading and Merging', () => { }); }); - it('should merge chatCompression settings, with workspace taking precedence', () => { + it('should merge compressionThreshold settings, with workspace taking precedence', () => { (mockFsExistsSync as Mock).mockReturnValue(true); const userSettingsContent = { general: {}, - model: { chatCompression: { contextPercentageThreshold: 0.5 } }, + model: { compressionThreshold: 0.5 }, }; const workspaceSettingsContent = { general: {}, - model: { chatCompression: { contextPercentageThreshold: 0.8 } }, + model: { compressionThreshold: 0.8 }, }; (fs.readFileSync as Mock).mockImplementation( @@ -1127,15 +1127,11 @@ describe('Settings Loading and Merging', () => { const settings = loadSettings(MOCK_WORKSPACE_DIR); - expect(settings.user.settings.model?.chatCompression).toEqual({ - contextPercentageThreshold: 0.5, - }); - expect(settings.workspace.settings.model?.chatCompression).toEqual({ - contextPercentageThreshold: 0.8, - }); - expect(settings.merged.model?.chatCompression).toEqual({ - contextPercentageThreshold: 0.8, - }); + expect(settings.user.settings.model?.compressionThreshold).toEqual(0.5); + expect(settings.workspace.settings.model?.compressionThreshold).toEqual( + 0.8, + ); + expect(settings.merged.model?.compressionThreshold).toEqual(0.8); }); it('should merge output format settings, with workspace taking precedence', () => { @@ -1162,13 +1158,13 @@ describe('Settings Loading and Merging', () => { expect(settings.merged.output?.format).toBe('json'); }); - it('should handle chatCompression when only in user settings', () => { + it('should handle compressionThreshold when only in user settings', () => { (mockFsExistsSync as Mock).mockImplementation( (p: fs.PathLike) => p === USER_SETTINGS_PATH, ); const userSettingsContent = { general: {}, - model: { chatCompression: { contextPercentageThreshold: 0.5 } }, + model: { compressionThreshold: 0.5 }, }; (fs.readFileSync as Mock).mockImplementation( (p: fs.PathOrFileDescriptor) => { @@ -1179,9 +1175,7 @@ describe('Settings Loading and Merging', () => { ); const settings = loadSettings(MOCK_WORKSPACE_DIR); - expect(settings.merged.model?.chatCompression).toEqual({ - contextPercentageThreshold: 0.5, - }); + expect(settings.merged.model?.compressionThreshold).toEqual(0.5); }); it('should have model as undefined if not in any settings file', () => { @@ -1191,39 +1185,15 @@ describe('Settings Loading and Merging', () => { expect(settings.merged.model).toBeUndefined(); }); - 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 = { - general: {}, - model: { 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.model?.chatCompression).toEqual({ - contextPercentageThreshold: 1.5, - }); - warnSpy.mockRestore(); - }); - - it('should deep merge chatCompression settings', () => { + it('should use user compressionThreshold if workspace does not define it', () => { (mockFsExistsSync as Mock).mockReturnValue(true); const userSettingsContent = { general: {}, - model: { chatCompression: { contextPercentageThreshold: 0.5 } }, + model: { compressionThreshold: 0.5 }, }; const workspaceSettingsContent = { general: {}, - model: { chatCompression: {} }, + model: {}, }; (fs.readFileSync as Mock).mockImplementation( @@ -1238,9 +1208,7 @@ describe('Settings Loading and Merging', () => { const settings = loadSettings(MOCK_WORKSPACE_DIR); - expect(settings.merged.model?.chatCompression).toEqual({ - contextPercentageThreshold: 0.5, - }); + expect(settings.merged.model?.compressionThreshold).toEqual(0.5); }); it('should merge includeDirectories from all scopes', () => { @@ -2025,9 +1993,6 @@ describe('Settings Loading and Merging', () => { }, model: { name: 'gemini-pro', - chatCompression: { - contextPercentageThreshold: 0.5, - }, }, mcpServers: { 'server-1': { @@ -2046,9 +2011,6 @@ describe('Settings Loading and Merging', () => { myTheme: {}, }, model: 'gemini-pro', - chatCompression: { - contextPercentageThreshold: 0.5, - }, mcpServers: { 'server-1': { command: 'node server.js', @@ -2088,9 +2050,6 @@ describe('Settings Loading and Merging', () => { }, model: { name: 'gemini-pro', - chatCompression: { - contextPercentageThreshold: 0.8, - }, }, context: { fileName: 'CONTEXT.md', @@ -2130,9 +2089,6 @@ describe('Settings Loading and Merging', () => { theme: 'dark', usageStatisticsEnabled: false, model: 'gemini-pro', - chatCompression: { - contextPercentageThreshold: 0.8, - }, contextFileName: 'CONTEXT.md', includeDirectories: ['/src'], sandbox: true, diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index e22ac7eb2a..dfd38b7e27 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -64,7 +64,7 @@ const MIGRATION_MAP: Record = { autoAccept: 'tools.autoAccept', autoConfigureMaxOldSpaceSize: 'advanced.autoConfigureMemory', bugCommand: 'advanced.bugCommand', - chatCompression: 'model.chatCompression', + chatCompression: 'model.compressionThreshold', checkpointing: 'general.checkpointing', coreTools: 'tools.core', contextFileName: 'context.fileName', diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 7de0c85e86..01f247e778 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -14,7 +14,6 @@ import type { BugCommandSettings, TelemetrySettings, AuthType, - ChatCompressionSettings, } from '@google/gemini-cli-core'; import { DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES, @@ -578,14 +577,15 @@ const SETTINGS_SCHEMA = { description: 'Settings for summarizing tool output.', showInDialog: false, }, - chatCompression: { - type: 'object', - label: 'Chat Compression', + compressionThreshold: { + type: 'number', + label: 'Compression Threshold', category: 'Model', requiresRestart: false, - default: undefined as ChatCompressionSettings | undefined, - description: 'Chat compression settings.', - showInDialog: false, + default: 0.2 as number, + description: + 'The fraction of context usage at which to trigger context compression (e.g. 0.2, 0.3).', + showInDialog: true, }, skipNextSpeakerCheck: { type: 'boolean', diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 10132b6a5a..ca37e9cada 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -92,10 +92,6 @@ export interface BugCommandSettings { urlTemplate: string; } -export interface ChatCompressionSettings { - contextPercentageThreshold?: number; -} - export interface SummarizeToolOutputSettings { tokenBudget?: number; } @@ -262,7 +258,7 @@ export interface ConfigParameters { folderTrust?: boolean; ideMode?: boolean; loadMemoryFromIncludeDirectories?: boolean; - chatCompression?: ChatCompressionSettings; + compressionThreshold?: number; interactive?: boolean; trustedFolder?: boolean; useRipgrep?: boolean; @@ -359,7 +355,7 @@ export class Config { | undefined; private readonly experimentalZedIntegration: boolean = false; private readonly loadMemoryFromIncludeDirectories: boolean = false; - private readonly chatCompression: ChatCompressionSettings | undefined; + private readonly compressionThreshold: number | undefined; private readonly interactive: boolean; private readonly ptyInfo: string; private readonly trustedFolder: boolean | undefined; @@ -462,7 +458,7 @@ export class Config { this.ideMode = params.ideMode ?? false; this.loadMemoryFromIncludeDirectories = params.loadMemoryFromIncludeDirectories ?? false; - this.chatCompression = params.chatCompression; + this.compressionThreshold = params.compressionThreshold; this.interactive = params.interactive ?? false; this.ptyInfo = params.ptyInfo ?? 'child_process'; this.trustedFolder = params.trustedFolder; @@ -1003,8 +999,8 @@ export class Config { this.fileSystemService = fileSystemService; } - getChatCompression(): ChatCompressionSettings | undefined { - return this.chatCompression; + getCompressionThreshold(): number | undefined { + return this.compressionThreshold; } isInteractiveShellEnabled(): boolean { diff --git a/packages/core/src/services/chatCompressionService.test.ts b/packages/core/src/services/chatCompressionService.test.ts index f7ffe55eed..a0766f9ffc 100644 --- a/packages/core/src/services/chatCompressionService.test.ts +++ b/packages/core/src/services/chatCompressionService.test.ts @@ -70,7 +70,7 @@ describe('findCompressSplitPoint', () => { expect(findCompressSplitPoint(history, 0.8)).toBe(4); }); - it('should return earlier splitpoint if no valid ones are after threshhold', () => { + it('should return earlier splitpoint if no valid ones are after threshold', () => { const history: Content[] = [ { role: 'user', parts: [{ text: 'This is the first message.' }] }, { role: 'model', parts: [{ text: 'This is the second message.' }] }, @@ -115,7 +115,7 @@ describe('ChatCompressionService', () => { getLastPromptTokenCount: vi.fn().mockReturnValue(500), } as unknown as GeminiChat; mockConfig = { - getChatCompression: vi.fn(), + getCompressionThreshold: vi.fn(), getContentGenerator: vi.fn(), } as unknown as Config; diff --git a/packages/core/src/services/chatCompressionService.ts b/packages/core/src/services/chatCompressionService.ts index fa96b27749..573b2ae458 100644 --- a/packages/core/src/services/chatCompressionService.ts +++ b/packages/core/src/services/chatCompressionService.ts @@ -106,8 +106,7 @@ export class ChatCompressionService { // Don't compress if not forced and we are under the limit. if (!force) { const threshold = - config.getChatCompression()?.contextPercentageThreshold ?? - DEFAULT_COMPRESSION_TOKEN_THRESHOLD; + config.getCompressionThreshold() ?? DEFAULT_COMPRESSION_TOKEN_THRESHOLD; if (originalTokenCount < threshold * tokenLimit(model)) { return { newHistory: null,