mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-16 23:02:51 -07:00
refactor environment
This commit is contained in:
@@ -17,6 +17,7 @@ import { ContextManager } from './contextManager.js';
|
||||
import { ContextEnvironmentImpl } from './sidecar/environmentImpl.js';
|
||||
import { SidecarLoader } from './sidecar/SidecarLoader.js';
|
||||
import { ContextTracer } from './tracer.js';
|
||||
import { ContextEventBus } from './eventBus.js';
|
||||
|
||||
import type { Content } from '@google/genai';
|
||||
|
||||
@@ -69,6 +70,7 @@ describe('ContextManager Golden Tests', () => {
|
||||
|
||||
const sidecar = SidecarLoader.fromLegacyConfig(mockConfig as any);
|
||||
const tracer = new ContextTracer('/tmp', 'test-session');
|
||||
const eventBus = new ContextEventBus();
|
||||
const env = new ContextEnvironmentImpl(
|
||||
{} as any,
|
||||
'test-prompt-id',
|
||||
@@ -77,6 +79,7 @@ describe('ContextManager Golden Tests', () => {
|
||||
'/tmp',
|
||||
tracer,
|
||||
4,
|
||||
eventBus
|
||||
);
|
||||
contextManager = new ContextManager(sidecar, env, tracer);
|
||||
});
|
||||
@@ -128,12 +131,23 @@ describe('ContextManager Golden Tests', () => {
|
||||
// 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.
|
||||
const tracer2 = new ContextTracer('/tmp', 'test2');
|
||||
const eventBus2 = new ContextEventBus();
|
||||
const env2 = new ContextEnvironmentImpl(
|
||||
{} as any,
|
||||
'test-prompt-id',
|
||||
'test',
|
||||
'/tmp',
|
||||
'/tmp',
|
||||
tracer2,
|
||||
4,
|
||||
eventBus2
|
||||
);
|
||||
contextManager = new ContextManager(
|
||||
{
|
||||
budget: { retainedTokens: 100000, maxTokens: 150000 },
|
||||
pipelines: [],
|
||||
} as any,
|
||||
{} as any,
|
||||
env2,
|
||||
tracer2,
|
||||
);
|
||||
|
||||
|
||||
@@ -51,12 +51,7 @@ export class ContextManager {
|
||||
|
||||
|
||||
constructor(private sidecar: SidecarConfig, private env: ContextEnvironment, private readonly tracer: ContextTracer) {
|
||||
|
||||
|
||||
this.eventBus = new ContextEventBus();
|
||||
if ('setEventBus' in this.env) {
|
||||
(this.env as any).setEventBus(this.eventBus);
|
||||
}
|
||||
this.eventBus = env.eventBus;
|
||||
|
||||
// Register built-ins BEFORE creating Orchestrator
|
||||
ProcessorRegistry.register({ id: 'ToolMaskingProcessor', create: (env, opts) => new ToolMaskingProcessor(env, opts as any) });
|
||||
|
||||
@@ -68,7 +68,7 @@ export class IrProjector {
|
||||
try {
|
||||
const fs = await import('node:fs/promises');
|
||||
const path = await import('node:path');
|
||||
const dumpPath = path.join(env.getTraceDir(), '.gemini', 'projected_context.json');
|
||||
const dumpPath = path.join(env.traceDir, '.gemini', 'projected_context.json');
|
||||
await fs.mkdir(path.dirname(dumpPath), { recursive: true });
|
||||
await fs.writeFile(dumpPath, JSON.stringify(contents, null, 2), 'utf-8');
|
||||
debugLogger.log(`[Observability] Context successfully dumped to ${dumpPath}`);
|
||||
|
||||
@@ -33,10 +33,10 @@ export class BlobDegradationProcessor implements ContextProcessor {
|
||||
let directoryCreated = false;
|
||||
|
||||
let blobOutputsDir = path.join(
|
||||
this.env.getProjectTempDir(),
|
||||
this.env.projectTempDir,
|
||||
'degraded-blobs',
|
||||
);
|
||||
const sessionId = this.env.getSessionId();
|
||||
const sessionId = this.env.sessionId;
|
||||
if (sessionId) {
|
||||
blobOutputsDir = path.join(
|
||||
blobOutputsDir,
|
||||
@@ -102,7 +102,7 @@ export class BlobDegradationProcessor implements ContextProcessor {
|
||||
|
||||
if (newText && tokensSaved > 0) {
|
||||
const newTokens = estimateTokenCountSync([{ text: newText }], 0, {
|
||||
charsPerToken: this.env.getCharsPerToken(),
|
||||
charsPerToken: this.env.charsPerToken,
|
||||
});
|
||||
part.presentation = { text: newText, tokens: newTokens };
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
} from '../ir/types.js';
|
||||
import type { ContextAccountingState } from '../pipeline.js';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { BaseLlmClient } from 'src/core/baseLlmClient.js';
|
||||
|
||||
describe('SemanticCompressionProcessor', () => {
|
||||
let processor: SemanticCompressionProcessor;
|
||||
@@ -26,9 +27,7 @@ describe('SemanticCompressionProcessor', () => {
|
||||
});
|
||||
|
||||
const env = createMockEnvironment();
|
||||
env.getLlmClient = vi
|
||||
.fn()
|
||||
.mockReturnValue({ generateContent: generateContentMock }) as any;
|
||||
vi.spyOn(env, 'llmClient', 'get').mockReturnValue({ generateContent: generateContentMock } as unknown as BaseLlmClient);
|
||||
processor = new SemanticCompressionProcessor(env, {
|
||||
nodeThresholdTokens: 2000,
|
||||
});
|
||||
|
||||
@@ -181,7 +181,7 @@ export class SemanticCompressionProcessor implements ContextProcessor {
|
||||
): Promise<string> {
|
||||
const promptMessage = `You are compressing an old episodic context buffer for an AI assistant.\nSummarize this ${contentType} block in 2-3 highly technical sentences. Keep all critical facts, file names, dependencies, and architectural decisions. Discard conversational filler and boilerplate.\n\nContent:\n${content.slice(0, 30000)}`;
|
||||
|
||||
const client = this.env.getLlmClient();
|
||||
const client = this.env.llmClient;
|
||||
try {
|
||||
const response = await client.generateContent({
|
||||
modelConfigKey: { model: this.modelToUse },
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface StateSnapshotProcessorOptions {
|
||||
|
||||
export class StateSnapshotProcessor implements ContextProcessor {
|
||||
static create(env: ContextEnvironment, options: StateSnapshotProcessorOptions): StateSnapshotProcessor {
|
||||
return new StateSnapshotProcessor(env, options, (env as any).getEventBus());
|
||||
return new StateSnapshotProcessor(env, options, env.eventBus);
|
||||
}
|
||||
readonly id = 'StateSnapshotProcessor';
|
||||
readonly name = 'StateSnapshotProcessor';
|
||||
@@ -76,7 +76,7 @@ export class StateSnapshotProcessor implements ContextProcessor {
|
||||
}
|
||||
|
||||
private async synthesizeSnapshot(episodes: Episode[]): Promise<Episode> {
|
||||
const client = this.env.getLlmClient();
|
||||
const client = this.env.llmClient;
|
||||
const systemPrompt =
|
||||
this.options.systemInstruction ??
|
||||
`You are an expert Context Memory Manager. You will be provided with a raw transcript of older conversation turns between a user and an AI assistant.
|
||||
@@ -106,7 +106,7 @@ Output ONLY the raw factual snapshot, formatted compactly. Do not include markdo
|
||||
modelConfigKey: { model: 'state-snapshot-processor' },
|
||||
contents: [{ role: 'user', parts: [{ text: userPromptText }] }],
|
||||
systemInstruction: { role: 'system', parts: [{ text: systemPrompt }] },
|
||||
promptId: this.env.getPromptId(),
|
||||
promptId: this.env.promptId,
|
||||
role: LlmRole.UTILITY_STATE_SNAPSHOT_PROCESSOR,
|
||||
abortSignal: new AbortController().signal,
|
||||
},
|
||||
|
||||
@@ -53,10 +53,10 @@ export class ToolMaskingProcessor implements ContextProcessor {
|
||||
const limitChars = maskingConfig.stringLengthThresholdTokens * 4;
|
||||
|
||||
let toolOutputsDir = path.join(
|
||||
this.env.getProjectTempDir(),
|
||||
this.env.projectTempDir,
|
||||
'tool-outputs',
|
||||
);
|
||||
const sessionId = this.env.getSessionId();
|
||||
const sessionId = this.env.sessionId;
|
||||
if (sessionId) {
|
||||
toolOutputsDir = path.join(
|
||||
toolOutputsDir,
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
export type { ContextTracer, ContextEventBus };
|
||||
|
||||
export interface ContextEnvironment {
|
||||
getLlmClient(): BaseLlmClient;
|
||||
getPromptId(): string;
|
||||
getSessionId(): string;
|
||||
getTraceDir(): string;
|
||||
getProjectTempDir(): string;
|
||||
getEventBus(): ContextEventBus;
|
||||
getTracer(): ContextTracer;
|
||||
getCharsPerToken(): number;
|
||||
readonly llmClient: BaseLlmClient;
|
||||
readonly promptId: string;
|
||||
readonly sessionId: string;
|
||||
readonly traceDir: string;
|
||||
readonly projectTempDir: string;
|
||||
readonly tracer: ContextTracer;
|
||||
readonly charsPerToken: number;
|
||||
|
||||
readonly eventBus: ContextEventBus;
|
||||
}
|
||||
|
||||
@@ -11,52 +11,14 @@ import type { ContextEnvironment } from './environment.js';
|
||||
import type { ContextEventBus } from '../eventBus.js';
|
||||
|
||||
export class ContextEnvironmentImpl implements ContextEnvironment {
|
||||
private eventBus?: ContextEventBus;
|
||||
|
||||
constructor(
|
||||
private llmClient: BaseLlmClient,
|
||||
private sessionId: string,
|
||||
private promptId: string,
|
||||
private traceDir: string,
|
||||
private tempDir: string,
|
||||
private tracer: ContextTracer,
|
||||
private charsPerToken: number,
|
||||
public readonly llmClient: BaseLlmClient,
|
||||
public readonly sessionId: string,
|
||||
public readonly promptId: string,
|
||||
public readonly traceDir: string,
|
||||
public readonly projectTempDir: string,
|
||||
public readonly tracer: ContextTracer,
|
||||
public readonly charsPerToken: number,
|
||||
public readonly eventBus: ContextEventBus,
|
||||
) {}
|
||||
|
||||
setEventBus(bus: ContextEventBus) {
|
||||
this.eventBus = bus;
|
||||
}
|
||||
|
||||
getEventBus(): ContextEventBus {
|
||||
if (!this.eventBus) throw new Error('EventBus not bound');
|
||||
return this.eventBus;
|
||||
}
|
||||
|
||||
getLlmClient(): BaseLlmClient {
|
||||
return this.llmClient;
|
||||
}
|
||||
|
||||
getSessionId(): string {
|
||||
return this.sessionId;
|
||||
}
|
||||
|
||||
getTraceDir(): string {
|
||||
return this.traceDir;
|
||||
}
|
||||
|
||||
getProjectTempDir(): string {
|
||||
return this.tempDir;
|
||||
}
|
||||
|
||||
getTracer(): ContextTracer {
|
||||
return this.tracer;
|
||||
}
|
||||
|
||||
getCharsPerToken(): number {
|
||||
return this.charsPerToken;
|
||||
}
|
||||
|
||||
getPromptId(): string {
|
||||
return this.promptId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,18 @@ import { ContextManager } from '../contextManager.js';
|
||||
export function createMockEnvironment(): ContextEnvironment {
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
getLlmClient: vi.fn().mockReturnValue({
|
||||
llmClient: vi.fn().mockReturnValue({
|
||||
generateContent: vi.fn().mockResolvedValue({
|
||||
text: 'Mock LLM summary response',
|
||||
}),
|
||||
} as unknown as BaseLlmClient),
|
||||
getPromptId: vi.fn().mockReturnValue('mock-prompt-id'),
|
||||
getSessionId: vi.fn().mockReturnValue('mock-session'),
|
||||
getTraceDir: vi.fn().mockReturnValue('/tmp/.gemini/trace'),
|
||||
getProjectTempDir: vi.fn().mockReturnValue('/tmp/.gemini/tool-outputs'),
|
||||
getEventBus: vi.fn(),
|
||||
getTracer: vi.fn(),
|
||||
getCharsPerToken: vi.fn().mockReturnValue(1),
|
||||
})() as unknown as BaseLlmClient,
|
||||
promptId: 'mock-prompt-id',
|
||||
sessionId: 'mock-session',
|
||||
traceDir: '/tmp/.gemini/trace',
|
||||
projectTempDir: '/tmp/.gemini/tool-outputs',
|
||||
eventBus: new ContextEventBus(),
|
||||
tracer: new ContextTracer('/tmp', 'mock-session'),
|
||||
charsPerToken: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,12 +88,14 @@ export function createMockContextConfig(
|
||||
import { ContextTracer } from '../tracer.js';
|
||||
import { ContextEnvironmentImpl } from '../sidecar/environmentImpl.js';
|
||||
import { SidecarLoader } from '../sidecar/SidecarLoader.js';
|
||||
import { ContextEventBus } from '../eventBus.js';
|
||||
import type { BaseLlmClient } from 'src/core/baseLlmClient.js';
|
||||
|
||||
export function setupContextComponentTest(config: Config) {
|
||||
const chatHistory = new AgentChatHistory();
|
||||
const sidecar = SidecarLoader.fromLegacyConfig(config);
|
||||
const tracer = new ContextTracer('/tmp', 'test-session');
|
||||
const eventBus = new ContextEventBus();
|
||||
const env = new ContextEnvironmentImpl(
|
||||
config.getBaseLlmClient(),
|
||||
'test prompt-id',
|
||||
@@ -102,6 +104,7 @@ export function setupContextComponentTest(config: Config) {
|
||||
'/tmp/gemini-test',
|
||||
tracer,
|
||||
1,
|
||||
eventBus
|
||||
);
|
||||
const contextManager = new ContextManager(sidecar, env, tracer);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user