@juspay/neurolink 9.41.0 → 9.42.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 (189) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +7 -1
  3. package/dist/auth/anthropicOAuth.d.ts +18 -3
  4. package/dist/auth/anthropicOAuth.js +137 -4
  5. package/dist/auth/providers/firebase.js +5 -1
  6. package/dist/auth/providers/jwt.js +5 -1
  7. package/dist/auth/providers/workos.js +5 -1
  8. package/dist/auth/sessionManager.d.ts +1 -1
  9. package/dist/auth/sessionManager.js +58 -27
  10. package/dist/browser/neurolink.min.js +337 -318
  11. package/dist/cli/commands/mcp.js +3 -0
  12. package/dist/cli/commands/proxy.d.ts +2 -1
  13. package/dist/cli/commands/proxy.js +279 -16
  14. package/dist/cli/commands/task.js +3 -0
  15. package/dist/cli/factories/commandFactory.d.ts +2 -0
  16. package/dist/cli/factories/commandFactory.js +38 -0
  17. package/dist/cli/parser.js +4 -3
  18. package/dist/client/aiSdkAdapter.js +3 -0
  19. package/dist/client/streamingClient.js +30 -10
  20. package/dist/core/modules/GenerationHandler.js +3 -2
  21. package/dist/core/redisConversationMemoryManager.js +7 -3
  22. package/dist/evaluation/BatchEvaluator.js +4 -1
  23. package/dist/evaluation/hooks/observabilityHooks.js +5 -3
  24. package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  25. package/dist/evaluation/pipeline/evaluationPipeline.js +20 -8
  26. package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  27. package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  28. package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
  29. package/dist/lib/auth/anthropicOAuth.js +137 -4
  30. package/dist/lib/auth/providers/firebase.js +5 -1
  31. package/dist/lib/auth/providers/jwt.js +5 -1
  32. package/dist/lib/auth/providers/workos.js +5 -1
  33. package/dist/lib/auth/sessionManager.d.ts +1 -1
  34. package/dist/lib/auth/sessionManager.js +58 -27
  35. package/dist/lib/client/aiSdkAdapter.js +3 -0
  36. package/dist/lib/client/streamingClient.js +30 -10
  37. package/dist/lib/core/modules/GenerationHandler.js +3 -2
  38. package/dist/lib/core/redisConversationMemoryManager.js +7 -3
  39. package/dist/lib/evaluation/BatchEvaluator.js +4 -1
  40. package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
  41. package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  42. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +20 -8
  43. package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  44. package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  45. package/dist/lib/neurolink.d.ts +3 -2
  46. package/dist/lib/neurolink.js +260 -494
  47. package/dist/lib/observability/otelBridge.d.ts +2 -2
  48. package/dist/lib/observability/otelBridge.js +12 -3
  49. package/dist/lib/providers/amazonBedrock.js +2 -4
  50. package/dist/lib/providers/anthropic.d.ts +9 -5
  51. package/dist/lib/providers/anthropic.js +19 -14
  52. package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
  53. package/dist/lib/providers/anthropicBaseProvider.js +5 -4
  54. package/dist/lib/providers/azureOpenai.d.ts +1 -1
  55. package/dist/lib/providers/azureOpenai.js +5 -4
  56. package/dist/lib/providers/googleAiStudio.js +30 -1
  57. package/dist/lib/providers/googleVertex.js +28 -6
  58. package/dist/lib/providers/huggingFace.d.ts +3 -3
  59. package/dist/lib/providers/huggingFace.js +6 -8
  60. package/dist/lib/providers/litellm.js +41 -29
  61. package/dist/lib/providers/mistral.js +2 -1
  62. package/dist/lib/providers/ollama.js +80 -23
  63. package/dist/lib/providers/openAI.js +3 -2
  64. package/dist/lib/providers/openRouter.js +2 -1
  65. package/dist/lib/providers/openaiCompatible.d.ts +4 -4
  66. package/dist/lib/providers/openaiCompatible.js +4 -4
  67. package/dist/lib/proxy/claudeFormat.d.ts +3 -2
  68. package/dist/lib/proxy/claudeFormat.js +25 -20
  69. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  70. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  71. package/dist/lib/proxy/modelRouter.js +3 -0
  72. package/dist/lib/proxy/oauthFetch.d.ts +1 -1
  73. package/dist/lib/proxy/oauthFetch.js +65 -72
  74. package/dist/lib/proxy/proxyConfig.js +44 -24
  75. package/dist/lib/proxy/proxyEnv.d.ts +19 -0
  76. package/dist/lib/proxy/proxyEnv.js +73 -0
  77. package/dist/lib/proxy/proxyFetch.js +50 -4
  78. package/dist/lib/proxy/proxyTracer.d.ts +133 -0
  79. package/dist/lib/proxy/proxyTracer.js +645 -0
  80. package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
  81. package/dist/lib/proxy/rawStreamCapture.js +83 -0
  82. package/dist/lib/proxy/requestLogger.d.ts +32 -5
  83. package/dist/lib/proxy/requestLogger.js +406 -37
  84. package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
  85. package/dist/lib/proxy/sseInterceptor.js +402 -0
  86. package/dist/lib/proxy/usageStats.d.ts +4 -3
  87. package/dist/lib/proxy/usageStats.js +25 -12
  88. package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
  89. package/dist/lib/rag/chunking/markdownChunker.js +15 -6
  90. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +7 -2
  91. package/dist/lib/server/routes/claudeProxyRoutes.js +1737 -508
  92. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
  93. package/dist/lib/services/server/ai/observability/instrumentation.js +240 -40
  94. package/dist/lib/tasks/backends/bullmqBackend.d.ts +1 -0
  95. package/dist/lib/tasks/backends/bullmqBackend.js +14 -7
  96. package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
  97. package/dist/lib/tasks/store/redisTaskStore.js +34 -26
  98. package/dist/lib/tasks/taskManager.d.ts +3 -0
  99. package/dist/lib/tasks/taskManager.js +63 -30
  100. package/dist/lib/telemetry/index.d.ts +2 -1
  101. package/dist/lib/telemetry/index.js +2 -1
  102. package/dist/lib/telemetry/telemetryService.d.ts +3 -0
  103. package/dist/lib/telemetry/telemetryService.js +65 -5
  104. package/dist/lib/types/cli.d.ts +10 -0
  105. package/dist/lib/types/proxyTypes.d.ts +37 -5
  106. package/dist/lib/types/streamTypes.d.ts +25 -3
  107. package/dist/lib/utils/messageBuilder.js +3 -2
  108. package/dist/lib/utils/providerHealth.d.ts +18 -0
  109. package/dist/lib/utils/providerHealth.js +240 -9
  110. package/dist/lib/utils/providerUtils.js +14 -8
  111. package/dist/lib/utils/toolChoice.d.ts +4 -0
  112. package/dist/lib/utils/toolChoice.js +7 -0
  113. package/dist/neurolink.d.ts +3 -2
  114. package/dist/neurolink.js +260 -494
  115. package/dist/observability/otelBridge.d.ts +2 -2
  116. package/dist/observability/otelBridge.js +12 -3
  117. package/dist/providers/amazonBedrock.js +2 -4
  118. package/dist/providers/anthropic.d.ts +9 -5
  119. package/dist/providers/anthropic.js +19 -14
  120. package/dist/providers/anthropicBaseProvider.d.ts +3 -3
  121. package/dist/providers/anthropicBaseProvider.js +5 -4
  122. package/dist/providers/azureOpenai.d.ts +1 -1
  123. package/dist/providers/azureOpenai.js +5 -4
  124. package/dist/providers/googleAiStudio.js +30 -1
  125. package/dist/providers/googleVertex.js +28 -6
  126. package/dist/providers/huggingFace.d.ts +3 -3
  127. package/dist/providers/huggingFace.js +6 -7
  128. package/dist/providers/litellm.js +41 -29
  129. package/dist/providers/mistral.js +2 -1
  130. package/dist/providers/ollama.js +80 -23
  131. package/dist/providers/openAI.js +3 -2
  132. package/dist/providers/openRouter.js +2 -1
  133. package/dist/providers/openaiCompatible.d.ts +4 -4
  134. package/dist/providers/openaiCompatible.js +4 -3
  135. package/dist/proxy/claudeFormat.d.ts +3 -2
  136. package/dist/proxy/claudeFormat.js +25 -20
  137. package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  138. package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  139. package/dist/proxy/modelRouter.js +3 -0
  140. package/dist/proxy/oauthFetch.d.ts +1 -1
  141. package/dist/proxy/oauthFetch.js +65 -72
  142. package/dist/proxy/proxyConfig.js +44 -24
  143. package/dist/proxy/proxyEnv.d.ts +19 -0
  144. package/dist/proxy/proxyEnv.js +72 -0
  145. package/dist/proxy/proxyFetch.js +50 -4
  146. package/dist/proxy/proxyTracer.d.ts +133 -0
  147. package/dist/proxy/proxyTracer.js +644 -0
  148. package/dist/proxy/rawStreamCapture.d.ts +10 -0
  149. package/dist/proxy/rawStreamCapture.js +82 -0
  150. package/dist/proxy/requestLogger.d.ts +32 -5
  151. package/dist/proxy/requestLogger.js +406 -37
  152. package/dist/proxy/sseInterceptor.d.ts +97 -0
  153. package/dist/proxy/sseInterceptor.js +401 -0
  154. package/dist/proxy/usageStats.d.ts +4 -3
  155. package/dist/proxy/usageStats.js +25 -12
  156. package/dist/rag/chunkers/MarkdownChunker.js +13 -5
  157. package/dist/rag/chunking/markdownChunker.js +15 -6
  158. package/dist/server/routes/claudeProxyRoutes.d.ts +7 -2
  159. package/dist/server/routes/claudeProxyRoutes.js +1737 -508
  160. package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
  161. package/dist/services/server/ai/observability/instrumentation.js +240 -40
  162. package/dist/tasks/backends/bullmqBackend.d.ts +1 -0
  163. package/dist/tasks/backends/bullmqBackend.js +14 -7
  164. package/dist/tasks/store/redisTaskStore.d.ts +1 -0
  165. package/dist/tasks/store/redisTaskStore.js +34 -26
  166. package/dist/tasks/taskManager.d.ts +3 -0
  167. package/dist/tasks/taskManager.js +63 -30
  168. package/dist/telemetry/index.d.ts +2 -1
  169. package/dist/telemetry/index.js +2 -1
  170. package/dist/telemetry/telemetryService.d.ts +3 -0
  171. package/dist/telemetry/telemetryService.js +65 -5
  172. package/dist/types/cli.d.ts +10 -0
  173. package/dist/types/proxyTypes.d.ts +37 -5
  174. package/dist/types/streamTypes.d.ts +25 -3
  175. package/dist/utils/messageBuilder.js +3 -2
  176. package/dist/utils/providerHealth.d.ts +18 -0
  177. package/dist/utils/providerHealth.js +240 -9
  178. package/dist/utils/providerUtils.js +14 -8
  179. package/dist/utils/toolChoice.d.ts +4 -0
  180. package/dist/utils/toolChoice.js +6 -0
  181. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
  182. package/docs/changelog.md +252 -0
  183. package/package.json +17 -1
  184. package/scripts/observability/check-proxy-telemetry.mjs +235 -0
  185. package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
  186. package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
  187. package/scripts/observability/manage-local-openobserve.sh +184 -0
  188. package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
  189. package/scripts/observability/proxy-observability.env.example +23 -0
