@juspay/neurolink 7.52.0 → 7.53.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 CHANGED
@@ -1,3 +1,15 @@
1
+ ## [7.53.1](https://github.com/juspay/neurolink/compare/v7.53.0...v7.53.1) (2025-10-27)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **(redis):** Emit redis related info through events for logging ([3224075](https://github.com/juspay/neurolink/commit/3224075d86c97f4206855d485748a95d609256af))
6
+
7
+ ## [7.53.0](https://github.com/juspay/neurolink/compare/v7.52.0...v7.53.0) (2025-10-26)
8
+
9
+ ### Features
10
+
11
+ - **(test):** vitest configuration setup for cli ([ffb7db3](https://github.com/juspay/neurolink/commit/ffb7db301e2e883af5427db6e6d00a8a7dd65023))
12
+
1
13
  ## [7.52.0](https://github.com/juspay/neurolink/compare/v7.51.3...v7.52.0) (2025-10-24)
2
14
 
3
15
  ### Features
@@ -148,13 +148,4 @@ export declare class RedisConversationMemoryManager {
148
148
  * Flush pending tool execution data for a session and merge into conversation
149
149
  */
150
150
  private flushPendingToolData;
151
- /**
152
- * Enforce session limit
153
- *
154
- * NOTE: This function is maintained only for backward compatibility with the
155
- * in-memory implementation. In Redis, we do not actually delete sessions based on
156
- * a global limit, as this could cause race conditions in a distributed environment.
157
- * Each session is managed independently with its own TTL.
158
- */
159
- private enforceSessionLimit;
160
151
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { randomUUID } from "crypto";
6
6
  import { ConversationMemoryError } from "../types/conversation.js";
7
- import { DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
7
+ import { MESSAGES_PER_TURN } 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";
@@ -376,8 +376,6 @@ export class RedisConversationMemoryManager {
376
376
  if (userId) {
377
377
  await this.addUserSession(userId, sessionId);
378
378
  }
379
- // Enforce session limit
380
- await this.enforceSessionLimit();
381
379
  logger.debug("[RedisConversationMemoryManager] Successfully stored conversation turn", {
382
380
  sessionId,
383
381
  totalMessages: conversation.messages.length,
@@ -799,10 +797,11 @@ User message: "${userMessage}`;
799
797
  results.push(metadata);
800
798
  }
801
799
  else {
802
- logger.debug("[RedisConversationMemoryManager] Empty or missing session metadata", {
800
+ logger.debug("[RedisConversationMemoryManager] Empty or missing session metadata - removing from user history", {
803
801
  userId,
804
802
  sessionId,
805
803
  });
804
+ await this.removeUserSession(userId, sessionId);
806
805
  }
807
806
  }
808
807
  catch (sessionError) {
@@ -916,41 +915,4 @@ User message: "${userMessage}`;
916
915
  totalMessages: conversation.messages.length,
917
916
  });
918
917
  }
919
- /**
920
- * Enforce session limit
921
- *
922
- * NOTE: This function is maintained only for backward compatibility with the
923
- * in-memory implementation. In Redis, we do not actually delete sessions based on
924
- * a global limit, as this could cause race conditions in a distributed environment.
925
- * Each session is managed independently with its own TTL.
926
- */
927
- async enforceSessionLimit() {
928
- logger.debug("[RedisConversationMemoryManager] Enforcing session limit (compatibility function)");
929
- if (!this.redisClient) {
930
- logger.debug("[RedisConversationMemoryManager] No Redis client, skipping session limit check");
931
- return;
932
- }
933
- const maxSessions = this.config.maxSessions || DEFAULT_MAX_SESSIONS;
934
- const pattern = `${this.redisConfig.keyPrefix}*`;
935
- logger.debug("[RedisConversationMemoryManager] Listing all session keys", {
936
- pattern,
937
- maxSessions,
938
- });
939
- // Use SCAN instead of KEYS to avoid blocking the server
940
- const keys = await scanKeys(this.redisClient, pattern);
941
- logger.debug("[RedisConversationMemoryManager] Found existing sessions using SCAN", {
942
- sessionCount: keys.length,
943
- maxSessions,
944
- needsTrimming: keys.length > maxSessions,
945
- });
946
- // In the Redis implementation, we intentionally do not delete sessions based on a global limit.
947
- // Each session is managed independently with its own TTL.
948
- if (keys.length > maxSessions) {
949
- logger.info("Redis session count exceeds limit, but not enforcing deletion", {
950
- currentCount: keys.length,
951
- maxSessions,
952
- reason: "Redis sessions are managed independently with TTL",
953
- });
954
- }
955
- }
956
918
  }
@@ -148,13 +148,4 @@ export declare class RedisConversationMemoryManager {
148
148
  * Flush pending tool execution data for a session and merge into conversation
149
149
  */
150
150
  private flushPendingToolData;
151
- /**
152
- * Enforce session limit
153
- *
154
- * NOTE: This function is maintained only for backward compatibility with the
155
- * in-memory implementation. In Redis, we do not actually delete sessions based on
156
- * a global limit, as this could cause race conditions in a distributed environment.
157
- * Each session is managed independently with its own TTL.
158
- */
159
- private enforceSessionLimit;
160
151
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { randomUUID } from "crypto";
6
6
  import { ConversationMemoryError } from "../types/conversation.js";
7
- import { DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemory.js";
7
+ import { MESSAGES_PER_TURN } 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";
@@ -376,8 +376,6 @@ export class RedisConversationMemoryManager {
376
376
  if (userId) {
377
377
  await this.addUserSession(userId, sessionId);
378
378
  }
379
- // Enforce session limit
380
- await this.enforceSessionLimit();
381
379
  logger.debug("[RedisConversationMemoryManager] Successfully stored conversation turn", {
382
380
  sessionId,
383
381
  totalMessages: conversation.messages.length,
@@ -799,10 +797,11 @@ User message: "${userMessage}`;
799
797
  results.push(metadata);
800
798
  }
801
799
  else {
802
- logger.debug("[RedisConversationMemoryManager] Empty or missing session metadata", {
800
+ logger.debug("[RedisConversationMemoryManager] Empty or missing session metadata - removing from user history", {
803
801
  userId,
804
802
  sessionId,
805
803
  });
804
+ await this.removeUserSession(userId, sessionId);
806
805
  }
807
806
  }
808
807
  catch (sessionError) {
@@ -916,42 +915,5 @@ User message: "${userMessage}`;
916
915
  totalMessages: conversation.messages.length,
917
916
  });
918
917
  }
919
- /**
920
- * Enforce session limit
921
- *
922
- * NOTE: This function is maintained only for backward compatibility with the
923
- * in-memory implementation. In Redis, we do not actually delete sessions based on
924
- * a global limit, as this could cause race conditions in a distributed environment.
925
- * Each session is managed independently with its own TTL.
926
- */
927
- async enforceSessionLimit() {
928
- logger.debug("[RedisConversationMemoryManager] Enforcing session limit (compatibility function)");
929
- if (!this.redisClient) {
930
- logger.debug("[RedisConversationMemoryManager] No Redis client, skipping session limit check");
931
- return;
932
- }
933
- const maxSessions = this.config.maxSessions || DEFAULT_MAX_SESSIONS;
934
- const pattern = `${this.redisConfig.keyPrefix}*`;
935
- logger.debug("[RedisConversationMemoryManager] Listing all session keys", {
936
- pattern,
937
- maxSessions,
938
- });
939
- // Use SCAN instead of KEYS to avoid blocking the server
940
- const keys = await scanKeys(this.redisClient, pattern);
941
- logger.debug("[RedisConversationMemoryManager] Found existing sessions using SCAN", {
942
- sessionCount: keys.length,
943
- maxSessions,
944
- needsTrimming: keys.length > maxSessions,
945
- });
946
- // In the Redis implementation, we intentionally do not delete sessions based on a global limit.
947
- // Each session is managed independently with its own TTL.
948
- if (keys.length > maxSessions) {
949
- logger.info("Redis session count exceeds limit, but not enforcing deletion", {
950
- currentCount: keys.length,
951
- maxSessions,
952
- reason: "Redis sessions are managed independently with TTL",
953
- });
954
- }
955
- }
956
918
  }
957
919
  //# sourceMappingURL=redisConversationMemoryManager.js.map
@@ -1942,19 +1942,56 @@ export class NeuroLink {
1942
1942
  }
1943
1943
  finally {
1944
1944
  // Store memory after stream consumption is complete
1945
- if (self.conversationMemory) {
1945
+ if (self.conversationMemory && enhancedOptions.context?.sessionId) {
1946
+ const storageStartTime = Date.now();
1947
+ const sessionId = enhancedOptions.context?.sessionId;
1948
+ const userId = enhancedOptions.context
1949
+ ?.userId;
1946
1950
  try {
1947
- await self.conversationMemory.storeConversationTurn(enhancedOptions.context
1948
- ?.sessionId, enhancedOptions.context
1949
- ?.userId, originalPrompt ?? "", accumulatedContent, new Date(startTime));
1951
+ self.emitter.emit("log-event", {
1952
+ type: "log-event:storage:start",
1953
+ data: {
1954
+ operation: "storeConversationTurn",
1955
+ sessionId,
1956
+ userId,
1957
+ timestamp: storageStartTime,
1958
+ source: "stream-finally-block",
1959
+ userInputLength: originalPrompt?.length ?? 0,
1960
+ responseLength: accumulatedContent.length,
1961
+ },
1962
+ });
1963
+ await self.conversationMemory.storeConversationTurn(sessionId, userId, originalPrompt ?? "", accumulatedContent, new Date(startTime));
1964
+ self.emitter.emit("log-event", {
1965
+ type: "log-event:storage:end",
1966
+ data: {
1967
+ operation: "storeConversationTurn",
1968
+ sessionId,
1969
+ userId,
1970
+ timestamp: Date.now(),
1971
+ duration: Date.now() - storageStartTime,
1972
+ source: "stream-finally-block",
1973
+ success: true,
1974
+ },
1975
+ });
1950
1976
  logger.debug("Stream conversation turn stored", {
1951
- sessionId: enhancedOptions.context
1952
- ?.sessionId,
1977
+ sessionId,
1953
1978
  userInputLength: originalPrompt?.length ?? 0,
1954
1979
  responseLength: accumulatedContent.length,
1955
1980
  });
1956
1981
  }
1957
1982
  catch (error) {
1983
+ self.emitter.emit("log-event", {
1984
+ type: "log-event:storage:error",
1985
+ data: {
1986
+ operation: "storeConversationTurn",
1987
+ sessionId,
1988
+ userId,
1989
+ timestamp: Date.now(),
1990
+ duration: Date.now() - storageStartTime,
1991
+ source: "stream-finally-block",
1992
+ error: error instanceof Error ? error.message : String(error),
1993
+ },
1994
+ });
1958
1995
  logger.warn("Failed to store stream conversation turn", {
1959
1996
  error: error instanceof Error ? error.message : String(error),
1960
1997
  });
@@ -2147,12 +2184,37 @@ export class NeuroLink {
2147
2184
  }
2148
2185
  finally {
2149
2186
  // Store memory after fallback stream consumption is complete
2150
- if (self.conversationMemory) {
2187
+ if (self.conversationMemory && enhancedOptions?.context?.sessionId) {
2188
+ const storageStartTime = Date.now();
2189
+ const sessionId = enhancedOptions?.context?.sessionId;
2190
+ const userId = enhancedOptions?.context
2191
+ ?.userId;
2151
2192
  try {
2152
- const sessionId = enhancedOptions?.context?.sessionId;
2153
- const userId = enhancedOptions?.context
2154
- ?.userId;
2193
+ self.emitter.emit("log", {
2194
+ type: "log-event:storage:start",
2195
+ data: {
2196
+ operation: "storeConversationTurn",
2197
+ sessionId,
2198
+ userId,
2199
+ timestamp: storageStartTime,
2200
+ source: "fallback-stream-finally-block",
2201
+ userInputLength: originalPrompt?.length ?? 0,
2202
+ responseLength: fallbackAccumulatedContent.length,
2203
+ },
2204
+ });
2155
2205
  await self.conversationMemory.storeConversationTurn(sessionId || options.context?.sessionId, userId || options.context?.userId, originalPrompt ?? "", fallbackAccumulatedContent, new Date(startTime));
2206
+ self.emitter.emit("log", {
2207
+ type: "log-event:storage:end",
2208
+ data: {
2209
+ operation: "storeConversationTurn",
2210
+ sessionId,
2211
+ userId,
2212
+ timestamp: Date.now(),
2213
+ duration: Date.now() - storageStartTime,
2214
+ source: "fallback-stream-finally-block",
2215
+ success: true,
2216
+ },
2217
+ });
2156
2218
  logger.debug("Fallback stream conversation turn stored", {
2157
2219
  sessionId: sessionId || options.context?.sessionId,
2158
2220
  userInputLength: originalPrompt?.length ?? 0,
@@ -2160,6 +2222,18 @@ export class NeuroLink {
2160
2222
  });
2161
2223
  }
2162
2224
  catch (error) {
2225
+ self.emitter.emit("log-event", {
2226
+ type: "log-event:storage:error",
2227
+ data: {
2228
+ operation: "storeConversationTurn",
2229
+ sessionId,
2230
+ userId,
2231
+ timestamp: Date.now(),
2232
+ duration: Date.now() - storageStartTime,
2233
+ source: "fallback-stream-finally-block",
2234
+ error: error instanceof Error ? error.message : String(error),
2235
+ },
2236
+ });
2163
2237
  logger.warn("Failed to store fallback stream conversation turn", {
2164
2238
  error: error instanceof Error ? error.message : String(error),
2165
2239
  });
package/dist/neurolink.js CHANGED
@@ -1942,19 +1942,56 @@ export class NeuroLink {
1942
1942
  }
1943
1943
  finally {
1944
1944
  // Store memory after stream consumption is complete
1945
- if (self.conversationMemory) {
1945
+ if (self.conversationMemory && enhancedOptions.context?.sessionId) {
1946
+ const storageStartTime = Date.now();
1947
+ const sessionId = enhancedOptions.context?.sessionId;
1948
+ const userId = enhancedOptions.context
1949
+ ?.userId;
1946
1950
  try {
1947
- await self.conversationMemory.storeConversationTurn(enhancedOptions.context
1948
- ?.sessionId, enhancedOptions.context
1949
- ?.userId, originalPrompt ?? "", accumulatedContent, new Date(startTime));
1951
+ self.emitter.emit("log-event", {
1952
+ type: "log-event:storage:start",
1953
+ data: {
1954
+ operation: "storeConversationTurn",
1955
+ sessionId,
1956
+ userId,
1957
+ timestamp: storageStartTime,
1958
+ source: "stream-finally-block",
1959
+ userInputLength: originalPrompt?.length ?? 0,
1960
+ responseLength: accumulatedContent.length,
1961
+ },
1962
+ });
1963
+ await self.conversationMemory.storeConversationTurn(sessionId, userId, originalPrompt ?? "", accumulatedContent, new Date(startTime));
1964
+ self.emitter.emit("log-event", {
1965
+ type: "log-event:storage:end",
1966
+ data: {
1967
+ operation: "storeConversationTurn",
1968
+ sessionId,
1969
+ userId,
1970
+ timestamp: Date.now(),
1971
+ duration: Date.now() - storageStartTime,
1972
+ source: "stream-finally-block",
1973
+ success: true,
1974
+ },
1975
+ });
1950
1976
  logger.debug("Stream conversation turn stored", {
1951
- sessionId: enhancedOptions.context
1952
- ?.sessionId,
1977
+ sessionId,
1953
1978
  userInputLength: originalPrompt?.length ?? 0,
1954
1979
  responseLength: accumulatedContent.length,
1955
1980
  });
1956
1981
  }
1957
1982
  catch (error) {
1983
+ self.emitter.emit("log-event", {
1984
+ type: "log-event:storage:error",
1985
+ data: {
1986
+ operation: "storeConversationTurn",
1987
+ sessionId,
1988
+ userId,
1989
+ timestamp: Date.now(),
1990
+ duration: Date.now() - storageStartTime,
1991
+ source: "stream-finally-block",
1992
+ error: error instanceof Error ? error.message : String(error),
1993
+ },
1994
+ });
1958
1995
  logger.warn("Failed to store stream conversation turn", {
1959
1996
  error: error instanceof Error ? error.message : String(error),
1960
1997
  });
@@ -2147,12 +2184,37 @@ export class NeuroLink {
2147
2184
  }
2148
2185
  finally {
2149
2186
  // Store memory after fallback stream consumption is complete
2150
- if (self.conversationMemory) {
2187
+ if (self.conversationMemory && enhancedOptions?.context?.sessionId) {
2188
+ const storageStartTime = Date.now();
2189
+ const sessionId = enhancedOptions?.context?.sessionId;
2190
+ const userId = enhancedOptions?.context
2191
+ ?.userId;
2151
2192
  try {
2152
- const sessionId = enhancedOptions?.context?.sessionId;
2153
- const userId = enhancedOptions?.context
2154
- ?.userId;
2193
+ self.emitter.emit("log", {
2194
+ type: "log-event:storage:start",
2195
+ data: {
2196
+ operation: "storeConversationTurn",
2197
+ sessionId,
2198
+ userId,
2199
+ timestamp: storageStartTime,
2200
+ source: "fallback-stream-finally-block",
2201
+ userInputLength: originalPrompt?.length ?? 0,
2202
+ responseLength: fallbackAccumulatedContent.length,
2203
+ },
2204
+ });
2155
2205
  await self.conversationMemory.storeConversationTurn(sessionId || options.context?.sessionId, userId || options.context?.userId, originalPrompt ?? "", fallbackAccumulatedContent, new Date(startTime));
2206
+ self.emitter.emit("log", {
2207
+ type: "log-event:storage:end",
2208
+ data: {
2209
+ operation: "storeConversationTurn",
2210
+ sessionId,
2211
+ userId,
2212
+ timestamp: Date.now(),
2213
+ duration: Date.now() - storageStartTime,
2214
+ source: "fallback-stream-finally-block",
2215
+ success: true,
2216
+ },
2217
+ });
2156
2218
  logger.debug("Fallback stream conversation turn stored", {
2157
2219
  sessionId: sessionId || options.context?.sessionId,
2158
2220
  userInputLength: originalPrompt?.length ?? 0,
@@ -2160,6 +2222,18 @@ export class NeuroLink {
2160
2222
  });
2161
2223
  }
2162
2224
  catch (error) {
2225
+ self.emitter.emit("log-event", {
2226
+ type: "log-event:storage:error",
2227
+ data: {
2228
+ operation: "storeConversationTurn",
2229
+ sessionId,
2230
+ userId,
2231
+ timestamp: Date.now(),
2232
+ duration: Date.now() - storageStartTime,
2233
+ source: "fallback-stream-finally-block",
2234
+ error: error instanceof Error ? error.message : String(error),
2235
+ },
2236
+ });
2163
2237
  logger.warn("Failed to store fallback stream conversation turn", {
2164
2238
  error: error instanceof Error ? error.message : String(error),
2165
2239
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "7.52.0",
3
+ "version": "7.53.1",
4
4
  "description": "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 deploy AI applications with 9 major providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
5
5
  "author": {
6
6
  "name": "Juspay Technologies",
@@ -56,11 +56,22 @@
56
56
  "// Shell Script Conversion": "",
57
57
  "convert:shell-scripts": "node tools/automation/shellConverter.js",
58
58
  "convert:specific": "node tools/automation/shellConverter.js --specific",
59
- "// Testing (Single Continuous Test Suite)": "",
60
- "test": "pnpm run test:providers",
61
- "test:providers": "npx tsx test/continuous-test-suite.ts",
59
+ "// Testing (Enhanced Vitest Framework)": "",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "test:ui": "vitest --ui",
63
+ "test:coverage": "vitest run --coverage",
64
+ "test:providers": "vitest run test/unit/providers",
65
+ "test:cli": "vitest run test/integration/cli",
66
+ "test:sdk": "vitest run test/unit/sdk",
67
+ "test:integration": "vitest run test/integration",
68
+ "test:e2e": "vitest run test/e2e",
69
+ "test:ci": "vitest run --coverage --reporter=junit --reporter=verbose",
70
+ "test:debug": "vitest --inspect-brk",
62
71
  "test:performance": "node tools/testing/performanceMonitor.js",
63
- "test:ci": "pnpm run test:providers && pnpm run test:performance",
72
+ "// Legacy Testing Support (during transition)": "",
73
+ "test:legacy": "npx tsx test/continuous-test-suite.ts",
74
+ "test:comparison": "pnpm run test && pnpm run test:legacy",
64
75
  "// Content Generation (Cross-platform JS)": "",
65
76
  "content:videos": "node tools/converted-scripts/generateAllVideos.js",
66
77
  "content:cleanup": "node tools/converted-scripts/cleanupHashNamedVideos.js",