mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-16 23:02:51 -07:00
fixes
This commit is contained in:
@@ -27,7 +27,9 @@ export class IrProjector {
|
||||
protectedIds: Set<string>
|
||||
): Promise<Content[]> {
|
||||
if (!sidecar.budget) {
|
||||
return this.projectAndDump(workingBuffer, env);
|
||||
const contents = IrMapper.fromIr(workingBuffer);
|
||||
tracer.logEvent('IrProjector', 'Projected Context to LLM (No Budget)', { projectedContext: contents });
|
||||
return contents;
|
||||
}
|
||||
|
||||
const maxTokens = sidecar.budget.maxTokens;
|
||||
@@ -35,7 +37,9 @@ export class IrProjector {
|
||||
|
||||
if (currentTokens <= maxTokens) {
|
||||
tracer.logEvent('IrProjector', `View is within maxTokens (${currentTokens} <= ${maxTokens}). Returning view.`);
|
||||
return this.projectAndDump(workingBuffer, env);
|
||||
const contents = IrMapper.fromIr(workingBuffer);
|
||||
tracer.logEvent('IrProjector', 'Projected Context to LLM', { projectedContext: contents });
|
||||
return contents;
|
||||
}
|
||||
|
||||
tracer.logEvent('IrProjector', `View exceeds maxTokens (${currentTokens} > ${maxTokens}). Hitting Synchronous Pressure Barrier.`);
|
||||
@@ -54,29 +58,8 @@ export class IrProjector {
|
||||
tracer.logEvent('IrProjector', `Finished projection. Final token count: ${finalTokens}.`);
|
||||
debugLogger.log(`Context Manager finished. Final actual token count: ${finalTokens}.`);
|
||||
|
||||
return this.projectAndDump(processedEpisodes, env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the internal IR graph into a flat Content[] array for the LLM.
|
||||
* If tracing is enabled via environment variables, dumps the payload to disk.
|
||||
*/
|
||||
private static async projectAndDump(episodes: Episode[], env: ContextEnvironment): Promise<Content[]> {
|
||||
const contents = IrMapper.fromIr(episodes);
|
||||
|
||||
if (process.env['GEMINI_DUMP_CONTEXT'] === 'true') {
|
||||
try {
|
||||
const fs = await import('node:fs/promises');
|
||||
const path = await import('node:path');
|
||||
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}`);
|
||||
} catch (e) {
|
||||
debugLogger.error(`Failed to dump context: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
const contents = IrMapper.fromIr(processedEpisodes);
|
||||
tracer.logEvent('IrProjector', 'Projected Sanitized Context to LLM', { projectedContextSanitized: contents });
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ export class PipelineOrchestrator {
|
||||
}
|
||||
|
||||
// Blocking execution
|
||||
this.tracer.logEvent('Orchestrator', `Triggering synchronous pipeline: ${pipeline.name}`);
|
||||
let currentEpisodes = [...episodes];
|
||||
for (let i = 0; i < pipeline.processors.length; i++) {
|
||||
const procDef = pipeline.processors[i];
|
||||
@@ -107,6 +108,7 @@ export class PipelineOrchestrator {
|
||||
if (!processor) continue;
|
||||
|
||||
try {
|
||||
this.tracer.logEvent('Orchestrator', `Executing processor: ${procDef.processorId}`);
|
||||
currentEpisodes = await processor.process(currentEpisodes, state);
|
||||
} catch (error) {
|
||||
debugLogger.error(`Pipeline ${pipeline.name} failed synchronously at ${procDef.processorId}:`, error);
|
||||
@@ -131,6 +133,7 @@ export class PipelineOrchestrator {
|
||||
if (!processor) continue;
|
||||
|
||||
try {
|
||||
this.tracer.logEvent('Orchestrator', `Executing processor: ${procDef.processorId} (async)`);
|
||||
currentEpisodes = await processor.process(currentEpisodes, state);
|
||||
} catch (error) {
|
||||
debugLogger.error(`Pipeline ${pipeline.name} failed at ${procDef.processorId}:`, error);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'node:fs';
|
||||
import { ContextTracer } from './tracer.js';
|
||||
|
||||
vi.mock('node:fs');
|
||||
|
||||
describe('ContextTracer', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it('initializes, logs events, and auto-saves large assets when GEMINI_CONTEXT_TRACE is true', () => {
|
||||
process.env['GEMINI_CONTEXT_TRACE'] = 'true';
|
||||
const mkdirSyncSpy = vi.spyOn(fs, 'mkdirSync');
|
||||
const appendFileSyncSpy = vi.spyOn(fs, 'appendFileSync');
|
||||
const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync');
|
||||
|
||||
const tracer = new ContextTracer('/fake/target', 'test-session');
|
||||
|
||||
expect(mkdirSyncSpy).toHaveBeenCalled();
|
||||
|
||||
// Small logging: shouldn't trigger saveAsset
|
||||
tracer.logEvent('TestComponent', 'TestAction', { key: 'value' });
|
||||
|
||||
expect(appendFileSyncSpy).toHaveBeenCalledTimes(2); // 1 for init, 1 for TestAction
|
||||
expect(writeFileSyncSpy).not.toHaveBeenCalled();
|
||||
const logCall = appendFileSyncSpy.mock.calls[1][1] as string;
|
||||
|
||||
expect(logCall).toContain('[TestComponent] TestAction');
|
||||
expect(logCall).toContain('{"key":"value"}');
|
||||
|
||||
// Large logging: should trigger auto-asset save
|
||||
const hugeString = 'a'.repeat(2000);
|
||||
tracer.logEvent('TestComponent', 'LargeAction', { largeKey: hugeString });
|
||||
|
||||
expect(writeFileSyncSpy).toHaveBeenCalled(); // asset saved
|
||||
|
||||
expect(appendFileSyncSpy).toHaveBeenCalledTimes(4); // init + TestAction + the inner saveAsset log + LargeAction log
|
||||
const largeLogCall = appendFileSyncSpy.mock.calls[3][1] as string;
|
||||
expect(largeLogCall).toContain('LargeAction');
|
||||
expect(largeLogCall).toContain('"$asset":'); // verifies it was extracted
|
||||
});
|
||||
|
||||
it('silently ignores logging when GEMINI_CONTEXT_TRACE is false', () => {
|
||||
process.env['GEMINI_CONTEXT_TRACE'] = 'false';
|
||||
const mkdirSyncSpy = vi.spyOn(fs, 'mkdirSync');
|
||||
const appendFileSyncSpy = vi.spyOn(fs, 'appendFileSync');
|
||||
const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync');
|
||||
|
||||
const tracer = new ContextTracer('/fake/target', 'test-session');
|
||||
expect(mkdirSyncSpy).not.toHaveBeenCalled();
|
||||
|
||||
tracer.logEvent('TestComponent', 'TestAction');
|
||||
expect(appendFileSyncSpy).not.toHaveBeenCalled();
|
||||
|
||||
const hugeString = 'a'.repeat(2000);
|
||||
tracer.logEvent('TestComponent', 'LargeAction', { largeKey: hugeString });
|
||||
expect(writeFileSyncSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,8 @@ export class ContextTracer {
|
||||
private assetsDir: string;
|
||||
private enabled: boolean;
|
||||
|
||||
private readonly MAX_INLINE_SIZE = 1000;
|
||||
|
||||
constructor(targetDir: string, sessionId: string) {
|
||||
this.enabled = process.env['GEMINI_CONTEXT_TRACE'] === 'true';
|
||||
this.traceDir = path.join(targetDir, '.gemini', 'context_trace', sessionId);
|
||||
@@ -37,9 +39,24 @@ export class ContextTracer {
|
||||
) {
|
||||
if (!this.enabled) return;
|
||||
try {
|
||||
let processedDetails: Record<string, unknown> | undefined;
|
||||
|
||||
if (details) {
|
||||
processedDetails = {};
|
||||
for (const [key, value] of Object.entries(details)) {
|
||||
const strValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
if (strValue && strValue.length > this.MAX_INLINE_SIZE) {
|
||||
const assetId = this.saveAsset(component, key, value);
|
||||
processedDetails[key] = { $asset: assetId };
|
||||
} else {
|
||||
processedDetails[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
const detailsStr = details
|
||||
? ` | Details: ${JSON.stringify(details)}`
|
||||
const detailsStr = processedDetails
|
||||
? ` | Details: ${JSON.stringify(processedDetails)}`
|
||||
: '';
|
||||
const logLine = `[${timestamp}] [${component}] ${action}${detailsStr}\n`;
|
||||
fs.appendFileSync(
|
||||
@@ -52,7 +69,7 @@ export class ContextTracer {
|
||||
}
|
||||
}
|
||||
|
||||
saveAsset(component: string, assetName: string, data: unknown): string {
|
||||
private saveAsset(component: string, assetName: string, data: unknown): string {
|
||||
if (!this.enabled) return 'asset-recording-disabled';
|
||||
try {
|
||||
const assetId = `${Date.now()}-${randomUUID().slice(0, 6)}-${assetName}.json`;
|
||||
|
||||
Reference in New Issue
Block a user