Files
gemini-cli/packages/core/src/context/testing/contextTestUtils.ts
T

222 lines
6.3 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi } from 'vitest';
import type { Config } from '../../config/config.js';
import type { ContextEnvironment } from '../sidecar/environment.js';
2026-04-06 17:59:01 +00:00
import type { Content } from '@google/genai';
import { AgentChatHistory } from '../../core/agentChatHistory.js';
import { ContextManager } from '../contextManager.js';
2026-04-06 22:27:32 +00:00
import { InMemoryFileSystem } from '../system/InMemoryFileSystem.js';
import { DeterministicIdGenerator } from '../system/DeterministicIdGenerator.js';
2026-04-07 04:46:04 +00:00
import type {
Episode,
UserPrompt,
SystemEvent,
SemanticPart,
} from '../ir/types.js';
2026-04-06 22:47:43 +00:00
import type { ContextAccountingState } from '../pipeline.js';
import { randomUUID } from 'node:crypto';
export function createDummyState(
isSatisfied = false,
deficit = 0,
protectedIds = new Set<string>(),
currentTokens = 5000,
maxTokens = 10000,
retainedTokens = 4000,
): ContextAccountingState {
return {
currentTokens,
maxTokens,
retainedTokens,
deficitTokens: deficit,
protectedEpisodeIds: protectedIds,
isBudgetSatisfied: isSatisfied,
};
}
export function createDummyEpisode(
id: string,
type: 'USER_PROMPT' | 'SYSTEM_EVENT',
2026-04-07 03:39:25 +00:00
parts: SemanticPart[] = [],
2026-04-07 04:46:04 +00:00
toolSteps: Array<{
intent: Record<string, unknown>;
observation: Record<string, unknown>;
toolName?: string;
tokens?: { intent: number; observation: number };
}> = [],
2026-04-06 22:47:43 +00:00
): Episode {
2026-04-07 03:39:25 +00:00
let trigger: UserPrompt | SystemEvent;
if (type === 'USER_PROMPT') {
2026-04-07 04:46:04 +00:00
trigger = {
id: randomUUID(),
type: 'USER_PROMPT',
semanticParts: parts,
metadata: {
originalTokens: 100,
currentTokens: 100,
transformations: [],
},
};
2026-04-07 03:39:25 +00:00
} else {
2026-04-07 04:46:04 +00:00
trigger = {
id: randomUUID(),
type: 'SYSTEM_EVENT',
name: 'dummy_event',
payload: {},
metadata: {
originalTokens: 100,
currentTokens: 100,
transformations: [],
},
};
2026-04-07 03:39:25 +00:00
}
2026-04-06 22:47:43 +00:00
return {
id,
timestamp: Date.now(),
2026-04-07 03:39:25 +00:00
trigger,
2026-04-07 04:46:04 +00:00
steps: toolSteps.map((step) => ({
2026-04-06 22:47:43 +00:00
id: randomUUID(),
type: 'TOOL_EXECUTION',
toolName: step.toolName || 'test_tool',
intent: step.intent,
observation: step.observation,
tokens: step.tokens || { intent: 50, observation: 50 },
2026-04-07 04:46:04 +00:00
metadata: {
originalTokens: 100,
currentTokens: 100,
transformations: [],
},
2026-04-06 22:47:43 +00:00
})),
};
}
2026-04-06 22:27:32 +00:00
export function createMockEnvironment(): ContextEnvironment {
return {
2026-04-06 17:59:01 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2026-04-06 19:18:17 +00:00
llmClient: vi.fn().mockReturnValue({
generateContent: vi.fn().mockResolvedValue({
text: 'Mock LLM summary response',
}),
2026-04-06 19:18:17 +00:00
})() as unknown as BaseLlmClient,
promptId: 'mock-prompt-id',
sessionId: 'mock-session',
traceDir: '/tmp/.gemini/trace',
projectTempDir: '/tmp/.gemini/tool-outputs',
eventBus: new ContextEventBus(),
2026-04-06 22:17:32 +00:00
tracer: new ContextTracer({ targetDir: '/tmp', sessionId: 'mock-session' }),
2026-04-06 19:18:17 +00:00
charsPerToken: 1,
2026-04-06 19:48:44 +00:00
tokenCalculator: new ContextTokenCalculator(1),
2026-04-06 22:27:32 +00:00
fileSystem: new InMemoryFileSystem(),
idGenerator: new DeterministicIdGenerator('mock-uuid-'),
};
}
/**
* Creates a block of synthetic conversation history designed to consume a specific number of tokens.
* Assumes roughly 4 characters per token for standard English text.
*/
export function createSyntheticHistory(
numTurns: number,
tokensPerTurn: number,
): Content[] {
const history: Content[] = [];
const charsPerTurn = tokensPerTurn * 1;
for (let i = 0; i < numTurns; i++) {
history.push({
role: 'user',
parts: [{ text: `User turn ${i}. ` + 'A'.repeat(charsPerTurn) }],
});
history.push({
role: 'model',
parts: [{ text: `Model response ${i}. ` + 'B'.repeat(charsPerTurn) }],
});
}
return history;
}
/**
* Creates a fully mocked Config object tailored for Context Component testing.
*/
export function createMockContextConfig(
overrides?: Record<string, unknown>,
llmClientOverride?: unknown,
): Config {
const defaultConfig = {
isContextManagementEnabled: vi.fn().mockReturnValue(true),
storage: {
getProjectTempDir: vi.fn().mockReturnValue('/tmp/gemini-test'),
},
getBaseLlmClient: vi.fn().mockReturnValue(
llmClientOverride || {
generateContent: vi.fn().mockResolvedValue({
text: '<mocked_snapshot>Synthesized state</mocked_snapshot>',
}),
},
),
getUsageStatisticsEnabled: vi.fn().mockReturnValue(false),
getTargetDir: vi.fn().mockReturnValue('/tmp'),
getSessionId: vi.fn().mockReturnValue('test-session'),
2026-04-06 19:54:09 +00:00
getExperimentalContextSidecarConfig: vi.fn().mockReturnValue(undefined),
};
2026-04-06 17:59:01 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return { ...defaultConfig, ...overrides } as unknown as Config;
}
/**
* Wires up a full ContextManager component with an AgentChatHistory and active background workers.
*/
import { ContextTracer } from '../tracer.js';
import { ContextEnvironmentImpl } from '../sidecar/environmentImpl.js';
import { SidecarLoader } from '../sidecar/SidecarLoader.js';
2026-04-06 19:18:17 +00:00
import { ContextEventBus } from '../eventBus.js';
2026-04-06 19:48:44 +00:00
import { ContextTokenCalculator } from '../utils/contextTokenCalculator.js';
2026-04-06 17:59:01 +00:00
import type { BaseLlmClient } from 'src/core/baseLlmClient.js';
2026-04-07 03:58:50 +00:00
import { ProcessorRegistry } from '../sidecar/registry.js';
import { registerBuiltInProcessors } from '../sidecar/builtins.js';
export function setupContextComponentTest(config: Config) {
const chatHistory = new AgentChatHistory();
2026-04-07 03:58:50 +00:00
const registry = new ProcessorRegistry();
registerBuiltInProcessors(registry);
const sidecar = SidecarLoader.fromConfig(config, registry);
2026-04-07 04:46:04 +00:00
const tracer = new ContextTracer({
targetDir: '/tmp',
sessionId: 'test-session',
});
2026-04-06 19:18:17 +00:00
const eventBus = new ContextEventBus();
const env = new ContextEnvironmentImpl(
2026-04-06 17:59:01 +00:00
config.getBaseLlmClient(),
'test prompt-id',
'test-session',
'/tmp',
'/tmp/gemini-test',
tracer,
1,
2026-04-07 04:46:04 +00:00
eventBus,
);
const contextManager = ContextManager.create(
sidecar,
env,
tracer,
undefined,
registry,
);
// The async worker is now internally managed by ContextManager
// Subscribe to history to enable the Eager/Opportunistic triggers
contextManager.subscribeToHistory(chatHistory);
return { chatHistory, contextManager };
}