@juspay/neurolink 7.33.4 → 7.34.0

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 (93) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +37 -0
  3. package/dist/cli/errorHandler.d.ts +1 -0
  4. package/dist/cli/errorHandler.js +28 -0
  5. package/dist/cli/factories/commandFactory.d.ts +23 -0
  6. package/dist/cli/factories/commandFactory.js +375 -60
  7. package/dist/cli/factories/ollamaCommandFactory.js +7 -1
  8. package/dist/cli/index.d.ts +1 -1
  9. package/dist/cli/index.js +9 -164
  10. package/dist/cli/loop/optionsSchema.d.ts +15 -0
  11. package/dist/cli/loop/optionsSchema.js +59 -0
  12. package/dist/cli/loop/session.d.ts +15 -0
  13. package/dist/cli/loop/session.js +252 -0
  14. package/dist/cli/parser.d.ts +1 -0
  15. package/dist/cli/parser.js +158 -0
  16. package/dist/cli/utils/ollamaUtils.js +6 -0
  17. package/dist/config/{conversationMemoryConfig.d.ts → conversationMemory.d.ts} +1 -1
  18. package/dist/core/baseProvider.js +17 -3
  19. package/dist/core/conversationMemoryFactory.d.ts +23 -0
  20. package/dist/core/conversationMemoryFactory.js +144 -0
  21. package/dist/core/conversationMemoryInitializer.d.ts +14 -0
  22. package/dist/core/conversationMemoryInitializer.js +127 -0
  23. package/dist/core/conversationMemoryManager.d.ts +3 -2
  24. package/dist/core/conversationMemoryManager.js +4 -3
  25. package/dist/core/redisConversationMemoryManager.d.ts +73 -0
  26. package/dist/core/redisConversationMemoryManager.js +483 -0
  27. package/dist/core/types.d.ts +1 -1
  28. package/dist/lib/config/{conversationMemoryConfig.d.ts → conversationMemory.d.ts} +1 -1
  29. package/dist/lib/core/baseProvider.js +17 -3
  30. package/dist/lib/core/conversationMemoryFactory.d.ts +23 -0
  31. package/dist/lib/core/conversationMemoryFactory.js +144 -0
  32. package/dist/lib/core/conversationMemoryInitializer.d.ts +14 -0
  33. package/dist/lib/core/conversationMemoryInitializer.js +127 -0
  34. package/dist/lib/core/conversationMemoryManager.d.ts +3 -2
  35. package/dist/lib/core/conversationMemoryManager.js +4 -3
  36. package/dist/lib/core/redisConversationMemoryManager.d.ts +73 -0
  37. package/dist/lib/core/redisConversationMemoryManager.js +483 -0
  38. package/dist/lib/core/types.d.ts +1 -1
  39. package/dist/lib/neurolink.d.ts +15 -9
  40. package/dist/lib/neurolink.js +218 -67
  41. package/dist/lib/providers/amazonBedrock.d.ts +4 -4
  42. package/dist/lib/providers/anthropic.d.ts +4 -4
  43. package/dist/lib/providers/azureOpenai.d.ts +4 -4
  44. package/dist/lib/providers/googleAiStudio.d.ts +4 -4
  45. package/dist/lib/providers/googleAiStudio.js +1 -1
  46. package/dist/lib/providers/huggingFace.d.ts +4 -4
  47. package/dist/lib/providers/litellm.d.ts +1 -1
  48. package/dist/lib/providers/mistral.d.ts +4 -4
  49. package/dist/lib/providers/mistral.js +2 -2
  50. package/dist/lib/providers/openAI.d.ts +4 -4
  51. package/dist/lib/session/globalSessionState.d.ts +27 -0
  52. package/dist/lib/session/globalSessionState.js +77 -0
  53. package/dist/lib/types/{conversationTypes.d.ts → conversation.d.ts} +32 -0
  54. package/dist/lib/types/generateTypes.d.ts +1 -1
  55. package/dist/lib/types/streamTypes.d.ts +1 -1
  56. package/dist/lib/utils/conversationMemory.d.ts +22 -0
  57. package/dist/lib/utils/conversationMemory.js +121 -0
  58. package/dist/lib/utils/conversationMemoryUtils.d.ts +1 -1
  59. package/dist/lib/utils/conversationMemoryUtils.js +2 -2
  60. package/dist/lib/utils/messageBuilder.d.ts +1 -1
  61. package/dist/lib/utils/messageBuilder.js +1 -1
  62. package/dist/lib/utils/redis.d.ts +42 -0
  63. package/dist/lib/utils/redis.js +263 -0
  64. package/dist/neurolink.d.ts +15 -9
  65. package/dist/neurolink.js +218 -67
  66. package/dist/providers/amazonBedrock.d.ts +4 -4
  67. package/dist/providers/anthropic.d.ts +4 -4
  68. package/dist/providers/azureOpenai.d.ts +4 -4
  69. package/dist/providers/googleAiStudio.d.ts +4 -4
  70. package/dist/providers/googleAiStudio.js +1 -1
  71. package/dist/providers/huggingFace.d.ts +4 -4
  72. package/dist/providers/litellm.d.ts +1 -1
  73. package/dist/providers/mistral.d.ts +4 -4
  74. package/dist/providers/mistral.js +2 -2
  75. package/dist/providers/openAI.d.ts +4 -4
  76. package/dist/session/globalSessionState.d.ts +27 -0
  77. package/dist/session/globalSessionState.js +77 -0
  78. package/dist/types/{conversationTypes.d.ts → conversation.d.ts} +32 -0
  79. package/dist/types/generateTypes.d.ts +1 -1
  80. package/dist/types/streamTypes.d.ts +1 -1
  81. package/dist/utils/conversationMemory.d.ts +22 -0
  82. package/dist/utils/conversationMemory.js +121 -0
  83. package/dist/utils/conversationMemoryUtils.d.ts +1 -1
  84. package/dist/utils/conversationMemoryUtils.js +2 -2
  85. package/dist/utils/messageBuilder.d.ts +1 -1
  86. package/dist/utils/messageBuilder.js +1 -1
  87. package/dist/utils/redis.d.ts +42 -0
  88. package/dist/utils/redis.js +263 -0
  89. package/package.json +3 -1
  90. /package/dist/config/{conversationMemoryConfig.js → conversationMemory.js} +0 -0
  91. /package/dist/lib/config/{conversationMemoryConfig.js → conversationMemory.js} +0 -0
  92. /package/dist/lib/types/{conversationTypes.js → conversation.js} +0 -0
  93. /package/dist/types/{conversationTypes.js → conversation.js} +0 -0