@@ -1,4 +1,5 @@
1
1
  // src/lib/auth/sessionManager.ts
2
+ import { withTimeout } from "../utils/async/withTimeout.js";
2
3
  import { logger } from "../utils/logger.js";
3
4
  /** Mask an identifier for safe logging: show first 4 chars + "***" */
4
5
  function maskId(id) {
@@ -7,6 +8,7 @@ function maskId(id) {
7
8
  }
8
9
  return `${id.slice(0, 4)}***`;
9
10
  }
11
+ const REDIS_CONNECT_TIMEOUT_MS = 5000;
10
12
  /**
11
13
  * In-memory session storage
12
14
  *
@@ -31,10 +33,12 @@ export class MemorySessionStorage {
31
33
  async set(session) {
32
34
  this.sessions.set(session.id, session);
33
35
  // Track user's sessions
34
- if (!this.userSessions.has(session.user.id)) {
35
- this.userSessions.set(session.user.id, new Set());
36
+ let sessionIds = this.userSessions.get(session.user.id);
37
+ if (!sessionIds) {
38
+ sessionIds = new Set();
39
+ this.userSessions.set(session.user.id, sessionIds);
36
40
  }
37
- this.userSessions.get(session.user.id).add(session.id);
41
+ sessionIds.add(session.id);
38
42
  }
39
43
  async delete(sessionId) {
40
44
  const session = this.sessions.get(sessionId);
@@ -84,7 +88,7 @@ export class MemorySessionStorage {
84
88
  * Redis session storage
85
89
  *
86
90
  * Distributed session storage using Redis. Suitable for multi-instance
87
- * deployments. Requires ioredis or similar Redis client.
91
+ * deployments. Requires the "redis" (node-redis) package.
88
92
  *
89
93
  * Note: Redis client must be provided or configured via environment.
90
94
  */
