This commit is contained in:
Your Name
2026-04-07 02:16:06 +00:00
parent 0dc8efb03f
commit 54e901bf42
20 changed files with 58 additions and 50 deletions
@@ -70,7 +70,7 @@ describe('ContextManager Golden Tests', () => {
}),
};
const sidecar = SidecarLoader.fromConfig(mockConfig as any);
const sidecar = SidecarLoader.fromConfig(mockConfig);
const tracer = new ContextTracer({ targetDir: '/tmp', sessionId: 'test-session' });
const eventBus = new ContextEventBus();
const env = new ContextEnvironmentImpl(
+3 -3
View File
@@ -10,8 +10,8 @@ import type { AgentChatHistory } from '../core/agentChatHistory.js';
import { debugLogger } from '../utils/debugLogger.js';
import type { Episode } from './ir/types.js';
import { ContextEventBus } from './eventBus.js';
import { ContextTracer } from './tracer.js';
import type { ContextEventBus } from './eventBus.js';
import type { ContextTracer } from './tracer.js';
@@ -142,7 +142,7 @@ export class ContextManager {
* (snapshot > summary > masked) instead of the raw text.
* Handles N-to-1 variant skipping automatically.
*/
public getWorkingBufferView(): Episode[] {
getWorkingBufferView(): Episode[] {
return generateWorkingBufferView(
this.pristineEpisodes,
this.sidecar.budget.retainedTokens,
@@ -30,7 +30,7 @@ export class EpisodeEditor {
* Provides a readonly view of the current working state of the episodes.
* Processors should iterate over this to decide what to mutate.
*/
get episodes(): ReadonlyArray<Episode> {
get episodes(): readonly Episode[] {
return this.workingOrder.map(id => this.workingMap.get(id)!);
}
@@ -16,9 +16,7 @@ describe('graphUtils (View Generator)', () => {
vi.resetAllMocks();
env = createMockEnvironment();
// Our token mock is 1 char = 1 token for simplicity
vi.spyOn(env.tokenCalculator, 'calculateEpisodeListTokens').mockImplementation((eps) => {
return eps.reduce((acc, ep) => acc + (ep.trigger.metadata.originalTokens || 100), 0);
});
vi.spyOn(env.tokenCalculator, 'calculateEpisodeListTokens').mockImplementation((eps) => eps.reduce((acc, ep) => acc + (ep.trigger.metadata.originalTokens || 100), 0));
});
it('returns pristine episodes untouched if under budget', () => {
+1 -1
View File
@@ -24,7 +24,7 @@ export function generateWorkingBufferView(
tracer: ContextTracer,
env: ContextEnvironment,
): Episode[] {
let currentEpisodes: Episode[] = [];
const currentEpisodes: Episode[] = [];
let rollingTokens = 0;
const skippedIds = new Set<string>();
tracer.logEvent('ViewGenerator', 'Generating Working Buffer View');
+2 -2
View File
@@ -33,7 +33,7 @@ export class IrProjector {
}
const maxTokens = sidecar.budget.maxTokens;
let currentTokens = env.tokenCalculator.calculateEpisodeListTokens(workingBuffer);
const currentTokens = env.tokenCalculator.calculateEpisodeListTokens(workingBuffer);
if (currentTokens <= maxTokens) {
tracer.logEvent('IrProjector', `View is within maxTokens (${currentTokens} <= ${maxTokens}). Returning view.`);
@@ -46,7 +46,7 @@ export class IrProjector {
debugLogger.log(`Context Manager Synchronous Barrier triggered: View at ${currentTokens} tokens (limit: ${maxTokens}).`);
const processedEpisodes = await orchestrator.executePipeline('Immediate Sanitization', workingBuffer, {
currentTokens: currentTokens,
currentTokens,
maxTokens: sidecar.budget.maxTokens,
retainedTokens: sidecar.budget.retainedTokens,
deficitTokens: Math.max(0, currentTokens - sidecar.budget.maxTokens),
@@ -9,7 +9,7 @@ import { BlobDegradationProcessor } from './blobDegradationProcessor.js';
import { EpisodeEditor } from '../ir/episodeEditor.js';
import type { UserPrompt } from '../ir/types.js';
import type { ContextEnvironment } from '../sidecar/environment.js';
import { InMemoryFileSystem } from '../system/InMemoryFileSystem.js';
import type { InMemoryFileSystem } from '../system/InMemoryFileSystem.js';
describe('BlobDegradationProcessor', () => {
let processor: BlobDegradationProcessor;
@@ -18,10 +18,10 @@ describe('EmergencyTruncationProcessor', () => {
vi.resetAllMocks();
env = createMockEnvironment();
// Force token calculator to return exactly what we tell it for deterministic testing
vi.spyOn(env.tokenCalculator, 'calculateEpisodeListTokens').mockImplementation((episodes) => {
vi.spyOn(env.tokenCalculator, 'calculateEpisodeListTokens').mockImplementation((episodes) =>
// Just sum up the metadata originalTokens for our dummy episodes
return episodes.reduce((acc, ep) => acc + (ep.trigger.metadata.originalTokens || 100), 0);
});
episodes.reduce((acc, ep) => acc + (ep.trigger.metadata.originalTokens || 100), 0)
);
processor = new EmergencyTruncationProcessor(env, {});
});
@@ -108,7 +108,7 @@ export class HistorySquashingProcessor implements ContextProcessor {
currentDeficit,
(p) => {
editor.editEpisode(ep.id, 'SQUASH_THOUGHT', (draft) => {
const draftStep = draft.steps![j];
const draftStep = draft.steps[j];
if (draftStep.type === 'AGENT_THOUGHT') {
draftStep.presentation = p;
}
@@ -116,7 +116,7 @@ export class HistorySquashingProcessor implements ContextProcessor {
},
() => {
editor.editEpisode(ep.id, 'SQUASH_THOUGHT', (draft) => {
const draftStep = draft.steps![j];
const draftStep = draft.steps[j];
if (draftStep.type === 'AGENT_THOUGHT') {
draftStep.metadata.transformations.push({
processorName: this.name,
@@ -100,7 +100,7 @@ export class SemanticCompressionProcessor implements ContextProcessor {
if (newTokens < oldTokens) {
editor.editEpisode(ep.id, 'SUMMARIZE_THOUGHT', (draft) => {
const draftStep = draft.steps![j];
const draftStep = draft.steps[j];
if (draftStep.type === 'AGENT_THOUGHT') {
draftStep.presentation = { text: summary, tokens: newTokens };
draftStep.metadata.transformations.push({
@@ -159,12 +159,12 @@ export class SemanticCompressionProcessor implements ContextProcessor {
if (newObsTokens < oldObsTokens) {
editor.editEpisode(ep.id, 'SUMMARIZE_TOOL', (draft) => {
const draftStep = draft.steps![j];
const draftStep = draft.steps[j];
if (draftStep.type === 'TOOL_EXECUTION') {
draftStep.presentation = {
intent: draftStep.presentation?.intent ?? draftStep.intent,
observation: newObsObject,
tokens: { intent: intentTokens as number, observation: newObsTokens },
tokens: { intent: intentTokens, observation: newObsTokens },
};
if (!draftStep.metadata) { draftStep.metadata = { transformations: [], currentTokens: 0, originalTokens: 0 } as unknown as IrMetadata };
if (!draftStep.metadata.transformations) { draftStep.metadata.transformations = [] };
@@ -87,7 +87,7 @@ Output ONLY the raw factual snapshot, formatted compactly. Do not include markdo
}
for (const step of ep.steps) {
if (step.type === 'TOOL_EXECUTION') {
userPromptText += `[Tool Called: ${(step as ToolExecution).toolName}]\n`;
userPromptText += `[Tool Called: ${(step).toolName}]\n`;
}
}
if (ep.yield) {
@@ -12,7 +12,7 @@ import type { Episode, ToolExecution } from '../ir/types.js';
import type { ContextAccountingState } from '../pipeline.js';
import { randomUUID } from 'node:crypto';
import type { ContextEnvironment } from '../sidecar/environment.js';
import { InMemoryFileSystem } from '../system/InMemoryFileSystem.js';
import type { InMemoryFileSystem } from '../system/InMemoryFileSystem.js';
describe('ToolMaskingProcessor', () => {
let processor: ToolMaskingProcessor;
@@ -200,7 +200,7 @@ export class ToolMaskingProcessor implements ContextProcessor {
this.env.tracer.logEvent('ToolMaskingProcessor', `Masked tool ${toolName}`, { recoveredTokens: savings });
editor.editEpisode(ep.id, 'MASK_TOOL', (draft) => {
const draftStep = draft.steps![j];
const draftStep = draft.steps[j];
if (draftStep.type !== 'TOOL_EXECUTION') return;
if (!draftStep.presentation) {
draftStep.presentation = {
@@ -1,3 +1,8 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { SidecarLoader } from './SidecarLoader.js';
import { defaultSidecarProfile } from './profiles.js';
@@ -27,7 +27,7 @@ export function registerBuiltInProcessors() {
},
required: ['processorId', 'options']
},
create: (env, opts) => new ToolMaskingProcessor(env, opts as any)
create: (env, opts) => new ToolMaskingProcessor(env, opts)
});
ProcessorRegistry.register({
@@ -57,7 +57,7 @@ export function registerBuiltInProcessors() {
},
required: ['processorId', 'options']
},
create: (env, opts) => new SemanticCompressionProcessor(env, opts as any)
create: (env, opts) => new SemanticCompressionProcessor(env, opts)
});
ProcessorRegistry.register({
@@ -74,7 +74,7 @@ export function registerBuiltInProcessors() {
},
required: ['processorId', 'options']
},
create: (env, opts) => new HistorySquashingProcessor(env, opts as any)
create: (env, opts) => new HistorySquashingProcessor(env, opts)
});
ProcessorRegistry.register({
@@ -94,7 +94,7 @@ export function registerBuiltInProcessors() {
},
required: ['processorId']
},
create: (env, opts) => StateSnapshotProcessor.create(env, opts as any)
create: (env, opts) => StateSnapshotProcessor.create(env, opts)
});
ProcessorRegistry.register({
@@ -107,7 +107,7 @@ export function registerBuiltInProcessors() {
},
required: ['processorId']
},
create: (env, opts) => EmergencyTruncationProcessor.create(env, opts as any)
create: (env, opts) => EmergencyTruncationProcessor.create(env, opts)
});
}
@@ -17,19 +17,19 @@ import type { IIdGenerator } from '../system/IIdGenerator.js';
import { NodeIdGenerator } from '../system/NodeIdGenerator.js';
export class ContextEnvironmentImpl implements ContextEnvironment {
public readonly tokenCalculator: ContextTokenCalculator;
public readonly fileSystem: IFileSystem;
public readonly idGenerator: IIdGenerator;
readonly tokenCalculator: ContextTokenCalculator;
readonly fileSystem: IFileSystem;
readonly idGenerator: IIdGenerator;
constructor(
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,
readonly llmClient: BaseLlmClient,
readonly sessionId: string,
readonly promptId: string,
readonly traceDir: string,
readonly projectTempDir: string,
readonly tracer: ContextTracer,
readonly charsPerToken: number,
readonly eventBus: ContextEventBus,
fileSystem?: IFileSystem,
idGenerator?: IIdGenerator,
) {
@@ -52,7 +52,7 @@ describe('PipelineOrchestrator (Component)', () => {
beforeEach(() => {
vi.resetAllMocks();
env = createMockEnvironment();
eventBus = env.eventBus as ContextEventBus;
eventBus = env.eventBus;
// Register our test processors
ProcessorRegistry.register({ id: 'DummySyncProcessor', create: () => new DummySyncProcessor() });
@@ -27,10 +27,10 @@ export interface TurnSummary {
}
export class SimulationHarness {
public readonly chatHistory: AgentChatHistory;
public contextManager!: ContextManager;
public readonly eventBus: ContextEventBus;
public config!: SidecarConfig;
readonly chatHistory: AgentChatHistory;
contextManager!: ContextManager;
readonly eventBus: ContextEventBus;
config!: SidecarConfig;
private tracer!: ContextTracer;
private currentTurnIndex = 0;
private tokenTrajectory: TurnSummary[] = [];
@@ -54,11 +54,11 @@ export class SimulationHarness {
this.config = config;
// Register all standard processors
ProcessorRegistry.register({ id: 'BlobDegradationProcessor', create: (env, opts) => new BlobDegradationProcessor(env) });
ProcessorRegistry.register({ id: 'ToolMaskingProcessor', create: (env, opts) => new ToolMaskingProcessor(env, opts as any) });
ProcessorRegistry.register({ id: 'HistorySquashingProcessor', create: (env, opts) => new HistorySquashingProcessor(env, opts as any) });
ProcessorRegistry.register({ id: 'SemanticCompressionProcessor', create: (env, opts) => new SemanticCompressionProcessor(env, opts as any) });
ProcessorRegistry.register({ id: 'StateSnapshotProcessor', create: (env, opts) => new StateSnapshotProcessor(env, opts as any, env.eventBus) });
ProcessorRegistry.register({ id: 'EmergencyTruncationProcessor', create: (env, opts) => new EmergencyTruncationProcessor(env, opts as any) });
ProcessorRegistry.register({ id: 'ToolMaskingProcessor', create: (env, opts) => new ToolMaskingProcessor(env, opts) });
ProcessorRegistry.register({ id: 'HistorySquashingProcessor', create: (env, opts) => new HistorySquashingProcessor(env, opts) });
ProcessorRegistry.register({ id: 'SemanticCompressionProcessor', create: (env, opts) => new SemanticCompressionProcessor(env, opts) });
ProcessorRegistry.register({ id: 'StateSnapshotProcessor', create: (env, opts) => new StateSnapshotProcessor(env, opts, env.eventBus) });
ProcessorRegistry.register({ id: 'EmergencyTruncationProcessor', create: (env, opts) => new EmergencyTruncationProcessor(env, opts) });
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(this as any).tracer = new ContextTracer({ targetDir: mockTempDir, sessionId: 'sim-session' });
@@ -39,7 +39,7 @@ export function createDummyEpisode(
id: string,
type: 'USER_PROMPT' | 'SYSTEM_EVENT',
parts: unknown[] = [],
toolSteps: { intent: Record<string, unknown>; observation: Record<string, unknown>; toolName?: string; tokens?: { intent: number; observation: number } }[] = []
toolSteps: Array<{ intent: Record<string, unknown>; observation: Record<string, unknown>; toolName?: string; tokens?: { intent: number; observation: number } }> = []
): Episode {
return {
id,
+5
View File
@@ -1,3 +1,8 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ContextTracer } from './tracer.js';
import { InMemoryFileSystem } from './system/InMemoryFileSystem.js';