@@ -0,0 +1,483 @@
1
+ /**
2
+ * Redis Conversation Memory Manager for NeuroLink
3
+ * Redis-based implementation of conversation storage with same interface as ConversationMemoryManager
4
+ */
5
+ import { ConversationMemoryError } from "../types/conversation.js";
6
+ import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
7
+ import { logger } from "../utils/logger.js";
8
+ import { NeuroLink } from "../neurolink.js";
9
+ import { createRedisClient, getSessionKey, getNormalizedConfig, serializeMessages, deserializeMessages, scanKeys, } from "../utils/redis.js";
10
+ /**
11
+ * Redis-based implementation of the ConversationMemoryManager
12
+ * Uses the same interface but stores data in Redis
13
+ */
14
+ export class RedisConversationMemoryManager {
15
+ config;
16
+ isInitialized = false;
17
+ redisConfig;
18
+ redisClient = null;
19
+ constructor(config, redisConfig = {}) {
20
+ this.config = config;
21
+ this.redisConfig = getNormalizedConfig(redisConfig);
22
+ }
23
+ /**
24
+ * Initialize the memory manager with Redis connection
25
+ */
26
+ async initialize() {
27
+ if (this.isInitialized) {
28
+ logger.debug("[RedisConversationMemoryManager] Already initialized, skipping");
29
+ return;
30
+ }
31
+ try {
32
+ logger.debug("[RedisConversationMemoryManager] Initializing with config", {
33
+ host: this.redisConfig.host,
34
+ port: this.redisConfig.port,
35
+ keyPrefix: this.redisConfig.keyPrefix,
36
+ ttl: this.redisConfig.ttl,
37
+ });
38
+ this.redisClient = await createRedisClient(this.redisConfig);
39
+ this.isInitialized = true;
40
+ logger.info("RedisConversationMemoryManager initialized", {
41
+ storage: "redis",
42
+ host: this.redisConfig.host,
43
+ port: this.redisConfig.port,
44
+ maxSessions: this.config.maxSessions,
45
+ maxTurnsPerSession: this.config.maxTurnsPerSession,
46
+ });
47
+ logger.debug("[RedisConversationMemoryManager] Redis client created successfully", {
48
+ clientType: this.redisClient?.constructor?.name || "unknown",
49
+ isConnected: !!this.redisClient,
50
+ });
51
+ }
52
+ catch (error) {
53
+ logger.error("[RedisConversationMemoryManager] Failed to initialize", {
54
+ error: error instanceof Error ? error.message : String(error),
55
+ stack: error instanceof Error ? error.stack : undefined,
56
+ config: {
57
+ host: this.redisConfig.host,
58
+ port: this.redisConfig.port,
59
+ },
60
+ });
61
+ throw new ConversationMemoryError("Failed to initialize Redis conversation memory", "CONFIG_ERROR", { error: error instanceof Error ? error.message : String(error) });
62
+ }
63
+ }
64
+ /**
65
+ * Store a conversation turn for a session
66
+ */
67
+ async storeConversationTurn(sessionId, userId, userMessage, aiResponse) {
68
+ logger.debug("[RedisConversationMemoryManager] Storing conversation turn", {
69
+ sessionId,
70
+ userId,
71
+ userMessageLength: userMessage.length,
72
+ aiResponseLength: aiResponse.length,
73
+ });
74
+ await this.ensureInitialized();
75
+ try {
76
+ if (!this.redisClient) {
77
+ throw new Error("Redis client not initialized");
78
+ }
79
+ // Generate Redis key
80
+ const redisKey = getSessionKey(this.redisConfig, sessionId);
81
+ // Get existing messages
82
+ const messagesData = await this.redisClient.get(redisKey);
83
+ const messages = deserializeMessages(messagesData);
84
+ logger.info("[RedisConversationMemoryManager] Deserialized messages", {
85
+ messageCount: messages.length,
86
+ roles: messages.map((m) => m.role),
87
+ });
88
+ // Add new messages
89
+ messages.push({ role: "user", content: userMessage }, { role: "assistant", content: aiResponse });
90
+ logger.info("[RedisConversationMemoryManager] Added new messages", {
91
+ newMessageCount: messages.length,
92
+ latestMessages: [
93
+ {
94
+ role: messages[messages.length - 2]?.role,
95
+ contentLength: messages[messages.length - 2]?.content.length,
96
+ },
97
+ {
98
+ role: messages[messages.length - 1]?.role,
99
+ contentLength: messages[messages.length - 1]?.content.length,
100
+ },
101
+ ],
102
+ });
103
+ // Handle summarization or message limit
104
+ if (this.config.enableSummarization) {
105
+ const userAssistantCount = messages.filter((msg) => msg.role === "user" || msg.role === "assistant").length;
106
+ const currentTurnCount = Math.floor(userAssistantCount / MESSAGES_PER_TURN);
107
+ logger.debug("[RedisConversationMemoryManager] Checking summarization threshold", {
108
+ userAssistantCount,
109
+ currentTurnCount,
110
+ summarizationThreshold: this.config.summarizationThresholdTurns || 20,
111
+ shouldSummarize: currentTurnCount >=
112
+ (this.config.summarizationThresholdTurns || 20),
113
+ });
114
+ if (currentTurnCount >= (this.config.summarizationThresholdTurns || 20)) {
115
+ await this._summarizeMessages(sessionId, userId, messages);
116
+ return;
117
+ }
118
+ }
119
+ else {
120
+ const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) *
121
+ MESSAGES_PER_TURN;
122
+ logger.debug("[RedisConversationMemoryManager] Checking message limit", {
123
+ currentMessageCount: messages.length,
124
+ maxMessages,
125
+ shouldTrimMessages: messages.length > maxMessages,
126
+ });
127
+ if (messages.length > maxMessages) {
128
+ const trimCount = messages.length - maxMessages;
129
+ logger.debug("[RedisConversationMemoryManager] Trimming messages", {
130
+ beforeCount: messages.length,
131
+ trimCount,
132
+ afterCount: maxMessages,
133
+ });
134
+ messages.splice(0, messages.length - maxMessages);
135
+ }
136
+ }
137
+ // Save updated messages
138
+ const serializedData = serializeMessages(messages);
139
+ logger.debug("[RedisConversationMemoryManager] Saving messages to Redis", {
140
+ redisKey,
141
+ messageCount: messages.length,
142
+ serializedDataLength: serializedData.length,
143
+ });
144
+ await this.redisClient.set(redisKey, serializedData);
145
+ // Set TTL if configured
146
+ if (this.redisConfig.ttl > 0) {
147
+ logger.debug("[RedisConversationMemoryManager] Setting Redis TTL", {
148
+ redisKey,
149
+ ttl: this.redisConfig.ttl,
150
+ });
151
+ await this.redisClient.expire(redisKey, this.redisConfig.ttl);
152
+ }
153
+ // Enforce session limit
154
+ await this.enforceSessionLimit();
155
+ logger.debug("[RedisConversationMemoryManager] Successfully stored conversation turn", {
156
+ sessionId,
157
+ totalMessages: messages.length,
158
+ });
159
+ }
160
+ catch (error) {
161
+ throw new ConversationMemoryError(`Failed to store conversation turn in Redis for session ${sessionId}`, "STORAGE_ERROR", {
162
+ sessionId,
163
+ error: error instanceof Error ? error.message : String(error),
164
+ });
165
+ }
166
+ }
167
+ /**
168
+ * Build context messages for AI prompt injection
169
+ */
170
+ async buildContextMessages(sessionId) {
171
+ logger.info("[RedisConversationMemoryManager] Building context messages", {
172
+ sessionId,
173
+ method: "buildContextMessages",
174
+ });
175
+ await this.ensureInitialized();
176
+ if (!this.redisClient) {
177
+ logger.warn("[RedisConversationMemoryManager] Redis client not available, returning empty context", {
178
+ sessionId,
179
+ });
180
+ return [];
181
+ }
182
+ const redisKey = getSessionKey(this.redisConfig, sessionId);
183
+ logger.info("[RedisConversationMemoryManager] Getting messages from Redis", {
184
+ sessionId,
185
+ redisKey,
186
+ });
187
+ const messagesData = await this.redisClient.get(redisKey);
188
+ logger.info("[RedisConversationMemoryManager] Retrieved message data from Redis", {
189
+ sessionId,
190
+ redisKey,
191
+ hasData: !!messagesData,
192
+ dataLength: messagesData?.length || 0,
193
+ });
194
+ const messages = deserializeMessages(messagesData);
195
+ logger.info("[RedisConversationMemoryManager] Deserialized messages for context", {
196
+ sessionId,
197
+ messageCount: messages.length,
198
+ messageRoles: messages.map((m) => m.role),
199
+ firstMessagePreview: messages[0]?.content?.substring(0, 50),
200
+ lastMessagePreview: messages[messages.length - 1]?.content?.substring(0, 50),
201
+ });
202
+ return messages;
203
+ }
204
+ /**
205
+ * Get session data
206
+ */
207
+ async getSession(sessionId) {
208
+ logger.debug("[RedisConversationMemoryManager] Getting session", {
209
+ sessionId,
210
+ method: "getSession",
211
+ });
212
+ await this.ensureInitialized();
213
+ if (!this.redisClient) {
214
+ logger.warn("[RedisConversationMemoryManager] Redis client not available", {
215
+ sessionId,
216
+ });
217
+ return undefined;
218
+ }
219
+ const redisKey = getSessionKey(this.redisConfig, sessionId);
220
+ logger.debug("[RedisConversationMemoryManager] Getting session data from Redis", {
221
+ sessionId,
222
+ redisKey,
223
+ });
224
+ const messagesData = await this.redisClient.get(redisKey);
225
+ logger.debug("[RedisConversationMemoryManager] Retrieved session data", {
226
+ sessionId,
227
+ hasData: !!messagesData,
228
+ dataLength: messagesData?.length || 0,
229
+ });
230
+ if (!messagesData) {
231
+ logger.debug("[RedisConversationMemoryManager] No session data found", {
232
+ sessionId,
233
+ redisKey,
234
+ });
235
+ return undefined;
236
+ }
237
+ const messages = deserializeMessages(messagesData);
238
+ logger.debug("[RedisConversationMemoryManager] Deserialized session messages", {
239
+ sessionId,
240
+ messageCount: messages.length,
241
+ messageRoles: messages.map((m) => m.role),
242
+ });
243
+ // We don't store the full SessionMemory object in Redis,
244
+ // just the messages, so we recreate the SessionMemory object here
245
+ const session = {
246
+ sessionId,
247
+ messages,
248
+ createdAt: Date.now(), // We don't have this information
249
+ lastActivity: Date.now(), // We don't have this information
250
+ };
251
+ logger.debug("[RedisConversationMemoryManager] Created session memory object", {
252
+ sessionId,
253
+ messageCount: session.messages.length,
254
+ });
255
+ return session;
256
+ }
257
+ /**
258
+ * Create summary system message
259
+ */
260
+ createSummarySystemMessage(content) {
261
+ return {
262
+ role: "system",
263
+ content: `Summary of previous conversation turns:\n\n${content}`,
264
+ };
265
+ }
266
+ /**
267
+ * Close Redis connection
268
+ */
269
+ async close() {
270
+ if (this.redisClient) {
271
+ await this.redisClient.quit();
272
+ this.redisClient = null;
273
+ this.isInitialized = false;
274
+ logger.info("Redis connection closed");
275
+ }
276
+ }
277
+ /**
278
+ * Get statistics about conversation storage
279
+ */
280
+ async getStats() {
281
+ await this.ensureInitialized();
282
+ if (!this.redisClient) {
283
+ return { totalSessions: 0, totalTurns: 0 };
284
+ }
285
+ // Get all session keys using SCAN instead of KEYS to avoid blocking
286
+ const pattern = `${this.redisConfig.keyPrefix}*`;
287
+ const keys = await scanKeys(this.redisClient, pattern);
288
+ logger.debug("[RedisConversationMemoryManager] Got session keys with SCAN", {
289
+ pattern,
290
+ keyCount: keys.length,
291
+ });
292
+ // Count messages in each session
293
+ let totalTurns = 0;
294
+ for (const key of keys) {
295
+ const messagesData = await this.redisClient.get(key);
296
+ const messages = deserializeMessages(messagesData);
297
+ totalTurns += messages.length / MESSAGES_PER_TURN;
298
+ }
299
+ return {
300
+ totalSessions: keys.length,
301
+ totalTurns,
302
+ };
303
+ }
304
+ /**
305
+ * Clear a specific session
306
+ */
307
+ async clearSession(sessionId) {
308
+ await this.ensureInitialized();
309
+ if (!this.redisClient) {
310
+ return false;
311
+ }
312
+ const redisKey = getSessionKey(this.redisConfig, sessionId);
313
+ const result = await this.redisClient.del(redisKey);
314
+ if (result > 0) {
315
+ logger.info("Redis session cleared", { sessionId });
316
+ return true;
317
+ }
318
+ return false;
319
+ }
320
+ /**
321
+ * Clear all sessions
322
+ */
323
+ async clearAllSessions() {
324
+ await this.ensureInitialized();
325
+ if (!this.redisClient) {
326
+ return;
327
+ }
328
+ const pattern = `${this.redisConfig.keyPrefix}*`;
329
+ // Use SCAN instead of KEYS to avoid blocking the server
330
+ const keys = await scanKeys(this.redisClient, pattern);
331
+ logger.debug("[RedisConversationMemoryManager] Got session keys with SCAN for clearing", {
332
+ pattern,
333
+ keyCount: keys.length,
334
+ });
335
+ if (keys.length > 0) {
336
+ // Process keys in batches to avoid blocking Redis for too long
337
+ const batchSize = 100;
338
+ for (let i = 0; i < keys.length; i += batchSize) {
339
+ const batch = keys.slice(i, i + batchSize);
340
+ await this.redisClient.del(batch);
341
+ logger.debug("[RedisConversationMemoryManager] Cleared batch of sessions", {
342
+ batchIndex: Math.floor(i / batchSize) + 1,
343
+ batchSize: batch.length,
344
+ totalProcessed: i + batch.length,
345
+ totalKeys: keys.length,
346
+ });
347
+ }
348
+ logger.info("All Redis sessions cleared", { clearedCount: keys.length });
349
+ }
350
+ }
351
+ /**
352
+ * Summarize messages for a session
353
+ */
354
+ async _summarizeMessages(sessionId, userId, messages) {
355
+ logger.info(`[RedisConversationMemory] Summarizing session ${sessionId}...`);
356
+ logger.debug("[RedisConversationMemoryManager] Starting message summarization", {
357
+ sessionId,
358
+ userId,
359
+ messageCount: messages.length,
360
+ messageTypes: messages.map((m) => m.role),
361
+ });
362
+ const targetTurns = this.config.summarizationTargetTurns || 10;
363
+ const splitIndex = Math.max(0, messages.length - targetTurns * MESSAGES_PER_TURN);
364
+ const messagesToSummarize = messages.slice(0, splitIndex);
365
+ const recentMessages = messages.slice(splitIndex);
366
+ if (messagesToSummarize.length === 0) {
367
+ return;
368
+ }
369
+ const summarizationPrompt = this._createSummarizationPrompt(messagesToSummarize);
370
+ const summarizer = new NeuroLink({
371
+ conversationMemory: { enabled: false },
372
+ });
373
+ try {
374
+ const providerName = this.config.summarizationProvider;
375
+ // Map provider names to correct format
376
+ let mappedProvider = providerName;
377
+ if (providerName === "vertex") {
378
+ mappedProvider = "googlevertex";
379
+ }
380
+ if (!mappedProvider) {
381
+ logger.error(`[RedisConversationMemory] Missing summarization provider`);
382
+ return;
383
+ }
384
+ logger.debug(`[RedisConversationMemory] Using provider: ${mappedProvider} for summarization`);
385
+ const summaryResult = await summarizer.generate({
386
+ input: { text: summarizationPrompt },
387
+ provider: mappedProvider,
388
+ model: this.config.summarizationModel,
389
+ disableTools: true,
390
+ });
391
+ if (!this.redisClient) {
392
+ throw new Error("Redis client not initialized");
393
+ }
394
+ if (summaryResult.content) {
395
+ const updatedMessages = [
396
+ this.createSummarySystemMessage(summaryResult.content),
397
+ ...recentMessages,
398
+ ];
399
+ const redisKey = getSessionKey(this.redisConfig, sessionId);
400
+ await this.redisClient.set(redisKey, serializeMessages(updatedMessages));
401
+ // Set TTL if configured
402
+ if (this.redisConfig.ttl > 0) {
403
+ await this.redisClient.expire(redisKey, this.redisConfig.ttl);
404
+ }
405
+ logger.info(`[RedisConversationMemory] Summarization complete for session ${sessionId}.`);
406
+ }
407
+ else {
408
+ logger.warn(`[RedisConversationMemory] Summarization failed for session ${sessionId}. History not modified.`);
409
+ }
410
+ }
411
+ catch (error) {
412
+ logger.error(`[RedisConversationMemory] Error during summarization for session ${sessionId}`, { error });
413
+ }
414
+ }
415
+ /**
416
+ * Create summarization prompt
417
+ */
418
+ _createSummarizationPrompt(history) {
419
+ const formattedHistory = history
420
+ .map((msg) => `${msg.role}: ${msg.content}`)
421
+ .join("\n\n");
422
+ return `
423
+ You are a context summarization AI. Your task is to condense the following conversation history for another AI assistant.
424
+ 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.
425
+ Ensure the summary flows logically and is ready to be used as context for the next turn in the conversation.
426
+
427
+ Conversation History to Summarize:
428
+ ---
429
+ ${formattedHistory}
430
+ ---
431
+ `.trim();
432
+ }
433
+ /**
434
+ * Ensure Redis client is initialized
435
+ */
436
+ async ensureInitialized() {
437
+ logger.debug("[RedisConversationMemoryManager] Ensuring initialization");
438
+ if (!this.isInitialized) {
439
+ logger.debug("[RedisConversationMemoryManager] Not initialized, initializing now");
440
+ await this.initialize();
441
+ }
442
+ else {
443
+ logger.debug("[RedisConversationMemoryManager] Already initialized");
444
+ }
445
+ }
446
+ /**
447
+ * Enforce session limit
448
+ *
449
+ * NOTE: This function is maintained only for backward compatibility with the
450
+ * in-memory implementation. In Redis, we do not actually delete sessions based on
451
+ * a global limit, as this could cause race conditions in a distributed environment.
452
+ * Each session is managed independently with its own TTL.
453
+ */
454
+ async enforceSessionLimit() {
455
+ logger.debug("[RedisConversationMemoryManager] Enforcing session limit (compatibility function)");
456
+ if (!this.redisClient) {
457
+ logger.debug("[RedisConversationMemoryManager] No Redis client, skipping session limit check");
458
+ return;
459
+ }
460
+ const maxSessions = this.config.maxSessions || DEFAULT_MAX_SESSIONS;
461
+ const pattern = `${this.redisConfig.keyPrefix}*`;
462
+ logger.debug("[RedisConversationMemoryManager] Listing all session keys", {
463
+ pattern,
464
+ maxSessions,
465
+ });
466
+ // Use SCAN instead of KEYS to avoid blocking the server
467
+ const keys = await scanKeys(this.redisClient, pattern);
468
+ logger.debug("[RedisConversationMemoryManager] Found existing sessions using SCAN", {
469
+ sessionCount: keys.length,
470
+ maxSessions,
471
+ needsTrimming: keys.length > maxSessions,
472
+ });
473
+ // In the Redis implementation, we intentionally do not delete sessions based on a global limit.
474
+ // Each session is managed independently with its own TTL.
475
+ if (keys.length > maxSessions) {
476
+ logger.info("Redis session count exceeds limit, but not enforcing deletion", {
477
+ currentCount: keys.length,
478
+ maxSessions,
479
+ reason: "Redis sessions are managed independently with TTL",
480
+ });
481
+ }
482
+ }
483
+ }
@@ -3,7 +3,7 @@ import type { ZodUnknownSchema, ValidationSchema } from "../types/typeAliases.js
3
3
  import type { GenerateResult } from "../types/generateTypes.js";
