@juspay/neurolink 8.19.1 → 8.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/loop/optionsSchema.js +4 -0
  3. package/dist/config/conversationMemory.d.ts +15 -0
  4. package/dist/config/conversationMemory.js +22 -3
  5. package/dist/core/conversationMemoryFactory.js +0 -3
  6. package/dist/core/conversationMemoryInitializer.js +1 -9
  7. package/dist/core/conversationMemoryManager.d.ts +31 -8
  8. package/dist/core/conversationMemoryManager.js +174 -80
  9. package/dist/core/redisConversationMemoryManager.d.ts +28 -13
  10. package/dist/core/redisConversationMemoryManager.js +211 -121
  11. package/dist/lib/config/conversationMemory.d.ts +15 -0
  12. package/dist/lib/config/conversationMemory.js +22 -3
  13. package/dist/lib/core/conversationMemoryFactory.js +0 -3
  14. package/dist/lib/core/conversationMemoryInitializer.js +1 -9
  15. package/dist/lib/core/conversationMemoryManager.d.ts +31 -8
  16. package/dist/lib/core/conversationMemoryManager.js +174 -80
  17. package/dist/lib/core/redisConversationMemoryManager.d.ts +28 -13
  18. package/dist/lib/core/redisConversationMemoryManager.js +211 -121
  19. package/dist/lib/neurolink.js +29 -22
  20. package/dist/lib/types/conversation.d.ts +58 -9
  21. package/dist/lib/types/generateTypes.d.ts +1 -0
  22. package/dist/lib/types/sdkTypes.d.ts +1 -1
  23. package/dist/lib/types/streamTypes.d.ts +1 -0
  24. package/dist/lib/utils/conversationMemory.d.ts +43 -1
  25. package/dist/lib/utils/conversationMemory.js +181 -5
  26. package/dist/lib/utils/conversationMemoryUtils.js +16 -1
  27. package/dist/lib/utils/imageProcessor.d.ts +1 -0
  28. package/dist/lib/utils/imageProcessor.js +29 -1
  29. package/dist/lib/utils/redis.js +0 -5
  30. package/dist/neurolink.js +29 -22
  31. package/dist/types/conversation.d.ts +58 -9
  32. package/dist/types/generateTypes.d.ts +1 -0
  33. package/dist/types/sdkTypes.d.ts +1 -1
  34. package/dist/types/streamTypes.d.ts +1 -0
  35. package/dist/utils/conversationMemory.d.ts +43 -1
  36. package/dist/utils/conversationMemory.js +181 -5
  37. package/dist/utils/conversationMemoryUtils.js +16 -1
  38. package/dist/utils/imageProcessor.d.ts +1 -0
  39. package/dist/utils/imageProcessor.js +29 -1
  40. package/dist/utils/redis.js +0 -5
  41. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [8.20.1](https://github.com/juspay/neurolink/compare/v8.20.0...v8.20.1) (2025-12-22)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **(Validation):** implement secure base64 validation with fail-fast checks ([f1b9b9c](https://github.com/juspay/neurolink/commit/f1b9b9c105db38ce439a5e69ff343b77b12be174)), closes [#277](https://github.com/juspay/neurolink/issues/277)
6
+
7
+ ## [8.20.0](https://github.com/juspay/neurolink/compare/v8.19.1...v8.20.0) (2025-12-22)
8
+
9
+ ### Features
10
+
11
+ - **(memory):** Implement token based summarization ([ffdc902](https://github.com/juspay/neurolink/commit/ffdc902f534c97a5aff38d7de419021fcabcd791))
12
+
1
13
  ## [8.19.1](https://github.com/juspay/neurolink/compare/v8.19.0...v8.19.1) (2025-12-20)
2
14
 
3
15
  ### Bug Fixes
@@ -61,5 +61,9 @@ export const textGenerationOptionsSchema = {
61
61
  type: "string",
62
62
  description: "Context about tools/MCPs used in the interaction.",
63
63
  },
64
+ enableSummarization: {
65
+ type: "boolean",
66
+ description: "Enable or disable automatic conversation summarization for this request.",
67
+ },
64
68
  };
65
69
  //# sourceMappingURL=optionsSchema.js.map
@@ -20,6 +20,21 @@ export declare const MESSAGES_PER_TURN = 2;
20
20
  * Used to enhance system prompts when conversation history exists
21
21
  */
22
22
  export declare const CONVERSATION_INSTRUCTIONS = "\n\nIMPORTANT: You are continuing an ongoing conversation. The previous messages in this conversation contain important context including:\n- Names, personal information, and preferences shared by the user\n- Projects, tasks, and topics discussed previously \n- Any decisions, agreements, or conclusions reached\n\nAlways reference and build upon this conversation history when relevant. If the user asks about information mentioned earlier in the conversation, refer to those previous messages to provide accurate, contextual responses.";
23
+ /**
24
+ * Percentage of model context window to use for conversation memory threshold
25
+ * Default: 80% of model's context window
26
+ */
27
+ export declare const MEMORY_THRESHOLD_PERCENTAGE = 0.8;
28
+ /**
29
+ * Fallback token threshold if model context unknown
30
+ */
31
+ export declare const DEFAULT_FALLBACK_THRESHOLD = 50000;
32
+ /**
33
+ * Ratio of threshold to keep as recent unsummarized messages
34
+ * When summarization triggers, this percentage of tokens from the end
35
+ * are preserved as detailed messages, while older content gets summarized.
36
+ */
37
+ export declare const RECENT_MESSAGES_RATIO = 0.3;
23
38
  /**
24
39
  * Structured output instructions for JSON/structured output mode
25
40
  * Used to ensure AI providers output only valid JSON without conversational filler
@@ -26,6 +26,21 @@ IMPORTANT: You are continuing an ongoing conversation. The previous messages in
26
26
  - Any decisions, agreements, or conclusions reached
27
27
 
28
28
  Always reference and build upon this conversation history when relevant. If the user asks about information mentioned earlier in the conversation, refer to those previous messages to provide accurate, contextual responses.`;
29
+ /**
30
+ * Percentage of model context window to use for conversation memory threshold
31
+ * Default: 80% of model's context window
32
+ */
33
+ export const MEMORY_THRESHOLD_PERCENTAGE = 0.8;
34
+ /**
35
+ * Fallback token threshold if model context unknown
36
+ */
37
+ export const DEFAULT_FALLBACK_THRESHOLD = 50000;
38
+ /**
39
+ * Ratio of threshold to keep as recent unsummarized messages
40
+ * When summarization triggers, this percentage of tokens from the end
41
+ * are preserved as detailed messages, while older content gets summarized.
42
+ */
43
+ export const RECENT_MESSAGES_RATIO = 0.3;
29
44
  /**
30
45
  * Structured output instructions for JSON/structured output mode
31
46
  * Used to ensure AI providers output only valid JSON without conversational filler
@@ -56,12 +71,16 @@ export function getConversationMemoryDefaults() {
56
71
  return {
57
72
  enabled: process.env.NEUROLINK_MEMORY_ENABLED === "true",
58
73
  maxSessions: Number(process.env.NEUROLINK_MEMORY_MAX_SESSIONS) || DEFAULT_MAX_SESSIONS,
74
+ enableSummarization: process.env.NEUROLINK_SUMMARIZATION_ENABLED !== "false",
75
+ tokenThreshold: process.env.NEUROLINK_TOKEN_THRESHOLD
76
+ ? Number(process.env.NEUROLINK_TOKEN_THRESHOLD)
77
+ : undefined,
78
+ summarizationProvider: process.env.NEUROLINK_SUMMARIZATION_PROVIDER || "vertex",
79
+ summarizationModel: process.env.NEUROLINK_SUMMARIZATION_MODEL || "gemini-2.5-flash",
80
+ // Deprecated (for backward compatibility)
59
81
  maxTurnsPerSession: Number(process.env.NEUROLINK_MEMORY_MAX_TURNS_PER_SESSION) ||
60
82
  DEFAULT_MAX_TURNS_PER_SESSION,
61
- enableSummarization: process.env.NEUROLINK_SUMMARIZATION_ENABLED === "true",
62
83
  summarizationThresholdTurns: Number(process.env.NEUROLINK_SUMMARIZATION_THRESHOLD_TURNS) || 20,
63
84
  summarizationTargetTurns: Number(process.env.NEUROLINK_SUMMARIZATION_TARGET_TURNS) || 10,
64
- summarizationProvider: process.env.NEUROLINK_SUMMARIZATION_PROVIDER || "vertex",
65
- summarizationModel: process.env.NEUROLINK_SUMMARIZATION_MODEL || "gemini-2.5-flash",
66
85
  };
67
86
  }
@@ -14,10 +14,7 @@ export function createConversationMemoryManager(config, storageType = "memory",
14
14
  config: {
15
15
  enabled: config.enabled,
16
16
  maxSessions: config.maxSessions,
17
- maxTurnsPerSession: config.maxTurnsPerSession,
18
17
  enableSummarization: config.enableSummarization,
19
- summarizationThresholdTurns: config.summarizationThresholdTurns,
20
- summarizationTargetTurns: config.summarizationTargetTurns,
21
18
  summarizationProvider: config.summarizationProvider,
22
19
  summarizationModel: config.summarizationModel,
23
20
  },
@@ -62,15 +62,7 @@ export async function initializeConversationMemory(config) {
62
62
  "RedisConversationMemoryManager",
63
63
  hasConfig: !!redisMemoryManager?.config,
64
64
  });
65
- logger.info("[conversationMemoryInitializer] Redis conversation memory manager created successfully", {
66
- configSource,
67
- host: redisConfig.host || "localhost",
68
- port: redisConfig.port || 6379,
69
- keyPrefix: redisConfig.keyPrefix || "neurolink:conversation:",
70
- maxSessions: memoryConfig.maxSessions,
71
- maxTurnsPerSession: memoryConfig.maxTurnsPerSession,
72
- managerType: redisMemoryManager?.constructor?.name,
73
- });
65
+ logger.info("[conversationMemoryInitializer] Redis conversation memory manager created successfully");
74
66
  // Perform basic validation
75
67
  if (redisMemoryManager?.constructor?.name !==
76
68
  "RedisConversationMemoryManager") {
@@ -2,11 +2,15 @@
2
2
  * Conversation Memory Manager for NeuroLink
3
3
  * Handles in-memory conversation storage, session management, and context injection
4
4
  */
5
- import type { ConversationMemoryConfig, SessionMemory, ConversationMemoryStats, ChatMessage } from "../types/conversation.js";
5
+ import type { ConversationMemoryConfig, SessionMemory, ConversationMemoryStats, ChatMessage, StoreConversationTurnOptions } from "../types/conversation.js";
6
6
  export declare class ConversationMemoryManager {
7
7
  private sessions;
8
8
  config: ConversationMemoryConfig;
9
9
  private isInitialized;
10
+ /**
11
+ * Track sessions currently being summarized to prevent race conditions
12
+ */
13
+ private summarizationInProgress;
10
14
  constructor(config: ConversationMemoryConfig);
11
15
  /**
12
16
  * Initialize the memory manager
@@ -14,19 +18,38 @@ export declare class ConversationMemoryManager {
14
18
  initialize(): Promise<void>;
15
19
  /**
16
20
  * Store a conversation turn for a session
17
- * ULTRA-OPTIMIZED: Direct ChatMessage[] storage with zero conversion overhead
21
+ * TOKEN-BASED: Validates message size and triggers summarization based on tokens
22
+ */
23
+ storeConversationTurn(options: StoreConversationTurnOptions): Promise<void>;
24
+ /**
25
+ * Validate and prepare a message before adding to session
26
+ * Truncates if message exceeds token limit
27
+ */
28
+ private validateAndPrepareMessage;
29
+ /**
30
+ * Check if summarization is needed based on token count
18
31
  */
19
- storeConversationTurn(sessionId: string, userId: string | undefined, userMessage: string, aiResponse: string, _startTimeStamp: Date | undefined): Promise<void>;
32
+ private checkAndSummarize;
20
33
  /**
21
- * Build context messages for AI prompt injection (ULTRA-OPTIMIZED)
22
- * Returns pre-stored message array with zero conversion overhead
34
+ * Estimate total tokens for a list of messages
35
+ */
36
+ private estimateTokens;
37
+ /**
38
+ * Build context messages for AI prompt injection (TOKEN-BASED)
39
+ * Returns messages from pointer onwards (or all if no pointer)
23
40
  * Now consistently async to match Redis implementation
24
41
  */
25
42
  buildContextMessages(sessionId: string): Promise<ChatMessage[]>;
26
43
  getSession(sessionId: string): SessionMemory | undefined;
27
- createSummarySystemMessage(content: string): ChatMessage;
28
- private _summarizeSession;
29
- private _createSummarizationPrompt;
44
+ createSummarySystemMessage(content: string, summarizesFrom?: string, summarizesTo?: string): ChatMessage;
45
+ /**
46
+ * Token-based summarization (pointer-based, non-destructive)
47
+ */
48
+ private summarizeSessionTokenBased;
49
+ /**
50
+ * Find split index to keep recent messages within target token count
51
+ */
52
+ private findSplitIndexByTokens;
30
53
  private ensureInitialized;
31
54
  private createNewSession;
32
55
  private enforceSessionLimit;
@@ -3,13 +3,19 @@
3
3
  * Handles in-memory conversation storage, session management, and context injection
4
4
  */
5
5
  import { ConversationMemoryError } from "../types/conversation.js";
6
- import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
6
+ import { DEFAULT_MAX_SESSIONS, MEMORY_THRESHOLD_PERCENTAGE, RECENT_MESSAGES_RATIO, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
7
7
  import { logger } from "../utils/logger.js";
8
- import { NeuroLink } from "../neurolink.js";
8
+ import { randomUUID } from "crypto";
9
+ import { TokenUtils } from "../constants/tokens.js";
10
+ import { buildContextFromPointer, getEffectiveTokenThreshold, generateSummary, } from "../utils/conversationMemory.js";
9
11
  export class ConversationMemoryManager {
10
12
  sessions = new Map();
11
13
  config;
12
14
  isInitialized = false;
15
+ /**
16
+ * Track sessions currently being summarized to prevent race conditions
17
+ */
18
+ summarizationInProgress = new Set();
13
19
  constructor(config) {
14
20
  this.config = config;
15
21
  }
@@ -34,121 +40,209 @@ export class ConversationMemoryManager {
34
40
  }
35
41
  /**
36
42
  * Store a conversation turn for a session
37
- * ULTRA-OPTIMIZED: Direct ChatMessage[] storage with zero conversion overhead
43
+ * TOKEN-BASED: Validates message size and triggers summarization based on tokens
38
44
  */
39
- async storeConversationTurn(sessionId, userId, userMessage, aiResponse, _startTimeStamp) {
45
+ async storeConversationTurn(options) {
40
46
  await this.ensureInitialized();
41
47
  try {
42
48
  // Get or create session
43
- let session = this.sessions.get(sessionId);
49
+ let session = this.sessions.get(options.sessionId);
44
50
  if (!session) {
45
- session = this.createNewSession(sessionId, userId);
46
- this.sessions.set(sessionId, session);
51
+ session = this.createNewSession(options.sessionId, options.userId);
52
+ this.sessions.set(options.sessionId, session);
47
53
  }
48
- // ULTRA-OPTIMIZED: Direct message storage - no intermediate objects
49
- session.messages.push({ role: "user", content: userMessage }, { role: "assistant", content: aiResponse });
54
+ const tokenThreshold = options.providerDetails
55
+ ? getEffectiveTokenThreshold(options.providerDetails.provider, options.providerDetails.model, this.config.tokenThreshold, session.tokenThreshold)
56
+ : this.config.tokenThreshold || 50000;
57
+ const userMsg = await this.validateAndPrepareMessage(options.userMessage, "user", tokenThreshold);
58
+ const assistantMsg = await this.validateAndPrepareMessage(options.aiResponse, "assistant", tokenThreshold);
59
+ session.messages.push(userMsg, assistantMsg);
50
60
  session.lastActivity = Date.now();
51
- if (this.config.enableSummarization) {
52
- const userAssistantCount = session.messages.filter((msg) => msg.role === "user" || msg.role === "assistant").length;
53
- const currentTurnCount = Math.floor(userAssistantCount / MESSAGES_PER_TURN);
54
- if (currentTurnCount >= (this.config.summarizationThresholdTurns || 20)) {
55
- await this._summarizeSession(session);
61
+ const shouldSummarize = options.enableSummarization !== undefined
62
+ ? options.enableSummarization
63
+ : this.config.enableSummarization;
64
+ if (shouldSummarize) {
65
+ // Only trigger summarization if not already in progress for this session
66
+ if (!this.summarizationInProgress.has(options.sessionId)) {
67
+ setImmediate(async () => {
68
+ try {
69
+ await this.checkAndSummarize(session, tokenThreshold);
70
+ }
71
+ catch (error) {
72
+ logger.error("Background summarization failed", {
73
+ sessionId: session.sessionId,
74
+ error: error instanceof Error ? error.message : String(error),
75
+ });
76
+ }
77
+ });
56
78
  }
57
- }
58
- else {
59
- const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) *
60
- MESSAGES_PER_TURN;
61
- if (session.messages.length > maxMessages) {
62
- session.messages = session.messages.slice(-maxMessages);
79
+ else {
80
+ logger.debug("[ConversationMemoryManager] Summarization already in progress, skipping", {
81
+ sessionId: options.sessionId,
82
+ });
63
83
  }
64
84
  }
65
85
  this.enforceSessionLimit();
66
86
  }
67
87
  catch (error) {
68
- throw new ConversationMemoryError(`Failed to store conversation turn for session ${sessionId}`, "STORAGE_ERROR", {
69
- sessionId,
88
+ throw new ConversationMemoryError(`Failed to store conversation turn for session ${options.sessionId}`, "STORAGE_ERROR", {
89
+ sessionId: options.sessionId,
70
90
  error: error instanceof Error ? error.message : String(error),
71
91
  });
72
92
  }
73
93
  }
74
94
  /**
75
- * Build context messages for AI prompt injection (ULTRA-OPTIMIZED)
76
- * Returns pre-stored message array with zero conversion overhead
95
+ * Validate and prepare a message before adding to session
96
+ * Truncates if message exceeds token limit
97
+ */
98
+ async validateAndPrepareMessage(content, role, threshold) {
99
+ const id = randomUUID();
100
+ const tokenCount = TokenUtils.estimateTokenCount(content);
101
+ const maxMessageSize = Math.floor(threshold * MEMORY_THRESHOLD_PERCENTAGE);
102
+ if (tokenCount > maxMessageSize) {
103
+ const truncated = TokenUtils.truncateToTokenLimit(content, maxMessageSize);
104
+ logger.warn("Message truncated due to token limit", {
105
+ id,
106
+ role,
107
+ originalTokens: tokenCount,
108
+ threshold,
109
+ truncatedTo: maxMessageSize,
110
+ });
111
+ return {
112
+ id,
113
+ role,
114
+ content: truncated,
115
+ timestamp: new Date().toISOString(),
116
+ metadata: {
117
+ truncated: true,
118
+ },
119
+ };
120
+ }
121
+ return {
122
+ id,
123
+ role,
124
+ content,
125
+ timestamp: new Date().toISOString(),
126
+ };
127
+ }
128
+ /**
129
+ * Check if summarization is needed based on token count
130
+ */
131
+ async checkAndSummarize(session, threshold) {
132
+ // Acquire lock - if already in progress, skip
133
+ if (this.summarizationInProgress.has(session.sessionId)) {
134
+ logger.debug("[ConversationMemoryManager] Summarization already in progress, skipping", {
135
+ sessionId: session.sessionId,
136
+ });
137
+ return;
138
+ }
139
+ this.summarizationInProgress.add(session.sessionId);
140
+ try {
141
+ const contextMessages = buildContextFromPointer(session);
142
+ const tokenCount = this.estimateTokens(contextMessages);
143
+ session.lastTokenCount = tokenCount;
144
+ session.lastCountedAt = Date.now();
145
+ logger.debug("Token count check", {
146
+ sessionId: session.sessionId,
147
+ tokenCount,
148
+ threshold,
149
+ needsSummarization: tokenCount >= threshold,
150
+ });
151
+ if (tokenCount >= threshold) {
152
+ await this.summarizeSessionTokenBased(session, threshold);
153
+ }
154
+ }
155
+ catch (error) {
156
+ logger.error("Token counting or summarization failed", {
157
+ sessionId: session.sessionId,
158
+ error: error instanceof Error ? error.message : String(error),
159
+ });
160
+ }
161
+ finally {
162
+ // Release lock when done
163
+ this.summarizationInProgress.delete(session.sessionId);
164
+ }
165
+ }
166
+ /**
167
+ * Estimate total tokens for a list of messages
168
+ */
169
+ estimateTokens(messages) {
170
+ return messages.reduce((total, msg) => {
171
+ return total + TokenUtils.estimateTokenCount(msg.content);
172
+ }, 0);
173
+ }
174
+ /**
175
+ * Build context messages for AI prompt injection (TOKEN-BASED)
176
+ * Returns messages from pointer onwards (or all if no pointer)
77
177
  * Now consistently async to match Redis implementation
78
178
  */
79
179
  async buildContextMessages(sessionId) {
80
180
  const session = this.sessions.get(sessionId);
81
- return session ? session.messages : [];
181
+ return session ? buildContextFromPointer(session) : [];
82
182
  }
83
183
  getSession(sessionId) {
84
184
  return this.sessions.get(sessionId);
85
185
  }
86
- createSummarySystemMessage(content) {
186
+ createSummarySystemMessage(content, summarizesFrom, summarizesTo) {
87
187
  return {
188
+ id: `summary-${randomUUID()}`,
88
189
  role: "system",
89
190
  content: `Summary of previous conversation turns:\n\n${content}`,
191
+ timestamp: new Date().toISOString(),
192
+ metadata: {
193
+ isSummary: true,
194
+ summarizesFrom,
195
+ summarizesTo,
196
+ },
90
197
  };
91
198
  }
92
- async _summarizeSession(session) {
93
- logger.info(`[ConversationMemory] Summarizing session ${session.sessionId}...`);
94
- const targetTurns = this.config.summarizationTargetTurns || 10;
95
- const splitIndex = Math.max(0, session.messages.length - targetTurns * MESSAGES_PER_TURN);
96
- const messagesToSummarize = session.messages.slice(0, splitIndex);
97
- const recentMessages = session.messages.slice(splitIndex);
199
+ /**
200
+ * Token-based summarization (pointer-based, non-destructive)
201
+ */
202
+ async summarizeSessionTokenBased(session, threshold) {
203
+ const startIndex = session.summarizedUpToMessageId
204
+ ? session.messages.findIndex((m) => m.id === session.summarizedUpToMessageId) + 1
205
+ : 0;
206
+ const recentMessages = session.messages.slice(startIndex);
207
+ if (recentMessages.length === 0) {
208
+ return;
209
+ }
210
+ const targetRecentTokens = threshold * RECENT_MESSAGES_RATIO;
211
+ const splitIndex = await this.findSplitIndexByTokens(recentMessages, targetRecentTokens);
212
+ const messagesToSummarize = recentMessages.slice(0, splitIndex);
98
213
  if (messagesToSummarize.length === 0) {
99
214
  return;
100
215
  }
101
- const summarizationPrompt = this._createSummarizationPrompt(messagesToSummarize);
102
- const summarizer = new NeuroLink({
103
- conversationMemory: { enabled: false },
216
+ const summary = await generateSummary(messagesToSummarize, this.config, "[ConversationMemory]", session.summarizedMessage);
217
+ if (!summary) {
218
+ logger.warn(`[ConversationMemory] Summary generation failed for session ${session.sessionId}`);
219
+ return;
220
+ }
221
+ const lastSummarized = messagesToSummarize[messagesToSummarize.length - 1];
222
+ session.summarizedUpToMessageId = lastSummarized.id;
223
+ session.summarizedMessage = summary; // Store summary separately
224
+ logger.info(`[ConversationMemory] Summarization complete for session ${session.sessionId}`, {
225
+ summarizedCount: messagesToSummarize.length,
226
+ totalMessages: session.messages.length,
227
+ pointer: session.summarizedUpToMessageId,
104
228
  });
105
- try {
106
- const providerName = this.config.summarizationProvider;
107
- // Map provider names to correct format
108
- let mappedProvider = providerName;
109
- if (providerName === "vertex") {
110
- mappedProvider = "googlevertex";
111
- }
112
- if (!mappedProvider) {
113
- logger.error(`[ConversationMemory] Missing summarization provider`);
114
- return;
115
- }
116
- logger.debug(`[ConversationMemory] Using provider: ${mappedProvider} for summarization`);
117
- const summaryResult = await summarizer.generate({
118
- input: { text: summarizationPrompt },
119
- provider: mappedProvider,
120
- model: this.config.summarizationModel,
121
- disableTools: true,
122
- });
123
- if (summaryResult.content) {
124
- session.messages = [
125
- this.createSummarySystemMessage(summaryResult.content),
126
- ...recentMessages,
127
- ];
128
- logger.info(`[ConversationMemory] Summarization complete for session ${session.sessionId}.`);
129
- }
130
- else {
131
- logger.warn(`[ConversationMemory] Summarization failed for session ${session.sessionId}. History not modified.`);
229
+ }
230
+ /**
231
+ * Find split index to keep recent messages within target token count
232
+ */
233
+ async findSplitIndexByTokens(messages, targetRecentTokens) {
234
+ let recentTokens = 0;
235
+ let splitIndex = messages.length;
236
+ for (let i = messages.length - 1; i >= 0; i--) {
237
+ const msgTokens = TokenUtils.estimateTokenCount(messages[i].content);
238
+ if (recentTokens + msgTokens > targetRecentTokens) {
239
+ splitIndex = i + 1;
240
+ break;
132
241
  }
242
+ recentTokens += msgTokens;
133
243
  }
134
- catch (error) {
135
- logger.error(`[ConversationMemory] Error during summarization for session ${session.sessionId}`, { error });
136
- }
137
- }
138
- _createSummarizationPrompt(history) {
139
- const formattedHistory = history
140
- .map((msg) => `${msg.role}: ${msg.content}`)
141
- .join("\n\n");
142
- return `
143
- You are a context summarization AI. Your task is to condense the following conversation history for another AI assistant.
144
- The summary must be a concise, third-person narrative that retains all critical information, including key entities, technical details, decisions made, and any specific dates or times mentioned.
145
- Ensure the summary flows logically and is ready to be used as context for the next turn in the conversation.
146
-
147
- Conversation History to Summarize:
148
- ---
149
- ${formattedHistory}
150
- ---
151
- `.trim();
244
+ // To ensure at least one message is summarized
245
+ return Math.max(1, splitIndex);
152
246
  }
153
247
  async ensureInitialized() {
154
248
  if (!this.isInitialized) {
@@ -2,7 +2,7 @@
2
2
  * Redis Conversation Memory Manager for NeuroLink
3
3
  * Redis-based implementation of conversation storage with same interface as ConversationMemoryManager
4
4
  */
5
- import type { ConversationMemoryConfig, ConversationMemoryStats, ChatMessage, RedisStorageConfig, SessionMetadata, RedisConversationObject } from "../types/conversation.js";
5
+ import type { ConversationMemoryConfig, ConversationMemoryStats, ChatMessage, RedisStorageConfig, SessionMetadata, RedisConversationObject, StoreConversationTurnOptions } from "../types/conversation.js";
6
6
  /**
7
7
  * Redis-based implementation of the ConversationMemoryManager
8
8
  * Uses the same interface but stores data in Redis
@@ -22,6 +22,11 @@ export declare class RedisConversationMemoryManager {
22
22
  * Key format: "${sessionId}:${userId}"
23
23
  */
24
24
  private titleGenerationInProgress;
25
+ /**
26
+ * Track sessions currently being summarized to prevent race conditions
27
+ * Key format: "${sessionId}:${userId}"
28
+ */
29
+ private summarizationInProgress;
25
30
  constructor(config: ConversationMemoryConfig, redisConfig?: RedisStorageConfig);
26
31
  /**
27
32
  * Initialize the memory manager with Redis connection
@@ -39,18 +44,10 @@ export declare class RedisConversationMemoryManager {
39
44
  * Remove a session from user's session set (private method)
40
45
  */
41
46
  private removeUserSession;
42
- /**
43
- * Generate next message ID for a conversation
44
- */
45
- private generateMessageId;
46
47
  /**
47
48
  * Generate current timestamp in ISO format
48
49
  */
49
50
  private generateTimestamp;
50
- /**
51
- * Generate a unique conversation ID using UUID v4
52
- */
53
- private generateUniqueId;
54
51
  /**
55
52
  * Store tool execution data for a session (temporarily to avoid race conditions)
56
53
  */
@@ -68,11 +65,29 @@ export declare class RedisConversationMemoryManager {
68
65
  /**
69
66
  * Store a conversation turn for a session
70
67
  */
71
- storeConversationTurn(sessionId: string, userId: string | undefined, userMessage: string, aiResponse: string, startTimeStamp: Date | undefined): Promise<void>;
68
+ storeConversationTurn(options: StoreConversationTurnOptions): Promise<void>;
69
+ /**
70
+ * Check if summarization is needed based on token count
71
+ */
72
+ private checkAndSummarize;
73
+ /**
74
+ * Estimate total tokens for a list of messages
75
+ */
76
+ private estimateTokens;
77
+ /**
78
+ * Token-based summarization (pointer-based, non-destructive)
79
+ */
80
+ private summarizeSessionTokenBased;
81
+ /**
82
+ * Find split index to keep recent messages within target token count
83
+ */
84
+ private findSplitIndexByTokens;
72
85
  /**
73
- * Build context messages for AI prompt injection
86
+ * Build context messages for AI prompt injection (TOKEN-BASED)
87
+ * Returns messages from pointer onwards (or all if no pointer)
88
+ * Filters out tool_call and tool_result messages when summarization is enabled
74
89
  */
75
- buildContextMessages(sessionId: string, userId?: string): Promise<ChatMessage[]>;
90
+ buildContextMessages(sessionId: string, userId?: string, enableSummarization?: boolean): Promise<ChatMessage[]>;
76
91
  /**
77
92
  * Get session metadata for a specific user session (optimized for listing)
78
93
  * Fetches only essential metadata without heavy message arrays
@@ -110,7 +125,7 @@ export declare class RedisConversationMemoryManager {
110
125
  /**
111
126
  * Create summary system message
112
127
  */
113
- createSummarySystemMessage(content: string): ChatMessage;
128
+ createSummarySystemMessage(content: string, summarizesFrom?: string, summarizesTo?: string): ChatMessage;
114
129
  /**
115
130
  * Close Redis connection
116
131
  */