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

158 lines
4.9 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2026-04-07 04:46:04 +00:00
import type { Content } from '@google/genai';
import type { AgentChatHistory } from '../core/agentChatHistory.js';
2026-04-08 02:34:06 +00:00
import type { ConcreteNode } from './ir/types.js';
2026-04-07 02:16:06 +00:00
import type { ContextEventBus } from './eventBus.js';
import type { ContextTracer } from './tracer.js';
import type { ContextEnvironment } from './sidecar/environment.js';
import type { SidecarConfig } from './sidecar/types.js';
2026-04-09 03:35:12 +00:00
import type { PipelineOrchestrator } from './sidecar/orchestrator.js';
2026-04-06 18:46:21 +00:00
import { HistoryObserver } from './historyObserver.js';
import { IrProjector } from './ir/projector.js';
2026-04-09 18:44:27 +00:00
import { ContextWorkingBufferImpl } from './sidecar/contextWorkingBuffer.js';
2026-04-07 03:58:50 +00:00
export class ContextManager {
2026-04-09 18:44:27 +00:00
// The master state containing the pristine graph and current active graph.
private buffer: ContextWorkingBufferImpl = ContextWorkingBufferImpl.initialize([]);
2026-04-09 01:33:55 +00:00
private pristineNodes: readonly ConcreteNode[] = [];
2026-04-09 18:44:27 +00:00
private readonly eventBus: ContextEventBus;
2026-04-07 04:46:04 +00:00
// Internal sub-components
2026-04-09 02:44:14 +00:00
private readonly orchestrator: PipelineOrchestrator;
private readonly historyObserver: HistoryObserver;
2026-04-07 03:13:14 +00:00
2026-04-09 02:44:14 +00:00
constructor(
private readonly sidecar: SidecarConfig,
private readonly env: ContextEnvironment,
2026-04-07 04:46:04 +00:00
private readonly tracer: ContextTracer,
orchestrator: PipelineOrchestrator,
2026-04-09 02:44:14 +00:00
chatHistory: AgentChatHistory,
2026-04-07 03:13:14 +00:00
) {
2026-04-06 19:18:17 +00:00
this.eventBus = env.eventBus;
2026-04-07 03:13:14 +00:00
this.orchestrator = orchestrator;
2026-04-09 02:44:14 +00:00
this.historyObserver = new HistoryObserver(
chatHistory,
this.env.eventBus,
this.tracer,
this.env.tokenCalculator,
this.env.irMapper,
);
this.historyObserver.start();
2026-04-06 19:54:09 +00:00
this.eventBus.onPristineHistoryUpdated((event) => {
2026-04-09 01:33:55 +00:00
this.pristineNodes = event.nodes;
2026-04-09 18:44:27 +00:00
const existingIds = new Set(this.buffer.nodes.map((n) => n.id));
2026-04-09 01:33:55 +00:00
const addedNodes = event.nodes.filter((n) => !existingIds.has(n.id));
2026-04-09 18:44:27 +00:00
2026-04-08 02:34:06 +00:00
if (addedNodes.length > 0) {
2026-04-09 18:44:27 +00:00
this.buffer = this.buffer.appendPristineNodes(addedNodes);
2026-04-08 02:34:06 +00:00
}
2026-04-07 20:59:26 +00:00
this.evaluateTriggers(event.newNodes);
2026-04-06 19:54:09 +00:00
});
}
/**
* Safely stops background workers and clears event listeners.
*/
shutdown() {
2026-04-06 17:59:01 +00:00
this.orchestrator.shutdown();
2026-04-09 02:44:14 +00:00
this.historyObserver.stop();
}
2026-04-06 19:54:09 +00:00
/**
* Evaluates if the current working buffer exceeds configured budget thresholds,
* firing consolidation events if necessary.
*/
2026-04-07 20:59:26 +00:00
private evaluateTriggers(newNodes: Set<string>) {
2026-04-06 19:54:09 +00:00
if (!this.sidecar.budget) return;
2026-04-07 20:59:26 +00:00
if (newNodes.size > 0) {
2026-04-09 17:43:51 +00:00
this.eventBus.emitChunkReceived({
2026-04-09 18:44:27 +00:00
nodes: this.buffer.nodes,
2026-04-09 17:43:51 +00:00
targetNodeIds: newNodes,
});
2026-04-07 20:59:26 +00:00
}
2026-04-06 19:54:09 +00:00
2026-04-09 17:43:51 +00:00
const currentTokens = this.env.tokenCalculator.calculateConcreteListTokens(
2026-04-09 18:44:27 +00:00
this.buffer.nodes,
2026-04-09 17:43:51 +00:00
);
2026-04-08 02:34:06 +00:00
2026-04-06 19:54:09 +00:00
if (currentTokens > this.sidecar.budget.retainedTokens) {
2026-04-07 20:59:26 +00:00
const agedOutNodes = new Set<string>();
let rollingTokens = 0;
2026-04-08 02:34:06 +00:00
// Walk backwards finding nodes that fall out of the retained budget
2026-04-09 18:44:27 +00:00
for (let i = this.buffer.nodes.length - 1; i >= 0; i--) {
const node = this.buffer.nodes[i];
2026-04-09 17:43:51 +00:00
rollingTokens += this.env.tokenCalculator.calculateConcreteListTokens([
node,
]);
2026-04-07 20:59:26 +00:00
if (rollingTokens > this.sidecar.budget.retainedTokens) {
2026-04-08 02:34:06 +00:00
agedOutNodes.add(node.id);
2026-04-07 20:59:26 +00:00
}
}
2026-04-08 02:34:06 +00:00
if (agedOutNodes.size > 0) {
this.eventBus.emitConsolidationNeeded({
2026-04-09 18:44:27 +00:00
nodes: this.buffer.nodes,
2026-04-08 02:34:06 +00:00
targetDeficit: currentTokens - this.sidecar.budget.retainedTokens,
targetNodeIds: agedOutNodes,
});
}
2026-04-06 19:54:09 +00:00
}
}
/**
2026-04-08 02:34:06 +00:00
* Retrieves the raw, uncompressed Episodic IR graph.
* Useful for internal tool rendering (like the trace viewer).
* Note: This is an expensive, deep clone operation.
*/
2026-04-08 20:44:56 +00:00
getPristineGraph(): readonly ConcreteNode[] {
2026-04-09 01:33:55 +00:00
return [...this.pristineNodes];
}
/**
2026-04-08 02:34:06 +00:00
* Generates a virtual view of the pristine graph, substituting in variants
* up to the configured token budget.
* This is the view that will eventually be projected back to the LLM.
*/
2026-04-09 01:33:55 +00:00
getNodes(): readonly ConcreteNode[] {
2026-04-09 18:44:27 +00:00
return [...this.buffer.nodes];
2026-04-08 02:34:06 +00:00
}
2026-04-07 04:46:04 +00:00
2026-04-08 02:34:06 +00:00
/**
* Executes the final 'gc_backstop' pipeline if necessary, enforcing the token budget,
* and maps the Episodic IR back into a raw Gemini Content[] array for transmission.
* This is the primary method called by the agent framework before sending a request.
*/
async projectCompressedHistory(
activeTaskIds: Set<string> = new Set(),
): Promise<Content[]> {
this.tracer.logEvent(
'ContextManager',
'Starting projection to LLM context',
);
// Apply final GC Backstop pressure barrier synchronously before mapping
const finalHistory = await IrProjector.project(
2026-04-09 18:44:27 +00:00
this.buffer.nodes,
2026-04-06 18:58:49 +00:00
this.orchestrator,
this.sidecar,
this.tracer,
this.env,
2026-04-08 02:34:06 +00:00
activeTaskIds,
);
2026-04-08 02:34:06 +00:00
this.tracer.logEvent('ContextManager', 'Finished projection');
return finalHistory;
}
}