4
4
  import type { StreamOptions, StreamResult } from "../types/streamTypes.js";
5
5
  import type { JsonValue } from "../types/common.js";
6
- import type { ChatMessage, ConversationMemoryConfig } from "../types/conversationTypes.js";
6
+ import type { ChatMessage, ConversationMemoryConfig } from "../types/conversation.js";
7
7
  import type { TokenUsage, AnalyticsData } from "../types/analytics.js";
8
8
  import type { EvaluationData } from "../index.js";
9
9
  export type { EvaluationData };
@@ -9,10 +9,11 @@ import type { TextGenerationOptions, TextGenerationResult } from "./types/index.
9
9
  import type { GenerateOptions, GenerateResult } from "./types/generateTypes.js";
10
10
  import type { StreamOptions, StreamResult } from "./types/streamTypes.js";
11
11
  import type { MCPServerInfo, MCPExecutableTool } from "./types/mcpTypes.js";
12
+ import type { ToolInfo } from "./mcp/contracts/mcpContract.js";
12
13
  import type { JsonObject } from "./types/common.js";
13
14
  import type { BatchOperationResult } from "./types/typeAliases.js";
14
15
  import { EventEmitter } from "events";
15
- import type { ConversationMemoryConfig, ChatMessage } from "./types/conversationTypes.js";
16
+ import type { ConversationMemoryConfig, ChatMessage } from "./types/conversation.js";
16
17
  import type { ExternalMCPServerInstance, ExternalMCPOperationResult, ExternalMCPToolInfo } from "./types/externalMcp.js";
