@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.
- package/CHANGELOG.md +12 -0
- package/dist/cli/loop/optionsSchema.js +4 -0
- package/dist/config/conversationMemory.d.ts +15 -0
- package/dist/config/conversationMemory.js +22 -3
- package/dist/core/conversationMemoryFactory.js +0 -3
- package/dist/core/conversationMemoryInitializer.js +1 -9
- package/dist/core/conversationMemoryManager.d.ts +31 -8
- package/dist/core/conversationMemoryManager.js +174 -80
- package/dist/core/redisConversationMemoryManager.d.ts +28 -13
- package/dist/core/redisConversationMemoryManager.js +211 -121
- package/dist/lib/config/conversationMemory.d.ts +15 -0
- package/dist/lib/config/conversationMemory.js +22 -3
- package/dist/lib/core/conversationMemoryFactory.js +0 -3
- package/dist/lib/core/conversationMemoryInitializer.js +1 -9
- package/dist/lib/core/conversationMemoryManager.d.ts +31 -8
- package/dist/lib/core/conversationMemoryManager.js +174 -80
- package/dist/lib/core/redisConversationMemoryManager.d.ts +28 -13
- package/dist/lib/core/redisConversationMemoryManager.js +211 -121
- package/dist/lib/neurolink.js +29 -22
- package/dist/lib/types/conversation.d.ts +58 -9
- package/dist/lib/types/generateTypes.d.ts +1 -0
- package/dist/lib/types/sdkTypes.d.ts +1 -1
- package/dist/lib/types/streamTypes.d.ts +1 -0
- package/dist/lib/utils/conversationMemory.d.ts +43 -1
- package/dist/lib/utils/conversationMemory.js +181 -5
- package/dist/lib/utils/conversationMemoryUtils.js +16 -1
- package/dist/lib/utils/imageProcessor.d.ts +1 -0
- package/dist/lib/utils/imageProcessor.js +29 -1
- package/dist/lib/utils/redis.js +0 -5
- package/dist/neurolink.js +29 -22
- package/dist/types/conversation.d.ts +58 -9
- package/dist/types/generateTypes.d.ts +1 -0
- package/dist/types/sdkTypes.d.ts +1 -1
- package/dist/types/streamTypes.d.ts +1 -0
- package/dist/utils/conversationMemory.d.ts +43 -1
- package/dist/utils/conversationMemory.js +181 -5
- package/dist/utils/conversationMemoryUtils.js +16 -1
- package/dist/utils/imageProcessor.d.ts +1 -0
- package/dist/utils/imageProcessor.js +29 -1
- package/dist/utils/redis.js +0 -5
- package/package.json +1 -1
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
6
|
import { ConversationMemoryError } from "../types/conversation.js";
|
|
7
|
-
import { MESSAGES_PER_TURN } from "../config/conversationMemory.js";
|
|
7
|
+
import { MESSAGES_PER_TURN, RECENT_MESSAGES_RATIO, } from "../config/conversationMemory.js";
|
|
8
8
|
import { logger } from "../utils/logger.js";
|
|
9
9
|
import { NeuroLink } from "../neurolink.js";
|
|
10
10
|
import { createRedisClient, getSessionKey, getUserSessionsKey, getNormalizedConfig, serializeConversation, deserializeConversation, scanKeys, } from "../utils/redis.js";
|
|
11
|
+
import { TokenUtils } from "../constants/tokens.js";
|
|
12
|
+
import { buildContextFromPointer, getEffectiveTokenThreshold, generateSummary, } from "../utils/conversationMemory.js";
|
|
11
13
|
/**
|
|
12
14
|
* Redis-based implementation of the ConversationMemoryManager
|
|
13
15
|
* Uses the same interface but stores data in Redis
|
|
@@ -27,6 +29,11 @@ export class RedisConversationMemoryManager {
|
|
|
27
29
|
* Key format: "${sessionId}:${userId}"
|
|
28
30
|
*/
|
|
29
31
|
titleGenerationInProgress = new Set();
|
|
32
|
+
/**
|
|
33
|
+
* Track sessions currently being summarized to prevent race conditions
|
|
34
|
+
* Key format: "${sessionId}:${userId}"
|
|
35
|
+
*/
|
|
36
|
+
summarizationInProgress = new Set();
|
|
30
37
|
constructor(config, redisConfig = {}) {
|
|
31
38
|
this.config = config;
|
|
32
39
|
this.redisConfig = getNormalizedConfig(redisConfig);
|
|
@@ -138,25 +145,12 @@ export class RedisConversationMemoryManager {
|
|
|
138
145
|
return false;
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Generate next message ID for a conversation
|
|
143
|
-
*/
|
|
144
|
-
generateMessageId(conversation) {
|
|
145
|
-
const currentCount = conversation?.messages?.length || 0;
|
|
146
|
-
return `msg_${currentCount + 1}`;
|
|
147
|
-
}
|
|
148
148
|
/**
|
|
149
149
|
* Generate current timestamp in ISO format
|
|
150
150
|
*/
|
|
151
151
|
generateTimestamp() {
|
|
152
152
|
return new Date().toISOString();
|
|
153
153
|
}
|
|
154
|
-
/**
|
|
155
|
-
* Generate a unique conversation ID using UUID v4
|
|
156
|
-
*/
|
|
157
|
-
generateUniqueId() {
|
|
158
|
-
return randomUUID();
|
|
159
|
-
}
|
|
160
154
|
/**
|
|
161
155
|
* Store tool execution data for a session (temporarily to avoid race conditions)
|
|
162
156
|
*/
|
|
@@ -224,49 +218,31 @@ export class RedisConversationMemoryManager {
|
|
|
224
218
|
/**
|
|
225
219
|
* Store a conversation turn for a session
|
|
226
220
|
*/
|
|
227
|
-
async storeConversationTurn(
|
|
221
|
+
async storeConversationTurn(options) {
|
|
228
222
|
logger.debug("[RedisConversationMemoryManager] Storing conversation turn", {
|
|
229
|
-
sessionId,
|
|
230
|
-
userId,
|
|
231
|
-
userMessageLength: userMessage.length,
|
|
232
|
-
aiResponseLength: aiResponse.length,
|
|
223
|
+
sessionId: options.sessionId,
|
|
224
|
+
userId: options.userId,
|
|
233
225
|
});
|
|
234
226
|
await this.ensureInitialized();
|
|
235
227
|
try {
|
|
236
228
|
if (!this.redisClient) {
|
|
237
229
|
throw new Error("Redis client not initialized");
|
|
238
230
|
}
|
|
239
|
-
|
|
240
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
241
|
-
// Get existing conversation object
|
|
231
|
+
const redisKey = getSessionKey(this.redisConfig, options.sessionId, options.userId);
|
|
242
232
|
const conversationData = await this.redisClient.get(redisKey);
|
|
243
233
|
let conversation = deserializeConversation(conversationData);
|
|
244
234
|
const currentTime = new Date().toISOString();
|
|
245
|
-
const normalizedUserId = userId || "randomUser";
|
|
246
|
-
// If no existing conversation, create a new one
|
|
235
|
+
const normalizedUserId = options.userId || "randomUser";
|
|
247
236
|
if (!conversation) {
|
|
248
|
-
|
|
249
|
-
const titleGenerationKey = `${sessionId}:${normalizedUserId}`;
|
|
237
|
+
const titleGenerationKey = `${options.sessionId}:${normalizedUserId}`;
|
|
250
238
|
setImmediate(async () => {
|
|
251
|
-
// Check if title generation is already in progress for this session
|
|
252
239
|
if (this.titleGenerationInProgress.has(titleGenerationKey)) {
|
|
253
|
-
logger.debug("[RedisConversationMemoryManager] Title generation already in progress, skipping", {
|
|
254
|
-
sessionId,
|
|
255
|
-
userId: normalizedUserId,
|
|
256
|
-
titleGenerationKey,
|
|
257
|
-
});
|
|
258
240
|
return;
|
|
259
241
|
}
|
|
260
|
-
// Mark title generation as in progress
|
|
261
242
|
this.titleGenerationInProgress.add(titleGenerationKey);
|
|
262
243
|
try {
|
|
263
|
-
const title = await this.generateConversationTitle(userMessage);
|
|
264
|
-
|
|
265
|
-
sessionId,
|
|
266
|
-
userId: normalizedUserId,
|
|
267
|
-
title,
|
|
268
|
-
});
|
|
269
|
-
const updatedRedisKey = getSessionKey(this.redisConfig, sessionId, userId || undefined);
|
|
244
|
+
const title = await this.generateConversationTitle(options.userMessage);
|
|
245
|
+
const updatedRedisKey = getSessionKey(this.redisConfig, options.sessionId, options.userId || undefined);
|
|
270
246
|
const updatedConversationData = await this.redisClient?.get(updatedRedisKey);
|
|
271
247
|
const updatedConversation = deserializeConversation(updatedConversationData || null);
|
|
272
248
|
if (updatedConversation) {
|
|
@@ -281,7 +257,7 @@ export class RedisConversationMemoryManager {
|
|
|
281
257
|
}
|
|
282
258
|
catch (titleError) {
|
|
283
259
|
logger.warn("[RedisConversationMemoryManager] Failed to generate conversation title in background", {
|
|
284
|
-
sessionId,
|
|
260
|
+
sessionId: options.sessionId,
|
|
285
261
|
userId: normalizedUserId,
|
|
286
262
|
error: titleError instanceof Error
|
|
287
263
|
? titleError.message
|
|
@@ -289,136 +265,243 @@ export class RedisConversationMemoryManager {
|
|
|
289
265
|
});
|
|
290
266
|
}
|
|
291
267
|
finally {
|
|
292
|
-
// Always remove from tracking set when done (success or failure)
|
|
293
268
|
this.titleGenerationInProgress.delete(titleGenerationKey);
|
|
294
|
-
logger.debug("[RedisConversationMemoryManager] Title generation completed, removed from tracking", {
|
|
295
|
-
sessionId,
|
|
296
|
-
userId: normalizedUserId,
|
|
297
|
-
titleGenerationKey,
|
|
298
|
-
remainingInProgress: this.titleGenerationInProgress.size,
|
|
299
|
-
});
|
|
300
269
|
}
|
|
301
270
|
});
|
|
302
271
|
conversation = {
|
|
303
|
-
id:
|
|
272
|
+
id: randomUUID(),
|
|
304
273
|
title: "New Conversation", // Temporary title until generated
|
|
305
|
-
sessionId,
|
|
274
|
+
sessionId: options.sessionId,
|
|
306
275
|
userId: normalizedUserId,
|
|
307
|
-
createdAt: startTimeStamp?.toISOString() || currentTime,
|
|
308
|
-
updatedAt: startTimeStamp?.toISOString() || currentTime,
|
|
276
|
+
createdAt: options.startTimeStamp?.toISOString() || currentTime,
|
|
277
|
+
updatedAt: options.startTimeStamp?.toISOString() || currentTime,
|
|
309
278
|
messages: [],
|
|
310
279
|
};
|
|
311
280
|
}
|
|
312
281
|
else {
|
|
313
|
-
// Update existing conversation timestamp
|
|
314
282
|
conversation.updatedAt = currentTime;
|
|
315
283
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
sessionId: conversation.sessionId,
|
|
320
|
-
userId: conversation.userId,
|
|
321
|
-
});
|
|
322
|
-
// Add new messages to conversation history with new format
|
|
284
|
+
const tokenThreshold = options.providerDetails
|
|
285
|
+
? getEffectiveTokenThreshold(options.providerDetails.provider, options.providerDetails.model, this.config.tokenThreshold, conversation.tokenThreshold)
|
|
286
|
+
: this.config.tokenThreshold || 50000;
|
|
323
287
|
const userMsg = {
|
|
324
|
-
id:
|
|
325
|
-
timestamp: startTimeStamp?.toISOString() || this.generateTimestamp(),
|
|
288
|
+
id: randomUUID(),
|
|
289
|
+
timestamp: options.startTimeStamp?.toISOString() || this.generateTimestamp(),
|
|
326
290
|
role: "user",
|
|
327
|
-
content: userMessage,
|
|
291
|
+
content: options.userMessage,
|
|
328
292
|
};
|
|
329
293
|
conversation.messages.push(userMsg);
|
|
330
|
-
await this.flushPendingToolData(conversation, sessionId, normalizedUserId);
|
|
294
|
+
await this.flushPendingToolData(conversation, options.sessionId, normalizedUserId);
|
|
331
295
|
const assistantMsg = {
|
|
332
|
-
id:
|
|
296
|
+
id: randomUUID(),
|
|
333
297
|
timestamp: this.generateTimestamp(),
|
|
334
298
|
role: "assistant",
|
|
335
|
-
content: aiResponse,
|
|
299
|
+
content: options.aiResponse,
|
|
336
300
|
};
|
|
337
301
|
conversation.messages.push(assistantMsg);
|
|
338
302
|
logger.info("[RedisConversationMemoryManager] Added new messages", {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
{
|
|
342
|
-
role: conversation.messages[conversation.messages.length - 2]?.role,
|
|
343
|
-
contentLength: conversation.messages[conversation.messages.length - 2]?.content
|
|
344
|
-
.length,
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
role: conversation.messages[conversation.messages.length - 1]?.role,
|
|
348
|
-
contentLength: conversation.messages[conversation.messages.length - 1]?.content
|
|
349
|
-
.length,
|
|
350
|
-
},
|
|
351
|
-
],
|
|
303
|
+
sessionId: conversation.sessionId,
|
|
304
|
+
userId: conversation.userId,
|
|
352
305
|
});
|
|
353
|
-
//
|
|
306
|
+
// Use per-request enableSummarization with higher priority than instance config
|
|
307
|
+
const shouldSummarize = options.enableSummarization !== undefined
|
|
308
|
+
? options.enableSummarization
|
|
309
|
+
: this.config.enableSummarization;
|
|
310
|
+
if (shouldSummarize) {
|
|
311
|
+
const normalizedUserId = options.userId || "randomUser";
|
|
312
|
+
const summarizationKey = `${options.sessionId}:${normalizedUserId}`;
|
|
313
|
+
// Only trigger summarization if not already in progress for this session
|
|
314
|
+
if (!this.summarizationInProgress.has(summarizationKey)) {
|
|
315
|
+
setImmediate(async () => {
|
|
316
|
+
try {
|
|
317
|
+
await this.checkAndSummarize(conversation, tokenThreshold, options.sessionId, options.userId);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
logger.error("Background summarization failed", {
|
|
321
|
+
sessionId: conversation.sessionId,
|
|
322
|
+
error: error instanceof Error ? error.message : String(error),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
logger.debug("[RedisConversationMemoryManager] Summarization already in progress, skipping", {
|
|
329
|
+
sessionId: options.sessionId,
|
|
330
|
+
userId: normalizedUserId,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
354
334
|
const serializedData = serializeConversation(conversation);
|
|
355
|
-
logger.debug("[RedisConversationMemoryManager] Saving conversation to Redis", {
|
|
356
|
-
redisKey,
|
|
357
|
-
messageCount: conversation.messages.length,
|
|
358
|
-
serializedDataLength: serializedData.length,
|
|
359
|
-
title: conversation.title,
|
|
360
|
-
});
|
|
361
|
-
logger.info("Storing conversation data to Redis", {
|
|
362
|
-
sessionId,
|
|
363
|
-
dataLength: serializedData.length,
|
|
364
|
-
messageCount: conversation.messages.length,
|
|
365
|
-
});
|
|
366
335
|
await this.redisClient.set(redisKey, serializedData);
|
|
367
|
-
// Set TTL if configured
|
|
368
336
|
if (this.redisConfig.ttl > 0) {
|
|
369
|
-
logger.debug("[RedisConversationMemoryManager] Setting Redis TTL", {
|
|
370
|
-
redisKey,
|
|
371
|
-
ttl: this.redisConfig.ttl,
|
|
372
|
-
});
|
|
373
337
|
await this.redisClient.expire(redisKey, this.redisConfig.ttl);
|
|
374
338
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
await this.addUserSession(userId, sessionId);
|
|
339
|
+
if (options.userId) {
|
|
340
|
+
await this.addUserSession(options.userId, options.sessionId);
|
|
378
341
|
}
|
|
379
342
|
logger.debug("[RedisConversationMemoryManager] Successfully stored conversation turn", {
|
|
380
|
-
sessionId,
|
|
343
|
+
sessionId: options.sessionId,
|
|
381
344
|
totalMessages: conversation.messages.length,
|
|
382
345
|
title: conversation.title,
|
|
383
346
|
});
|
|
384
347
|
}
|
|
385
348
|
catch (error) {
|
|
386
|
-
throw new ConversationMemoryError(`Failed to store conversation turn in Redis for session ${sessionId}`, "STORAGE_ERROR", {
|
|
349
|
+
throw new ConversationMemoryError(`Failed to store conversation turn in Redis for session ${options.sessionId}`, "STORAGE_ERROR", {
|
|
350
|
+
sessionId: options.sessionId,
|
|
351
|
+
error: error instanceof Error ? error.message : String(error),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Check if summarization is needed based on token count
|
|
357
|
+
*/
|
|
358
|
+
async checkAndSummarize(conversation, threshold, sessionId, userId) {
|
|
359
|
+
const normalizedUserId = userId || "randomUser";
|
|
360
|
+
const summarizationKey = `${sessionId}:${normalizedUserId}`;
|
|
361
|
+
// Acquire lock - if already in progress, skip
|
|
362
|
+
if (this.summarizationInProgress.has(summarizationKey)) {
|
|
363
|
+
logger.debug("[RedisConversationMemoryManager] Summarization already in progress, skipping", {
|
|
387
364
|
sessionId,
|
|
365
|
+
userId: normalizedUserId,
|
|
366
|
+
});
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
this.summarizationInProgress.add(summarizationKey);
|
|
370
|
+
try {
|
|
371
|
+
const session = {
|
|
372
|
+
sessionId: conversation.sessionId,
|
|
373
|
+
userId: conversation.userId,
|
|
374
|
+
messages: conversation.messages,
|
|
375
|
+
summarizedUpToMessageId: conversation.summarizedUpToMessageId,
|
|
376
|
+
summarizedMessage: conversation.summarizedMessage,
|
|
377
|
+
tokenThreshold: conversation.tokenThreshold,
|
|
378
|
+
lastTokenCount: conversation.lastTokenCount,
|
|
379
|
+
lastCountedAt: conversation.lastCountedAt,
|
|
380
|
+
createdAt: new Date(conversation.createdAt).getTime(),
|
|
381
|
+
lastActivity: new Date(conversation.updatedAt).getTime(),
|
|
382
|
+
};
|
|
383
|
+
const contextMessages = buildContextFromPointer(session);
|
|
384
|
+
const tokenCount = this.estimateTokens(contextMessages);
|
|
385
|
+
conversation.lastTokenCount = tokenCount;
|
|
386
|
+
conversation.lastCountedAt = Date.now();
|
|
387
|
+
if (tokenCount >= threshold) {
|
|
388
|
+
await this.summarizeSessionTokenBased(conversation, threshold, sessionId, userId);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
logger.error("Token counting or summarization failed", {
|
|
393
|
+
sessionId: conversation.sessionId,
|
|
388
394
|
error: error instanceof Error ? error.message : String(error),
|
|
389
395
|
});
|
|
390
396
|
}
|
|
397
|
+
finally {
|
|
398
|
+
// Release lock when done
|
|
399
|
+
this.summarizationInProgress.delete(summarizationKey);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Estimate total tokens for a list of messages
|
|
404
|
+
*/
|
|
405
|
+
estimateTokens(messages) {
|
|
406
|
+
return messages.reduce((total, msg) => {
|
|
407
|
+
return total + TokenUtils.estimateTokenCount(msg.content);
|
|
408
|
+
}, 0);
|
|
391
409
|
}
|
|
392
410
|
/**
|
|
393
|
-
*
|
|
411
|
+
* Token-based summarization (pointer-based, non-destructive)
|
|
394
412
|
*/
|
|
395
|
-
async
|
|
413
|
+
async summarizeSessionTokenBased(conversation, threshold, sessionId, userId) {
|
|
414
|
+
const startIndex = conversation.summarizedUpToMessageId
|
|
415
|
+
? conversation.messages.findIndex((m) => m.id === conversation.summarizedUpToMessageId) + 1
|
|
416
|
+
: 0;
|
|
417
|
+
const recentMessages = conversation.messages.slice(startIndex);
|
|
418
|
+
if (recentMessages.length === 0) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// We only want to include user, assistant, and system messages in summarization
|
|
422
|
+
const filteredRecentMessages = recentMessages.filter((msg) => msg.role !== "tool_call" && msg.role !== "tool_result");
|
|
423
|
+
const targetRecentTokens = threshold * RECENT_MESSAGES_RATIO;
|
|
424
|
+
const splitIndex = await this.findSplitIndexByTokens(filteredRecentMessages, targetRecentTokens);
|
|
425
|
+
const messagesToSummarize = filteredRecentMessages.slice(0, splitIndex);
|
|
426
|
+
if (messagesToSummarize.length === 0) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const summary = await generateSummary(messagesToSummarize, this.config, "[RedisConversationMemoryManager]", conversation.summarizedMessage);
|
|
430
|
+
if (!summary) {
|
|
431
|
+
logger.warn(`[RedisConversationMemoryManager] Summary generation failed for session ${conversation.sessionId}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const lastSummarized = messagesToSummarize[messagesToSummarize.length - 1];
|
|
435
|
+
conversation.summarizedUpToMessageId = lastSummarized.id;
|
|
436
|
+
conversation.summarizedMessage = summary;
|
|
437
|
+
if (this.redisClient) {
|
|
438
|
+
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
439
|
+
const serializedData = serializeConversation(conversation);
|
|
440
|
+
await this.redisClient.set(redisKey, serializedData);
|
|
441
|
+
if (this.redisConfig.ttl > 0) {
|
|
442
|
+
await this.redisClient.expire(redisKey, this.redisConfig.ttl);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Find split index to keep recent messages within target token count
|
|
448
|
+
*/
|
|
449
|
+
async findSplitIndexByTokens(messages, targetRecentTokens) {
|
|
450
|
+
let recentTokens = 0;
|
|
451
|
+
let splitIndex = messages.length;
|
|
452
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
453
|
+
const msgTokens = TokenUtils.estimateTokenCount(messages[i].content);
|
|
454
|
+
if (recentTokens + msgTokens > targetRecentTokens) {
|
|
455
|
+
splitIndex = i + 1;
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
recentTokens += msgTokens;
|
|
459
|
+
}
|
|
460
|
+
// Ensure we're summarizing at least something
|
|
461
|
+
return Math.max(1, splitIndex);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Build context messages for AI prompt injection (TOKEN-BASED)
|
|
465
|
+
* Returns messages from pointer onwards (or all if no pointer)
|
|
466
|
+
* Filters out tool_call and tool_result messages when summarization is enabled
|
|
467
|
+
*/
|
|
468
|
+
async buildContextMessages(sessionId, userId, enableSummarization) {
|
|
396
469
|
logger.info("[RedisConversationMemoryManager] Building context messages", {
|
|
397
470
|
sessionId,
|
|
398
471
|
userId,
|
|
399
472
|
method: "buildContextMessages",
|
|
400
473
|
});
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
userId,
|
|
406
|
-
});
|
|
474
|
+
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
475
|
+
const conversationData = await this.redisClient?.get(redisKey);
|
|
476
|
+
const conversation = deserializeConversation(conversationData || null);
|
|
477
|
+
if (!conversation) {
|
|
407
478
|
return [];
|
|
408
479
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
480
|
+
const session = {
|
|
481
|
+
sessionId: conversation.sessionId,
|
|
482
|
+
userId: conversation.userId,
|
|
483
|
+
messages: conversation.messages,
|
|
484
|
+
summarizedUpToMessageId: conversation.summarizedUpToMessageId,
|
|
485
|
+
summarizedMessage: conversation.summarizedMessage,
|
|
486
|
+
tokenThreshold: conversation.tokenThreshold,
|
|
487
|
+
lastTokenCount: conversation.lastTokenCount,
|
|
488
|
+
lastCountedAt: conversation.lastCountedAt,
|
|
489
|
+
createdAt: new Date(conversation.createdAt).getTime(),
|
|
490
|
+
lastActivity: new Date(conversation.updatedAt).getTime(),
|
|
491
|
+
};
|
|
492
|
+
const contextMessages = buildContextFromPointer(session);
|
|
493
|
+
const isSummarizationEnabled = enableSummarization !== undefined
|
|
494
|
+
? enableSummarization
|
|
495
|
+
: this.config.enableSummarization === true;
|
|
496
|
+
let finalMessages = contextMessages;
|
|
497
|
+
if (isSummarizationEnabled) {
|
|
498
|
+
finalMessages = contextMessages.filter((msg) => msg.role !== "tool_call" && msg.role !== "tool_result");
|
|
499
|
+
}
|
|
413
500
|
logger.info("[RedisConversationMemoryManager] Retrieved context messages", {
|
|
414
501
|
sessionId,
|
|
415
502
|
userId,
|
|
416
|
-
messageCount: messages.length,
|
|
417
|
-
messageRoles: messages.map((m) => m.role),
|
|
418
|
-
firstMessagePreview: messages[0]?.content?.substring(0, 50),
|
|
419
|
-
lastMessagePreview: messages[messages.length - 1]?.content?.substring(0, 50),
|
|
420
503
|
});
|
|
421
|
-
return
|
|
504
|
+
return finalMessages;
|
|
422
505
|
}
|
|
423
506
|
/**
|
|
424
507
|
* Get session metadata for a specific user session (optimized for listing)
|
|
@@ -649,10 +732,17 @@ User message: "${userMessage}`;
|
|
|
649
732
|
/**
|
|
650
733
|
* Create summary system message
|
|
651
734
|
*/
|
|
652
|
-
createSummarySystemMessage(content) {
|
|
735
|
+
createSummarySystemMessage(content, summarizesFrom, summarizesTo) {
|
|
653
736
|
return {
|
|
737
|
+
id: `summary-${randomUUID()}`,
|
|
654
738
|
role: "system",
|
|
655
739
|
content: `Summary of previous conversation turns:\n\n${content}`,
|
|
740
|
+
timestamp: new Date().toISOString(),
|
|
741
|
+
metadata: {
|
|
742
|
+
isSummary: true,
|
|
743
|
+
summarizesFrom,
|
|
744
|
+
summarizesTo,
|
|
745
|
+
},
|
|
656
746
|
};
|
|
657
747
|
}
|
|
658
748
|
/**
|
|
@@ -876,7 +966,7 @@ User message: "${userMessage}`;
|
|
|
876
966
|
// Store in mapping for tool results
|
|
877
967
|
toolCallMap.set(toolCallId, toolName);
|
|
878
968
|
const toolCallMessage = {
|
|
879
|
-
id:
|
|
969
|
+
id: randomUUID(),
|
|
880
970
|
timestamp: toolCall.timestamp?.toISOString() || this.generateTimestamp(),
|
|
881
971
|
role: "tool_call",
|
|
882
972
|
content: "", // Can be empty for tool calls
|
|
@@ -893,7 +983,7 @@ User message: "${userMessage}`;
|
|
|
893
983
|
const toolCallId = String(toolResult.toolCallId || toolResult.id || "unknown");
|
|
894
984
|
const toolName = toolCallMap.get(toolCallId) || "unknown";
|
|
895
985
|
const toolResultMessage = {
|
|
896
|
-
id:
|
|
986
|
+
id: randomUUID(),
|
|
897
987
|
timestamp: toolResult.timestamp?.toISOString() || this.generateTimestamp(),
|
|
898
988
|
role: "tool_result",
|
|
899
989
|
content: "", // Can be empty for tool results
|
package/dist/lib/neurolink.js
CHANGED
|
@@ -2089,12 +2089,22 @@ Current user's request: ${currentInput}`;
|
|
|
2089
2089
|
if (self.conversationMemory && enhancedOptions.context?.sessionId) {
|
|
2090
2090
|
const sessionId = enhancedOptions.context?.sessionId;
|
|
2091
2091
|
const userId = enhancedOptions.context?.userId;
|
|
2092
|
+
let providerDetails = undefined;
|
|
2093
|
+
if (enhancedOptions.model) {
|
|
2094
|
+
providerDetails = {
|
|
2095
|
+
provider: providerName,
|
|
2096
|
+
model: enhancedOptions.model,
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2092
2099
|
try {
|
|
2093
|
-
await self.conversationMemory.storeConversationTurn(
|
|
2094
|
-
logger.debug("Stream conversation turn stored", {
|
|
2100
|
+
await self.conversationMemory.storeConversationTurn({
|
|
2095
2101
|
sessionId,
|
|
2096
|
-
|
|
2097
|
-
|
|
2102
|
+
userId,
|
|
2103
|
+
userMessage: originalPrompt ?? "",
|
|
2104
|
+
aiResponse: accumulatedContent,
|
|
2105
|
+
startTimeStamp: new Date(startTime),
|
|
2106
|
+
providerDetails,
|
|
2107
|
+
enableSummarization: enhancedOptions.enableSummarization,
|
|
2098
2108
|
});
|
|
2099
2109
|
}
|
|
2100
2110
|
catch (error) {
|
|
@@ -2194,6 +2204,7 @@ Current user's request: ${currentInput}`;
|
|
|
2194
2204
|
const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
|
|
2195
2205
|
// Get conversation messages for context
|
|
2196
2206
|
const conversationMessages = await getConversationMessages(this.conversationMemory, {
|
|
2207
|
+
...options,
|
|
2197
2208
|
prompt: options.input.text,
|
|
2198
2209
|
context: options.context,
|
|
2199
2210
|
});
|
|
@@ -2297,12 +2308,22 @@ Current user's request: ${currentInput}`;
|
|
|
2297
2308
|
const sessionId = enhancedOptions?.context?.sessionId;
|
|
2298
2309
|
const userId = enhancedOptions?.context
|
|
2299
2310
|
?.userId;
|
|
2311
|
+
let providerDetails = undefined;
|
|
2312
|
+
if (options.model) {
|
|
2313
|
+
providerDetails = {
|
|
2314
|
+
provider: providerName,
|
|
2315
|
+
model: options.model,
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2300
2318
|
try {
|
|
2301
|
-
await self.conversationMemory.storeConversationTurn(
|
|
2302
|
-
logger.debug("Fallback stream conversation turn stored", {
|
|
2319
|
+
await self.conversationMemory.storeConversationTurn({
|
|
2303
2320
|
sessionId: sessionId || options.context?.sessionId,
|
|
2304
|
-
|
|
2305
|
-
|
|
2321
|
+
userId: userId || options.context?.userId,
|
|
2322
|
+
userMessage: originalPrompt ?? "",
|
|
2323
|
+
aiResponse: fallbackAccumulatedContent,
|
|
2324
|
+
startTimeStamp: new Date(startTime),
|
|
2325
|
+
providerDetails,
|
|
2326
|
+
enableSummarization: enhancedOptions?.enableSummarization,
|
|
2306
2327
|
});
|
|
2307
2328
|
}
|
|
2308
2329
|
catch (error) {
|
|
@@ -4231,23 +4252,9 @@ Current user's request: ${currentInput}`;
|
|
|
4231
4252
|
// Import the integration module
|
|
4232
4253
|
const { initializeConversationMemory } = await import("./core/conversationMemoryInitializer.js");
|
|
4233
4254
|
// Use the integration module to create the appropriate memory manager
|
|
4234
|
-
const memoryManagerCreateStartTime = process.hrtime.bigint();
|
|
4235
4255
|
const memoryManager = await initializeConversationMemory(this.conversationMemoryConfig);
|
|
4236
4256
|
// Assign to conversationMemory with proper type to handle both memory manager types
|
|
4237
4257
|
this.conversationMemory = memoryManager;
|
|
4238
|
-
const memoryManagerCreateEndTime = process.hrtime.bigint();
|
|
4239
|
-
const memoryManagerCreateDurationNs = memoryManagerCreateEndTime - memoryManagerCreateStartTime;
|
|
4240
|
-
logger.info(`[NeuroLink] ✅ LOG_POINT_G004_MEMORY_LAZY_INIT_SUCCESS`, {
|
|
4241
|
-
logPoint: "G004_MEMORY_LAZY_INIT_SUCCESS",
|
|
4242
|
-
generateInternalId,
|
|
4243
|
-
timestamp: new Date().toISOString(),
|
|
4244
|
-
elapsedMs: Date.now() - generateInternalStartTime,
|
|
4245
|
-
elapsedNs: (process.hrtime.bigint() - generateInternalHrTimeStart).toString(),
|
|
4246
|
-
memoryManagerCreateDurationNs: memoryManagerCreateDurationNs.toString(),
|
|
4247
|
-
memoryManagerCreateDurationMs: Number(memoryManagerCreateDurationNs) / 1000000,
|
|
4248
|
-
storageType: process.env.STORAGE_TYPE || "memory",
|
|
4249
|
-
message: "Lazy conversation memory initialization completed successfully",
|
|
4250
|
-
});
|
|
4251
4258
|
// Reset the lazy init flag since we've now initialized
|
|
4252
4259
|
this.conversationMemoryNeedsInit = false;
|
|
4253
4260
|
}
|