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

222 lines
6.7 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi } from 'vitest';
2026-04-06 17:59:01 +00:00
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-06 22:47:43 +00:00
import { randomUUID } from 'node:crypto';
2026-04-08 17:12:12 +00:00
import { ContextTracer } from '../tracer.js';
import { ContextEnvironmentImpl } from '../sidecar/environmentImpl.js';
import { SidecarLoader } from '../sidecar/SidecarLoader.js';
import { ContextEventBus } from '../eventBus.js';
import { ContextTokenCalculator } from '../utils/contextTokenCalculator.js';
import { ProcessorRegistry } from '../sidecar/registry.js';
import { registerBuiltInProcessors } from '../sidecar/builtins.js';
import type { ContextAccountingState } from '../pipeline.js';
import type { ConcreteNode, ToolExecution } from '../ir/types.js';
import type { ContextEnvironment } from '../sidecar/environment.js';
import type { Config } from '../../config/config.js';
import type { BaseLlmClient } from '../../core/baseLlmClient.js';
import type { Content } from '@google/genai';
2026-04-06 22:47:43 +00:00
export function createDummyState(
isSatisfied = false,
deficit = 0,
protectedIds = new Set<string>(),
currentTokens = 5000,
maxTokens = 10000,
retainedTokens = 4000,
): ContextAccountingState {
return {
currentTokens,
maxTokens,
retainedTokens,
deficitTokens: deficit,
2026-04-08 02:34:06 +00:00
protectedLogicalIds: protectedIds,
2026-04-06 22:47:43 +00:00
isBudgetSatisfied: isSatisfied,
};
}
2026-04-08 02:34:06 +00:00
export function createDummyNode(
logicalParentId: string,
type: 'USER_PROMPT' | 'SYSTEM_EVENT' | 'AGENT_THOUGHT' | 'AGENT_YIELD',
tokens = 100,
overrides?: Partial<ConcreteNode>,
2026-04-08 20:27:00 +00:00
id?: string,
2026-04-08 02:34:06 +00:00
): ConcreteNode {
2026-04-08 17:12:12 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2026-04-08 02:34:06 +00:00
return {
id: id || randomUUID(),
episodeId: logicalParentId,
logicalParentId,
type: type as any,
timestamp: Date.now(),
text: `Dummy ${type}`,
name: type === 'SYSTEM_EVENT' ? 'dummy_event' : undefined,
payload: type === 'SYSTEM_EVENT' ? {} : undefined,
semanticParts: [],
metadata: {
originalTokens: tokens,
currentTokens: tokens,
transformations: [],
},
...overrides,
} as unknown as ConcreteNode;
}
export function createDummyToolNode(
logicalParentId: string,
intentTokens = 100,
obsTokens = 200,
overrides?: Partial<ToolExecution>,
2026-04-08 20:27:00 +00:00
id?: string,
2026-04-08 02:34:06 +00:00
): ToolExecution {
2026-04-08 17:12:12 +00:00
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
2026-04-08 02:34:06 +00:00
return {
id: id || randomUUID(),
episodeId: logicalParentId,
logicalParentId,
type: 'TOOL_EXECUTION',
timestamp: Date.now(),
toolName: 'dummy_tool',
intent: { action: 'test' },
observation: { result: 'ok' },
tokens: {
intent: intentTokens,
observation: obsTokens,
},
metadata: {
originalTokens: intentTokens + obsTokens,
currentTokens: intentTokens + obsTokens,
transformations: [],
},
...overrides,
} as unknown as ToolExecution;
}
2026-04-08 20:27:00 +00:00
export function createMockEnvironment(
overrides?: Partial<ContextEnvironment>,
): 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-'),
2026-04-08 02:34:06 +00:00
...overrides,
} as ContextEnvironment;
}
/**
* 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.
*/
2026-04-08 20:27:00 +00:00
export function setupContextComponentTest(
config: Config,
sidecarOverride?: import('../sidecar/types.js').SidecarConfig,
) {
const chatHistory = new AgentChatHistory();
2026-04-07 03:58:50 +00:00
const registry = new ProcessorRegistry();
registerBuiltInProcessors(registry);
2026-04-08 20:27:00 +00:00
const sidecar = sidecarOverride || 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 };
}