@@ -111,16 +115,25 @@ export class RedisSessionStorage {
111
115
  async createClient() {
112
116
  try {
113
117
  // Use variable indirection to prevent TypeScript from resolving the module at compile time
114
- const moduleName = "ioredis";
115
- const ioredis = await import(/* @vite-ignore */ moduleName);
116
- const Redis = ioredis.default || ioredis.Redis;
117
- this.client = new Redis(this.redisUrl);
118
- return this.client;
118
+ const moduleName = "redis";
119
+ const redisModule = (await import(
120
+ /* @vite-ignore */ moduleName));
121
+ const client = redisModule.createClient({
122
+ url: this.redisUrl,
123
+ });
124
+ client.on("error", (err) => {
125
+ logger.error("Redis session client error:", err.message);
126
+ });
127
+ await withTimeout(client.connect(), REDIS_CONNECT_TIMEOUT_MS, `Redis session client connect timed out after ${REDIS_CONNECT_TIMEOUT_MS}ms`);
128
+ this.client = client;
129
+ return client;
119
130
  }
120
- catch {
131
+ catch (error) {
121
132
  this.initPromise = null;
122
- logger.error('Redis client (ioredis) not available. Install ioredis package and ensure Redis is reachable when using storage: "redis".');
123
- throw new Error("Redis client not available");
133
+ logger.error('Redis client not available. Ensure the "redis" package is installed and Redis is reachable when using storage: "redis".');
134
+ throw error instanceof Error
135
+ ? error
136
+ : new Error("Redis client not available");
124
137
  }
125
138
  }
126
139
  sessionKey(sessionId) {
@@ -136,6 +149,13 @@ export class RedisSessionStorage {
136
149
  if (!data) {
137
150
  return null;
138
151
  }
152
+ if (typeof data !== "string") {
153
+ logger.warn("Unexpected Redis session payload type", {
154
+ sessionId: maskId(sessionId),
155
+ type: typeof data,
156
+ });
157
+ return null;
158
+ }
139
159
  const session = JSON.parse(data);
140
160
  // Parse dates
141
161
  session.createdAt = new Date(session.createdAt);
@@ -143,7 +163,7 @@ export class RedisSessionStorage {
143
163
  session.expiresAt = new Date(session.expiresAt);
144
164
  }
145
165
  // Check expiration
146
- if (new Date() > session.expiresAt) {
166
+ if (session.expiresAt && new Date() > session.expiresAt) {
147
167
  await this.delete(sessionId);
148
168
  return null;
149
169
  }
@@ -162,9 +182,9 @@ export class RedisSessionStorage {
162
182
  ? Math.max(1, Math.floor((session.expiresAt.getTime() - Date.now()) / 1000))
163
183
  : this.ttl;
164
184
  // Store session
165
- await client.setex(this.sessionKey(session.id), ttlSeconds, JSON.stringify(session));
185
+ await client.setEx(this.sessionKey(session.id), ttlSeconds, JSON.stringify(session));
166
186
  // Track user's sessions
167
- await client.sadd(this.userSessionsKey(session.user.id), session.id);
187
+ await client.sAdd(this.userSessionsKey(session.user.id), session.id);
168
188
  await client.expire(this.userSessionsKey(session.user.id), this.ttl);
169
189
  }
170
190
  catch (error) {
@@ -180,13 +200,21 @@ export class RedisSessionStorage {
180
200
  // recursion for expired sessions.
181
201
  const data = await client.get(this.sessionKey(sessionId));
182
202
  if (data) {
183
- try {
184
- const session = JSON.parse(data);
185
- await client.srem(this.userSessionsKey(session.user.id), sessionId);
203
+ if (typeof data !== "string") {
204
+ logger.warn("Unexpected Redis session payload type during delete", {
205
+ sessionId: maskId(sessionId),
206
+ type: typeof data,
207
+ });
186
208
  }
187
- catch {
188
- // If parsing fails, we still delete the key below
189
- logger.warn(`Failed to parse session data for cleanup: ${maskId(sessionId)}`);
209
+ else {
210
+ try {
211
+ const session = JSON.parse(data);
212
+ await client.sRem(this.userSessionsKey(session.user.id), sessionId);
213
+ }
214
+ catch {
215
+ // If parsing fails, we still delete the key below
216
+ logger.warn(`Failed to parse session data for cleanup: ${maskId(sessionId)}`);
217
+ }
190
218
  }
191
219
  }
192
220
  await client.del(this.sessionKey(sessionId));
@@ -198,7 +226,7 @@ export class RedisSessionStorage {
198
226
  async getUserSessions(userId) {
199
227
  try {
200
228
  const client = await this.getClient();
201
- const sessionIds = await client.smembers(this.userSessionsKey(userId));
229
+ const sessionIds = await client.sMembers(this.userSessionsKey(userId));
202
230
  const sessions = [];
203
231
  for (const sessionId of sessionIds) {
204
232
  const session = await this.get(sessionId);
@@ -216,7 +244,7 @@ export class RedisSessionStorage {
216
244
  async deleteUserSessions(userId) {
217
245
  try {
218
246
  const client = await this.getClient();
219
- const sessionIds = await client.smembers(this.userSessionsKey(userId));
247
+ const sessionIds = await client.sMembers(this.userSessionsKey(userId));
220
248
  for (const sessionId of sessionIds) {
221
249
  await client.del(this.sessionKey(sessionId));
222
250
  }
@@ -232,10 +260,13 @@ export class RedisSessionStorage {
232
260
  // Use SCAN instead of KEYS to avoid blocking Redis in production
233
261
  let cursor = "0";
234
262
  do {
235
- const [nextCursor, keys] = await client.scan(cursor, "MATCH", `${this.prefix}*`, "COUNT", "100");
236
- cursor = nextCursor;
237
- if (keys.length > 0) {
238
- await client.del(...keys);
263
+ const result = await client.scan(cursor, {
264
+ MATCH: `${this.prefix}*`,
265
+ COUNT: 100,
266
+ });
267
+ cursor = result.cursor;
268
+ if (result.keys.length > 0) {
269
+ await client.del(result.keys);
239
270
  }
240
271
  } while (cursor !== "0");
241
272
  }
@@ -186,6 +186,9 @@ export class NeuroLinkLanguageModel {
186
186
  // Drain anything already buffered.
187
187
  while (buffer.length > 0) {
188
188
  const chunk = buffer.shift();
189
+ if (!chunk) {
190
+ break;
191
+ }
189
192
  yield chunk;
190
193
  if (chunk.type === "finish") {
191
194
  return;
@@ -251,10 +251,12 @@ export class SSEClient {
251
251
  * Register event handler
252
252
  */
253
253
  on(event, callback) {
254
- if (!this.eventHandlers.has(event)) {
255
- this.eventHandlers.set(event, new Set());
254
+ let handlers = this.eventHandlers.get(event);
255
+ if (!handlers) {
256
+ handlers = new Set();
257
+ this.eventHandlers.set(event, handlers);
256
258
  }
257
- this.eventHandlers.get(event).add(callback);
259
+ handlers.add(callback);
258
260
  }
259
261
  /**
260
262
  * Remove event handler
@@ -315,7 +317,11 @@ export class SSEClient {
315
317
  });
316
318
  while (!done && !error) {
317
319
  if (events.length > 0) {
318
- yield events.shift();
320
+ const nextEvent = events.shift();
321
+ if (!nextEvent) {
322
+ continue;
323
+ }
324
+ yield nextEvent;
319
325
  }
320
326
  else {
321
327
  await new Promise((resolve) => {
@@ -325,7 +331,11 @@ export class SSEClient {
325
331
  }
326
332
  // Yield remaining events
327
333
  while (events.length > 0) {
328
- yield events.shift();
334
+ const nextEvent = events.shift();
335
+ if (!nextEvent) {
336
+ continue;
337
+ }
338
+ yield nextEvent;
329
339
  }
330
340
  if (error) {
331
341
  throw error;
@@ -545,10 +555,12 @@ export class WebSocketStreamingClient {
545
555
  * Register event handler
546
556
  */
547
557
  on(event, callback) {
548
- if (!this.eventHandlers.has(event)) {
549
- this.eventHandlers.set(event, new Set());
558
+ let handlers = this.eventHandlers.get(event);
559
+ if (!handlers) {
560
+ handlers = new Set();
561
+ this.eventHandlers.set(event, handlers);
550
562
  }
551
- this.eventHandlers.get(event).add(callback);
563
+ handlers.add(callback);
552
564
  }
553
565
  /**
554
566
  * Remove event handler
@@ -591,7 +603,11 @@ export class WebSocketStreamingClient {
591
603
  try {
592
604
  while (!disconnected) {
593
605
  if (messageQueue.length > 0) {
594
- yield messageQueue.shift();
606
+ const nextMessage = messageQueue.shift();
607
+ if (nextMessage === undefined) {
608
+ continue;
609
+ }
610
+ yield nextMessage;
595
611
  }
596
612
  else {
597
613
  await new Promise((resolve) => {
@@ -601,7 +617,11 @@ export class WebSocketStreamingClient {
601
617
  }
602
618
  // Yield remaining messages
603
619
  while (messageQueue.length > 0) {
604
- yield messageQueue.shift();
620
+ const nextMessage = messageQueue.shift();
621
+ if (nextMessage === undefined) {
622
+ continue;
623
+ }
624
+ yield nextMessage;
605
625
  }
606
626
  }
607
627
  finally {
@@ -95,6 +95,7 @@ export class GenerationHandler {
95
95
  };
96
96
  }
97
97
  }
98
+ const prepareStep = options.prepareStep;
98
99
  return await generateText({
99
100
  model,
100
101
  messages,
@@ -103,8 +104,8 @@ export class GenerationHandler {
103
104
  stopWhen: stepCountIs(options.maxSteps ?? DEFAULT_MAX_STEPS),
104
105
  ...(shouldUseTools &&
105
106
  options.toolChoice && { toolChoice: options.toolChoice }),
106
- ...(options.prepareStep && {
107
- experimental_prepareStep: ((stepOptions) => options.prepareStep({
107
+ ...(prepareStep && {
108
+ experimental_prepareStep: ((stepOptions) => prepareStep({
108
109
  ...stepOptions,
109
110
  maxSteps: options.maxSteps ?? DEFAULT_MAX_STEPS,
110
111
  })),
@@ -16,6 +16,7 @@ import { runWithCurrentLangfuseContext } from "../services/server/ai/observabili
16
16
  import { logger } from "../utils/logger.js";
17
17
  import { createRedisClient, deserializeConversation, getNormalizedConfig, getPooledRedisClient, getSessionKey, getUserSessionsKey, releasePooledRedisClient, scanKeys, serializeConversation, } from "../utils/redis.js";
18
18
  const redisTracer = tracers.redis;
19
+ const REDIS_TIMEOUT_MS = 5000;
19
20
  /**
20
21
  * Redis-based implementation of the ConversationMemoryManager
21
22
  * Uses the same interface but stores data in Redis
@@ -130,13 +131,14 @@ export class RedisConversationMemoryManager {
130
131
  if (!this.redisClient) {
131
132
  return undefined;
132
133
  }
134
+ const redisClient = this.redisClient;
133
135
  return redisTracer.startActiveSpan("neurolink.memory.getSession", { kind: SpanKind.CLIENT, attributes: { "session.id": sessionId } }, async (span) => {
134
136
  if (userId) {
135
137
  span.setAttribute("user.id", userId);
136
138
  }
137
139
  try {
138
140
  const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
139
- const conversationData = await this.redisClient.get(redisKey);
141
+ const conversationData = await withTimeout(redisClient.get(redisKey), REDIS_TIMEOUT_MS);
140
142
  const conversation = deserializeConversation(conversationData || null);
141
143
  if (!conversation) {
142
144
  span.setAttribute("session.found", false);
@@ -630,6 +632,7 @@ export class RedisConversationMemoryManager {
630
632
  logger.warn("[RedisConversationMemoryManager] Redis client not available in buildContextMessages");
631
633
  return [];
632
634
  }
635
+ const redisClient = this.redisClient;
633
636
  // NLK-GAP-012: Add span for buildContext CRUD operation
634
637
  return redisTracer.startActiveSpan("neurolink.memory.buildContext", {
635
638
  kind: SpanKind.CLIENT,
@@ -645,7 +648,7 @@ export class RedisConversationMemoryManager {
645
648
  method: "buildContextMessages",
646
649
  });
647
650
  const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
648
- const conversationData = await this.redisClient.get(redisKey);
651
+ const conversationData = await withTimeout(redisClient.get(redisKey), REDIS_TIMEOUT_MS);
649
652
  const conversation = deserializeConversation(conversationData || null);
650
653
  if (!conversation) {
651
654
  span.setAttribute("session.found", false);
@@ -1080,6 +1083,7 @@ User message: "${userMessage}"`;
1080
1083
  if (!this.redisClient) {
1081
1084
  return false;
1082
1085
  }
1086
+ const redisClient = this.redisClient;
1083
1087
  // NLK-GAP-012: Add span for clearSession CRUD operation
1084
1088
  return redisTracer.startActiveSpan("neurolink.memory.clear", {
1085
1089
  kind: SpanKind.CLIENT,
@@ -1090,7 +1094,7 @@ User message: "${userMessage}"`;
1090
1094
  }, async (span) => {
1091
1095
  try {
1092
1096
  const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
1093
- const result = await this.redisClient.del(redisKey);
1097
+ const result = await withTimeout(redisClient.del(redisKey), REDIS_TIMEOUT_MS);
1094
1098
  if (Number(result) > 0) {
1095
1099
  // Remove session from user's session set
1096
1100
  if (userId) {
@@ -6,6 +6,9 @@ import { Evaluator } from "./index.js";
6
6
  import { createBatchEvaluationError, isRetryableEvaluationError, } from "./errors/EvaluationError.js";
7
7
  import { logger } from "../utils/logger.js";
8
8
  import { NeuroLinkFeatureError } from "../core/infrastructure/index.js";
9
+ function hasEvaluationData(result) {
10
+ return result.success && result.data !== undefined;
11
+ }
9
12
  /**
10
13
  * BatchEvaluator - Performs evaluation on multiple items in parallel.
11
14
  * Supports configurable concurrency, retry logic, and progress tracking.
@@ -201,7 +204,7 @@ export class BatchEvaluator {
201
204
  }
202
205
  }
203
206
  // Calculate summary statistics
204
- const successfulResults = results.filter((r) => r.success && r.data);
207
+ const successfulResults = results.filter(hasEvaluationData);
205
208
  const scores = successfulResults.map((r) => r.data.overall);
206
209
  const passingScores = successfulResults.filter((r) => r.data.overall >=
207
210
  (autoEvalConfig.threshold || this.config.threshold || 7));
@@ -41,10 +41,12 @@ export class ObservabilityHooks {
41
41
  * Register an event handler
42
42
  */
43
43
  on(event, handler) {
44
- if (!this._handlers.has(event)) {
45
- this._handlers.set(event, new Set());
44
+ let handlers = this._handlers.get(event);
45
+ if (!handlers) {
46
+ handlers = new Set();
47
+ this._handlers.set(event, handlers);
46
48
  }
47
- this._handlers.get(event).add(handler);
49
+ handlers.add(handler);
48
50
  // Return unsubscribe function
49
51
  return () => {
50
52
  this._handlers.get(event)?.delete(handler);
@@ -12,9 +12,9 @@ export type PipelineExecutionOptions = {
12
12
  correlationId?: string;
13
13
  /** Custom timeout override */
14
14
  timeout?: number;
15
- /** Skip specific scorers */
15
+ /** Skip specific scorers. Mutually exclusive with onlyScorers. */
16
16
  skipScorers?: string[];
17
- /** Only run specific scorers */
17
+ /** Only run specific scorers. Mutually exclusive with skipScorers. */
18
18
  onlyScorers?: string[];
19
19
  /** Additional metadata to attach */
20
20
  metadata?: JsonObject;
@@ -59,6 +59,7 @@ export declare class EvaluationPipeline {
59
59
  * Execute the pipeline on input
60
60
  */
61
61
  execute(input: ScorerInput, options?: PipelineExecutionOptions): Promise<PipelineResult>;
62
+ private _validateExecutionOptions;
62
63
  /**
63
64
  * Get scorers to run based on options
64
65
  */
@@ -91,6 +91,7 @@ export class EvaluationPipeline {
91
91
  if (!this._initialized) {
92
92
  await this.initialize();
93
93
  }
94
+ this._validateExecutionOptions(options);
94
95
  const startTime = Date.now();
95
96
  const correlationId = options?.correlationId ?? `pipeline-${Date.now()}`;
96
97
  logger.debug(`Executing pipeline: ${this._config.name ?? "unnamed"}`, {
@@ -173,16 +174,25 @@ export class EvaluationPipeline {
173
174
  skippedScorers,
174
175
  };
175
176
  }
177
+ _validateExecutionOptions(options) {
178
+ const hasOnlyScorers = !!options?.onlyScorers && options.onlyScorers.length > 0;
179
+ const hasSkipScorers = !!options?.skipScorers && options.skipScorers.length > 0;
180
+ if (hasOnlyScorers && hasSkipScorers) {
181
+ throw new Error("Cannot specify both 'onlyScorers' and 'skipScorers' options");
182
+ }
183
+ }
176
184
  /**
177
185
  * Get scorers to run based on options
178
186
  */
179
187
  _getScorersToRun(options) {
180
188
  const allScorers = Array.from(this._scorers.entries());
181
- if (options?.onlyScorers && options.onlyScorers.length > 0) {
182
- return allScorers.filter(([id]) => options.onlyScorers.includes(id));
189
+ const onlyScorers = options?.onlyScorers;
190
+ const skipScorers = options?.skipScorers;
191
+ if (onlyScorers && onlyScorers.length > 0) {
192
+ return allScorers.filter(([id]) => onlyScorers.includes(id));
183
193
  }
184
- if (options?.skipScorers && options.skipScorers.length > 0) {
185
- return allScorers.filter(([id]) => !options.skipScorers.includes(id));
194
+ if (skipScorers && skipScorers.length > 0) {
195
+ return allScorers.filter(([id]) => !skipScorers.includes(id));
186
196
  }
187
197
  return allScorers;
188
198
  }
@@ -191,11 +201,13 @@ export class EvaluationPipeline {
191
201
  */
192
202
  _getSkippedScorers(options) {
193
203
  const allIds = Array.from(this._scorers.keys());
194
- if (options?.onlyScorers && options.onlyScorers.length > 0) {
195
- return allIds.filter((id) => !options.onlyScorers.includes(id));
204
+ const onlyScorers = options?.onlyScorers;
205
+ const skipScorers = options?.skipScorers;
206
+ if (onlyScorers && onlyScorers.length > 0) {
207
+ return allIds.filter((id) => !onlyScorers.includes(id));
196
208
  }
197
- if (options?.skipScorers && options.skipScorers.length > 0) {
198
- return options.skipScorers.filter((id) => allIds.includes(id));
209
+ if (skipScorers && skipScorers.length > 0) {
210
+ return skipScorers.filter((id) => allIds.includes(id));
199
211
  }
200
212
  return [];
201
213
  }
@@ -2,6 +2,9 @@
2
2
  * @file Batch Strategy
3
3
  * Batch processing for evaluation pipelines
4
4
  */
5
+ function hasPipelineResult(result) {
6
+ return !result.error && result.result !== undefined;
7
+ }
5
8
  /**
6
9
  * Default batch configuration
7
10
  */
@@ -66,7 +69,7 @@ export class BatchStrategy {
66
69
  }
67
70
  // Calculate summary
68
71
  const totalDuration = Date.now() - startTime;
69
- const successfulResults = results.filter((r) => !r.error && r.result);
72
+ const successfulResults = results.filter(hasPipelineResult);
70
73
  const scores = successfulResults.map((r) => r.result.overallScore);
71
74
  const passed = successfulResults.filter((r) => r.result.passed);
72
75
  return {
@@ -192,7 +195,7 @@ export async function* streamBatchEvaluation(pipeline, inputs, config) {
192
195
  durations.push(result.duration);
193
196
  // If continueOnError is false and this result has an error, abort and return summary
194
197
  if (result.error && batchConfig.continueOnError === false) {
195
- const successfulResults = results.filter((r) => !r.error && r.result);
198
+ const successfulResults = results.filter(hasPipelineResult);
196
199
  const earlyScores = successfulResults.map((r) => r.result.overallScore);
197
200
  const earlyPassed = successfulResults.filter((r) => r.result.passed);
198
201
  return {
@@ -219,7 +222,7 @@ export async function* streamBatchEvaluation(pipeline, inputs, config) {
219
222
  }
220
223
  }
221
224
  // Return summary
222
- const successfulResults = results.filter((r) => !r.error && r.result);
225
+ const successfulResults = results.filter(hasPipelineResult);
223
226
  const scores = successfulResults.map((r) => r.result.overallScore);
224
227
  const passed = successfulResults.filter((r) => r.result.passed);
225
228
  return {
@@ -17,6 +17,12 @@ export const DEFAULT_SAMPLING_CONFIG = {
17
17
  maxRate: 1.0,
18
18
  },
19
19
  };
20
+ const DEFAULT_ADAPTIVE_CONFIG = {
21
+ enabled: false,
22
+ qualityThreshold: 0.7,
23
+ minRate: 0.1,
24
+ maxRate: 1.0,
25
+ };
20
26
  /**
21
27
  * Sampling strategy for evaluation
22
28
  */
@@ -27,9 +33,10 @@ export class SamplingStrategy {
27
33
  _maxRecentScores = 100;
28
34
  constructor(config = {}) {
29
35
  const clamp01 = (v) => Math.max(0, Math.min(1, v));
36
+ const defaultAdaptive = DEFAULT_SAMPLING_CONFIG.adaptive ?? DEFAULT_ADAPTIVE_CONFIG;
30
37
  const rawRate = config.rate ?? DEFAULT_SAMPLING_CONFIG.rate;
31
- const rawMinRate = config.adaptive?.minRate ?? DEFAULT_SAMPLING_CONFIG.adaptive.minRate;
32
- const rawMaxRate = config.adaptive?.maxRate ?? DEFAULT_SAMPLING_CONFIG.adaptive.maxRate;
38
+ const rawMinRate = config.adaptive?.minRate ?? defaultAdaptive.minRate;
39
+ const rawMaxRate = config.adaptive?.maxRate ?? defaultAdaptive.maxRate;
33
40
  const minRate = clamp01(Math.min(rawMinRate, rawMaxRate));
34
41
  const maxRate = clamp01(Math.max(rawMinRate, rawMaxRate));
35
42
  this._config = {
@@ -41,9 +48,8 @@ export class SamplingStrategy {
41
48
  ...(config.alwaysEvaluate ?? {}),
42
49
  },
43
50
  adaptive: {
44
- enabled: config.adaptive?.enabled ?? DEFAULT_SAMPLING_CONFIG.adaptive.enabled,
45
- qualityThreshold: clamp01(config.adaptive?.qualityThreshold ??
46
- DEFAULT_SAMPLING_CONFIG.adaptive.qualityThreshold),
51
+ enabled: config.adaptive?.enabled ?? defaultAdaptive.enabled,
52
+ qualityThreshold: clamp01(config.adaptive?.qualityThreshold ?? defaultAdaptive.qualityThreshold),
47
53
  minRate,
48
54
  maxRate,
49
55
  },
@@ -156,9 +162,12 @@ export class SamplingStrategy {
156
162
  */
157
163
  configure(config) {
158
164
  const clamp01 = (v) => Math.max(0, Math.min(1, v));
165
+ const currentAdaptive = this._config.adaptive ??
166
+ DEFAULT_SAMPLING_CONFIG.adaptive ??
167
+ DEFAULT_ADAPTIVE_CONFIG;
159
168
  const rawRate = config.rate ?? this._config.rate;
160
- const rawMinRate = config.adaptive?.minRate ?? this._config.adaptive.minRate;
161
- const rawMaxRate = config.adaptive?.maxRate ?? this._config.adaptive.maxRate;
169
+ const rawMinRate = config.adaptive?.minRate ?? currentAdaptive.minRate;
170
+ const rawMaxRate = config.adaptive?.maxRate ?? currentAdaptive.maxRate;
162
171
  const minRate = clamp01(Math.min(rawMinRate, rawMaxRate));
163
172
  const maxRate = clamp01(Math.max(rawMinRate, rawMaxRate));
164
173
  this._config = {
@@ -170,9 +179,8 @@ export class SamplingStrategy {
170
179
  ...(config.alwaysEvaluate ?? {}),
171
180
  },
172
181
  adaptive: {
173
- enabled: config.adaptive?.enabled ?? this._config.adaptive.enabled,
174
- qualityThreshold: clamp01(config.adaptive?.qualityThreshold ??
175
- this._config.adaptive.qualityThreshold),
182
+ enabled: config.adaptive?.enabled ?? currentAdaptive.enabled,
183
+ qualityThreshold: clamp01(config.adaptive?.qualityThreshold ?? currentAdaptive.qualityThreshold),
176
184
  minRate,
177
185
  maxRate,
178
186
  },
@@ -13,8 +13,9 @@ import type { MCPToolAnnotations } from "./mcp/toolAnnotations.js";
13
13
  import { MCPToolRegistry } from "./mcp/toolRegistry.js";
14
14
  import type { MetricsSummary, TraceView } from "./observability/metricsAggregator.js";
15
15
  import type { SpanData } from "./observability/types/spanTypes.js";
16
- import type { JsonObject, NeuroLinkEvents, TypedEventEmitter } from "./types/common.js";
16
+ import { TaskManager } from "./tasks/taskManager.js";
17
17
  import type { AuthenticatedContext, MastraAuthProvider } from "./types/authTypes.js";
18
+ import type { JsonObject, NeuroLinkEvents, TypedEventEmitter } from "./types/common.js";
18
19
  import type { MCPEnhancementsConfig, NeuroLinkAuthConfig, NeurolinkConstructorConfig } from "./types/configTypes.js";
19
20
  import type { ChatMessage } from "./types/conversation.js";
20
21
  import type { ExternalMCPOperationResult, ExternalMCPServerInstance, ExternalMCPToolInfo } from "./types/externalMcp.js";
@@ -25,7 +26,6 @@ import type { ObservabilityConfig } from "./types/observability.js";
25
26
  import type { StreamOptions, StreamResult } from "./types/streamTypes.js";
26
27
  import type { ToolExecutionContext, ToolExecutionSummary, ToolInfo, ToolRegistrationOptions } from "./types/tools.js";
27
28
  import type { BatchOperationResult } from "./types/typeAliases.js";
28
- import { TaskManager } from "./tasks/taskManager.js";
29
29
  export declare class NeuroLink {
30
30
  private mcpInitialized;
31
31
  private mcpSkipped;
@@ -1909,6 +1909,7 @@ export declare class NeuroLink {
1909
1909
  * @param config - Auth provider or configuration to create one
1910
1910
  */
1911
1911
  setAuthProvider(config: NeuroLinkAuthConfig): Promise<void>;
1912
+ private initializeAuthProviderFromConfig;
1912
1913
  /**
1913
1914
  * Get the currently configured authentication provider
1914
1915
  */