17
18
  export interface ProviderStatus {
18
19
  provider: string;
@@ -44,6 +45,8 @@ export declare class NeuroLink {
44
45
  private emitter;
45
46
  private autoDiscoveredServerInfos;
46
47
  private externalServerManager;
48
+ private toolCache;
49
+ private readonly toolCacheDuration;
47
50
  private toolCircuitBreakers;
48
51
  private toolExecutionMetrics;
49
52
  /**
@@ -57,6 +60,8 @@ export declare class NeuroLink {
57
60
  */
58
61
  private emitToolEndEvent;
59
62
  private conversationMemory?;
63
+ private conversationMemoryNeedsInit;
64
+ private conversationMemoryConfig?;
60
65
  /**
61
66
  * Creates a new NeuroLink instance for AI text generation with MCP tool integration.
62
67
  *
@@ -240,6 +245,7 @@ export declare class NeuroLink {
240
245
  private emitGenerationStartEvents;
241
246
  /**
242
247
  * Initialize conversation memory for generation
248
+ * Lazily initializes memory if needed from constructor flags
243
249
  */
244
250
  private initializeConversationMemoryForGeneration;
245
251
  /**
@@ -611,13 +617,8 @@ export declare class NeuroLink {
611
617
  * Get all available tools including custom and in-memory ones
612
618
  * @returns Array of available tools with metadata
613
619
  */
614
- getAllAvailableTools(): Promise<{
615
- name: string;
616
- description: string;
617
- server: string;
618
- category?: string;
619
- inputSchema?: import("./types/typeAliases.js").StandardRecord;
620
- }[]>;
620
+ private invalidateToolCache;
621
+ getAllAvailableTools(): Promise<ToolInfo[]>;
621
622
  /**
622
623
  * Get comprehensive status of all AI providers
623
624
  * Primary method for provider health checking and diagnostics
@@ -795,7 +796,7 @@ export declare class NeuroLink {
795
796
  /**
796
797
  * Get conversation memory statistics (public API)
797
798
  */
798
- getConversationStats(): Promise<import("./types/conversationTypes.js").ConversationMemoryStats>;
799
+ getConversationStats(): Promise<import("./types/conversation.js").ConversationMemoryStats>;
799
800
  /**
800
801
  * Get complete conversation history for a specific session (public API)
801
802
  * @param sessionId - The session ID to retrieve history for
@@ -906,6 +907,11 @@ export declare class NeuroLink {
906
907
  * Unregister a specific external MCP tool from the main registry
907
908
  */
908
909
  private unregisterExternalMCPToolFromRegistry;
910
+ /**
911
+ * Lazily initialize conversation memory when needed
912
+ * This is called the first time a generate or stream operation is performed
913
+ */
914
+ private lazyInitializeConversationMemory;
909
915
  /**
910
916
  * Unregister all external MCP tools from the main registry
911
917
  */