switch to sidecar profile

This commit is contained in:
Your Name
2026-04-06 15:45:56 +00:00
parent 2ee2399953
commit 279dd99211
8 changed files with 83 additions and 146 deletions
File diff suppressed because one or more lines are too long
@@ -54,36 +54,6 @@ describe('ContextManager Golden Tests', () => {
protectLatestTurn: false,
protectionThresholdTokens: 100,
}),
getContextManagementConfig: vi.fn().mockReturnValue({
strategies: {
historySquashing: { maxTokensPerNode: 3000 },
toolMasking: { stringLengthThresholdTokens: 10000 },
semanticCompression: {
nodeThresholdTokens: 5000,
compressionModel: 'chat-compression-2.5-flash-lite',
},
},
budget: {
maxTokens: 1000,
retainedTokens: 500,
protectedEpisodes: 1,
protectSystemEpisode: true,
},
historyWindow: { maxTokens: 1000, retainedTokens: 500 },
messageLimits: {
normalMaxTokens: 100,
retainedMaxTokens: 50,
normalizationHeadRatio: 0.1,
},
tools: {
outputMasking: {
enabled: true,
protectLatestTurn: false,
protectionThresholdTokens: 100,
minPrunableThresholdTokens: 50,
},
},
}),
storage: { getProjectTempDir: vi.fn().mockReturnValue('/tmp') },
getUsageStatisticsEnabled: vi.fn().mockReturnValue(false),
getBaseLlmClient: vi.fn().mockReturnValue({
@@ -145,56 +115,12 @@ describe('ContextManager Golden Tests', () => {
});
it('should not modify history when under budget', async () => {
mockConfig.getContextManagementConfig.mockReturnValue({
strategies: {
historySquashing: { maxTokensPerNode: 3000 },
toolMasking: { stringLengthThresholdTokens: 10000 },
semanticCompression: {
nodeThresholdTokens: 5000,
compressionModel: 'chat-compression-2.5-flash-lite',
},
},
budget: {
maxTokens: 15000000,
retainedTokens: 50000,
protectedEpisodes: 1,
protectSystemEpisode: true,
},
historyWindow: { maxTokens: 100000, retainedTokens: 50000 },
messageLimits: {
normalMaxTokens: 100,
retainedMaxTokens: 50,
normalizationHeadRatio: 0.1,
},
tools: {
outputMasking: {
enabled: true,
protectLatestTurn: false,
protectionThresholdTokens: 100,
minPrunableThresholdTokens: 50,
},
},
});
const history = createLargeHistory();
(contextManager as any).pristineEpisodes = (
await import('./ir/mapper.js')
).IrMapper.toIr(history);
// In Golden Tests, we just want to ensure the logic doesn't throw or alter unprotected history in weird ways.
// Since we're skipping processors due to being under budget, it should equal history.
mockConfig.getContextManagementConfig.mockReturnValue({
strategies: {
historySquashing: { maxTokensPerNode: 3000 },
toolMasking: { stringLengthThresholdTokens: 10000 },
semanticCompression: {
nodeThresholdTokens: 5000,
},
},
budget: {
maxTokens: 15000000,
retainedTokens: 50000,
},
gcBackstop: { target: 'incremental', strategy: 'truncate' },
});
const tracer2 = new ContextTracer('/tmp', 'test2');
contextManager = new ContextManager({ pipelines: { eagerBackground: [], normalProcessingGraph: [], retainedProcessingGraph: [] } } as any, {} as any, tracer2);
@@ -4,56 +4,32 @@
* SPDX-License-Identifier: Apache-2.0
*/
import * as fs from 'node:fs';
import type { Config } from '../../config/config.js';
import type { SidecarConfig } from './types.js';
import { defaultSidecarProfile } from './profiles.js';
export class SidecarLoader {
/**
* Generates a default Sidecar JSON graph from the user's legacy UI profile settings.
* Generates a Sidecar JSON graph from the experimental config file path or defaults.
*/
static fromLegacyConfig(config: Config): SidecarConfig {
const mngConfig = config.getContextManagementConfig ? config.getContextManagementConfig() : undefined;
const strat: any = mngConfig?.strategies ?? {};
const budget = mngConfig?.budget ?? { retainedTokens: 65000, maxTokens: 150000, maxPressureStrategy: 'truncate', gcTarget: 'incremental', freeTokensTarget: 10000 };
return {
budget: {
retainedTokens: budget.retainedTokens,
maxTokens: budget.maxTokens,
},
gcBackstop: {
strategy: budget.maxPressureStrategy,
target: budget.gcTarget,
freeTokensTarget: budget.freeTokensTarget,
},
pipelines: {
eagerBackground: [
{
processorId: 'StateSnapshotWorker',
options: {},
}
],
retainedProcessingGraph: [
{
processorId: 'HistorySquashingProcessor',
options: { maxTokensPerNode: strat.historySquashing?.maxTokensPerNode ?? 3000 }
}
],
normalProcessingGraph: [
{
processorId: 'ToolMaskingProcessor',
options: { stringLengthThresholdTokens: strat.toolMasking?.stringLengthThresholdTokens ?? 8000 }
},
{
processorId: 'BlobDegradationProcessor',
options: {}
},
{
processorId: 'SemanticCompressionProcessor',
options: { nodeThresholdTokens: strat.semanticCompression?.nodeThresholdTokens ?? 3000 }
}
]
static fromConfig(config: Config): SidecarConfig {
const sidecarPath = typeof (config as any).getExperimentalContextSidecarConfig === 'function' ? (config as any).getExperimentalContextSidecarConfig() : undefined;
if (sidecarPath && fs.existsSync(sidecarPath)) {
try {
const fileContent = fs.readFileSync(sidecarPath, 'utf8');
return JSON.parse(fileContent) as SidecarConfig;
} catch (error) {
console.error(`Failed to parse Sidecar configuration file at ${sidecarPath}:`, error);
// Fallback to default
}
};
}
return defaultSidecarProfile;
}
static fromLegacyConfig(config: Config): SidecarConfig {
return SidecarLoader.fromConfig(config);
}
}
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type { SidecarConfig } from './types.js';
/**
* The standard default context management profile.
* Optimized for safety, precision, and reliable summarization.
*/
export const defaultSidecarProfile: SidecarConfig = {
budget: {
retainedTokens: 65000,
maxTokens: 150000,
},
gcBackstop: {
strategy: 'truncate',
target: 'incremental',
freeTokensTarget: 10000,
},
pipelines: {
eagerBackground: [
{
processorId: 'StateSnapshotWorker',
options: { pollingIntervalMs: 5000 }
}
],
retainedProcessingGraph: [
{
processorId: 'HistorySquashingProcessor',
options: { maxTokensPerNode: 3000 }
}
],
normalProcessingGraph: [
{
processorId: 'ToolMaskingProcessor',
options: { stringLengthThresholdTokens: 8000 }
},
{
processorId: 'BlobDegradationProcessor',
options: {}
},
{
processorId: 'SemanticCompressionProcessor',
options: { nodeThresholdTokens: 5000, contextWindowPercentage: 0.2 }
}
]
}
};
@@ -69,26 +69,6 @@ export function createMockContextConfig(
storage: {
getProjectTempDir: vi.fn().mockReturnValue('/tmp/gemini-test'),
},
getContextManagementConfig: vi.fn().mockReturnValue({
enabled: true,
charsPerToken: 1,
strategies: {
historySquashing: { maxTokensPerNode: 3000 },
toolMasking: { stringLengthThresholdTokens: 10000 },
semanticCompression: { nodeThresholdTokens: 5000 },
},
budget: { retainedTokens: 500, maxTokens: 150000, maxPressureStrategy: 'truncate', gcTarget: 'incremental', freeTokensTarget: 1000 },
gcBackstop: { strategy: 'truncate', target: 'freeNTokens', freeTokensTarget: 100 },
pipelines: {
eagerBackground: [{ processorId: 'StateSnapshotWorker', options: {} }],
retainedProcessingGraph: [{ processorId: 'HistorySquashingProcessor', options: { maxTokensPerNode: 3000 } }],
normalProcessingGraph: [
{ processorId: 'ToolMaskingProcessor', options: { stringLengthThresholdTokens: 10000 } },
{ processorId: 'BlobDegradationProcessor', options: {} },
{ processorId: 'SemanticCompressionProcessor', options: { nodeThresholdTokens: 5000 } }
]
}
}),
getBaseLlmClient: vi.fn().mockReturnValue(
llmClientOverride || {
generateContent: vi.fn().mockResolvedValue({
@@ -62,11 +62,7 @@ describe('ToolOutputMaskingService', () => {
}
});
it('should respect remote configuration overrides', async () => {
mockConfig.getContextManagementConfig = () =>
({
strategies: { toolMasking: { stringLengthThresholdTokens: 100 } },
}) as any; // eslint-disable-line @typescript-eslint/no-explicit-any
it.skip('should respect remote configuration overrides (Feature Moved to Sidecar)', async () => {
const history: Content[] = [
{
@@ -75,8 +75,7 @@ export class ToolOutputMaskingService {
return { newHistory: history, maskedCount: 0, tokensSaved: 0 };
}
const maskingConfig = config.getContextManagementConfig?.()?.strategies
?.toolMasking || { stringLengthThresholdTokens: 10000 };
const maskingConfig = { stringLengthThresholdTokens: 10000 };
let cumulativeToolTokens = 0;
let protectionBoundaryReached = false;
let totalPrunableTokens = 0;
+1 -1
View File
@@ -258,7 +258,7 @@ describe('Gemini Client (client.ts)', () => {
getProjectRoot: vi.fn().mockReturnValue('/test/project/root'),
getIncludeDirectoryTree: vi.fn().mockReturnValue(true),
storage: {
getProjectTempDir: vi.fn().mockReturnValue('/test/temp'),
getProjectTempDir: vi.fn().mockReturnValue('/tmp/gemini-test'),
},
getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator),
getBaseLlmClient: vi.fn().mockReturnValue({