feat(core): Land ContextCompressionService (#24483)

This commit is contained in:
joshualitt
2026-04-02 09:22:04 -07:00
committed by GitHub
parent beff8c91aa
commit e0044f2868
32 changed files with 1160 additions and 229 deletions
+85
View File
@@ -21,6 +21,8 @@ import {
type MCPServerConfig,
type GeminiCLIExtension,
Storage,
generalistProfile,
type ContextManagementConfig,
} from '@google/gemini-cli-core';
import { loadCliConfig, parseArguments, type CliArgs } from './config.js';
import {
@@ -2174,6 +2176,89 @@ describe('loadCliConfig directWebFetch', () => {
});
});
describe('loadCliConfig context management', () => {
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
vi.spyOn(ExtensionManager.prototype, 'getExtensions').mockReturnValue([]);
});
afterEach(() => {
vi.unstubAllEnvs();
vi.restoreAllMocks();
});
it('should be false by default when generalistProfile / context management is not set in settings', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments(createTestMergedSettings());
const settings = createTestMergedSettings();
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getContextManagementConfig()).haveOwnProperty(
'enabled',
false,
);
expect(config.isContextManagementEnabled()).toBe(false);
});
it('should be true when generalistProfile is set to true in settings', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments(createTestMergedSettings());
const settings = createTestMergedSettings({
experimental: {
generalistProfile: true,
},
});
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getContextManagementConfig()).toStrictEqual(
generalistProfile,
);
expect(config.isContextManagementEnabled()).toBe(true);
});
it('should be true when contextManagement is set to true in settings', async () => {
process.argv = ['node', 'script.js'];
const argv = await parseArguments(createTestMergedSettings());
const contextManagementConfig: Partial<ContextManagementConfig> = {
historyWindow: {
maxTokens: 100_000,
retainedTokens: 50_000,
},
messageLimits: {
normalMaxTokens: 1000,
retainedMaxTokens: 10_000,
normalizationHeadRatio: 0.25,
},
tools: {
distillation: {
maxOutputTokens: 10_000,
summarizationThresholdTokens: 15_000,
},
outputMasking: {
protectionThresholdTokens: 30_000,
minPrunableThresholdTokens: 10_000,
protectLatestTurn: false,
},
},
};
const settings = createTestMergedSettings({
experimental: {
contextManagement: true,
},
// The type of numbers is being inferred strangely, and so we have to cast
// to `any` here.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
contextManagement: contextManagementConfig as any,
});
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getContextManagementConfig()).toStrictEqual({
enabled: true,
...contextManagementConfig,
});
expect(config.isContextManagementEnabled()).toBe(true);
});
});
describe('screenReader configuration', () => {
beforeEach(() => {
vi.resetAllMocks();
+12 -4
View File
@@ -46,6 +46,7 @@ import {
type HookEventName,
type OutputFormat,
detectIdeFromEnv,
generalistProfile,
} from '@google/gemini-cli-core';
import {
type Settings,
@@ -883,6 +884,16 @@ export async function loadCliConfig(
}
}
const useGeneralistProfile =
settings.experimental?.generalistProfile ?? false;
const useContextManagement =
settings.experimental?.contextManagement ?? false;
const contextManagement = {
...(useGeneralistProfile ? generalistProfile : {}),
...(useContextManagement ? settings?.contextManagement : {}),
enabled: useContextManagement || useGeneralistProfile,
};
return new Config({
acpMode: isAcpMode,
clientName,
@@ -977,10 +988,7 @@ export async function loadCliConfig(
disabledSkills: settings.skills?.disabled,
experimentalJitContext: settings.experimental?.jitContext,
experimentalMemoryManager: settings.experimental?.memoryManager,
contextManagement: {
enabled: settings.experimental?.contextManagement,
...settings?.contextManagement,
},
contextManagement,
modelSteering: settings.experimental?.modelSteering,
topicUpdateNarration: settings.experimental?.topicUpdateNarration,
noBrowser: !!process.env['NO_BROWSER'],
+10
View File
@@ -2149,6 +2149,16 @@ const SETTINGS_SCHEMA = {
'Replace the built-in save_memory tool with a memory manager subagent that supports adding, removing, de-duplicating, and organizing memories.',
showInDialog: true,
},
generalistProfile: {
type: 'boolean',
label: 'Use the generalist profile to manage agent contexts.',
category: 'Experimental',
requiresRestart: true,
default: false,
description:
'Suitable for general coding and software development tasks.',
showInDialog: true,
},
contextManagement: {
type: 'boolean',
label: 'Enable Context Management',
+1 -1
View File
@@ -1034,7 +1034,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
let fileCount: number;
if (config.isJitContextEnabled()) {
await config.getContextManager()?.refresh();
await config.getMemoryContextManager()?.refresh();
config.updateSystemInstructionIfInitialized();
flattenedMemory = flattenMemory(config.getUserMemory());
fileCount = config.getGeminiMdFileCount();
@@ -106,7 +106,7 @@ describe('rewindCommand', () => {
},
config: {
getSessionId: () => 'test-session-id',
getContextManager: () => ({ refresh: mockResetContext }),
getMemoryContextManager: () => ({ refresh: mockResetContext }),
getProjectRoot: mockGetProjectRoot,
},
},
@@ -61,7 +61,9 @@ async function rewindConversation(
client.setHistory(clientHistory as Content[]);
// Reset context manager as we are rewinding history
await context.services.agentContext?.config.getContextManager()?.refresh();
await context.services.agentContext?.config
.getMemoryContextManager()
?.refresh();
// Update UI History
// We generate IDs based on index for the rewind history