UNPKG

@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
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); } }