@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and
114 lines (113 loc) • 5.24 kB
JavaScript
import { logger } from "../utils/logger.js";
import { formatHistoryToString } from "./utils.js";
/**
* Manages conversation context, automatically summarizing it when it
* exceeds a specified word count limit.
*/
export class ContextManager {
static SUMMARIZATION_FAILED_WARNING = "[System Warning: Context summarization failed. Conversation history has been truncated.]";
static SUMMARIZATION_EMPTY_WARNING = "[System Warning: Context summarization failed to return valid content. Conversation history has been truncated.]";
history;
wordCount;
internalGenerator;
config;
constructor(generatorFunction, config, initialContext = "This is the start of the conversation.") {
this.internalGenerator = generatorFunction;
this.config = config;
const initialMessage = {
role: "system",
content: initialContext,
};
initialMessage.wordCount = this.config.estimateWordCount([initialMessage]);
this.history = [initialMessage];
this.wordCount = initialMessage.wordCount;
}
async addTurn(role, message) {
const newMessage = { role, content: message };
newMessage.wordCount = this.config.estimateWordCount([newMessage]);
this.history.push(newMessage);
this.wordCount += newMessage.wordCount;
logger.info(`[ContextManager] Current word count: ${this.wordCount} / ${this.config.highWaterMarkWords}`);
if (this.wordCount > this.config.highWaterMarkWords) {
await this._summarize();
}
}
/**
* Formats the history including the latest user turn for the prompt, without modifying the permanent history.
*/
getContextForPrompt(role, message) {
const tempHistory = [...this.history, { role, content: message }];
return formatHistoryToString(tempHistory);
}
getCurrentContext() {
// Format the history into a single string for the provider prompt
return formatHistoryToString(this.history);
}
async _summarize() {
try {
const prompt = this.config.getSummarizationPrompt(this.history, this.config.lowWaterMarkWords);
// Construct options for the internal method, bypassing the main 'generate' entry point
const textOptions = {
prompt,
provider: this.config.summarizationProvider,
model: this.config.summarizationModel,
// Ensure summarization does not trigger more context management or tools
disableTools: true,
};
// Call the internal generation function directly to avoid recursion
const result = await this.internalGenerator(textOptions);
if (typeof result.content === "string" && result.content.length > 0) {
// Replace the history with a single system message containing the summary
const newHistory = [
{ role: "system", content: result.content },
];
this.history = newHistory;
this.wordCount = this.config.estimateWordCount(this.history);
logger.info(`[ContextManager] Summarization complete. New history length: ${this.wordCount} words.`);
}
else {
logger.warn("[ContextManager] Summarization returned empty or non-string content; truncating history as a fallback.");
this._truncateHistory(this.config.lowWaterMarkWords);
this.history.unshift({
role: "system",
content: ContextManager.SUMMARIZATION_EMPTY_WARNING,
});
this.wordCount = this.config.estimateWordCount(this.history);
}
logger.debug(`[ContextManager] New history: ${JSON.stringify(this.history)}`);
}
catch (error) {
logger.error("Context summarization failed:", { error });
// Fallback strategy: truncate the history to the target word count.
this._truncateHistory(this.config.lowWaterMarkWords);
this.history.unshift({
role: "system",
content: ContextManager.SUMMARIZATION_FAILED_WARNING,
});
this.wordCount = this.config.estimateWordCount(this.history);
}
}
/**
* Truncates the history to a specific word count, preserving the most recent messages.
*/
_truncateHistory(wordLimit) {
if (this.wordCount <= wordLimit) {
return;
}
let runningCount = 0;
let sliceIndex = this.history.length;
for (let i = this.history.length - 1; i >= 0; i--) {
let wordCount = this.history[i].wordCount;
if (wordCount === undefined) {
logger.warn(`[ContextManager] Word count cache missing for message at index ${i}. Recalculating.`);
wordCount = this.config.estimateWordCount([this.history[i]]);
}
runningCount += wordCount;
if (runningCount > wordLimit) {
sliceIndex = i + 1;
break;
}
}
this.history = this.history.slice(sliceIndex);
}
}