@juspay/neurolink 9.40.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 (224) hide show
  1. package/CHANGELOG.md +12 -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 +471 -445
  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.d.ts +56 -0
  15. package/dist/cli/commands/task.js +838 -0
  16. package/dist/cli/factories/commandFactory.d.ts +2 -0
  17. package/dist/cli/factories/commandFactory.js +38 -0
  18. package/dist/cli/parser.js +8 -4
  19. package/dist/client/aiSdkAdapter.js +3 -0
  20. package/dist/client/streamingClient.js +30 -10
  21. package/dist/core/modules/GenerationHandler.js +3 -2
  22. package/dist/core/redisConversationMemoryManager.js +7 -3
  23. package/dist/evaluation/BatchEvaluator.js +4 -1
  24. package/dist/evaluation/hooks/observabilityHooks.js +5 -3
  25. package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  26. package/dist/evaluation/pipeline/evaluationPipeline.js +20 -8
  27. package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  28. package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  29. package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
  30. package/dist/lib/auth/anthropicOAuth.js +137 -4
  31. package/dist/lib/auth/providers/firebase.js +5 -1
  32. package/dist/lib/auth/providers/jwt.js +5 -1
  33. package/dist/lib/auth/providers/workos.js +5 -1
  34. package/dist/lib/auth/sessionManager.d.ts +1 -1
  35. package/dist/lib/auth/sessionManager.js +58 -27
  36. package/dist/lib/client/aiSdkAdapter.js +3 -0
  37. package/dist/lib/client/streamingClient.js +30 -10
  38. package/dist/lib/core/modules/GenerationHandler.js +3 -2
  39. package/dist/lib/core/redisConversationMemoryManager.js +7 -3
  40. package/dist/lib/evaluation/BatchEvaluator.js +4 -1
  41. package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
  42. package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  43. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +20 -8
  44. package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  45. package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  46. package/dist/lib/neurolink.d.ts +18 -1
  47. package/dist/lib/neurolink.js +367 -484
  48. package/dist/lib/observability/otelBridge.d.ts +2 -2
  49. package/dist/lib/observability/otelBridge.js +12 -3
  50. package/dist/lib/providers/amazonBedrock.js +2 -4
  51. package/dist/lib/providers/anthropic.d.ts +9 -5
  52. package/dist/lib/providers/anthropic.js +19 -14
  53. package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
  54. package/dist/lib/providers/anthropicBaseProvider.js +5 -4
  55. package/dist/lib/providers/azureOpenai.d.ts +1 -1
  56. package/dist/lib/providers/azureOpenai.js +5 -4
  57. package/dist/lib/providers/googleAiStudio.js +30 -1
  58. package/dist/lib/providers/googleVertex.js +28 -6
  59. package/dist/lib/providers/huggingFace.d.ts +3 -3
  60. package/dist/lib/providers/huggingFace.js +6 -8
  61. package/dist/lib/providers/litellm.js +41 -29
  62. package/dist/lib/providers/mistral.js +2 -1
  63. package/dist/lib/providers/ollama.js +80 -23
  64. package/dist/lib/providers/openAI.js +3 -2
  65. package/dist/lib/providers/openRouter.js +2 -1
  66. package/dist/lib/providers/openaiCompatible.d.ts +4 -4
  67. package/dist/lib/providers/openaiCompatible.js +4 -4
  68. package/dist/lib/proxy/claudeFormat.d.ts +3 -2
  69. package/dist/lib/proxy/claudeFormat.js +25 -20
  70. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  71. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  72. package/dist/lib/proxy/modelRouter.js +3 -0
  73. package/dist/lib/proxy/oauthFetch.d.ts +1 -1
  74. package/dist/lib/proxy/oauthFetch.js +65 -72
  75. package/dist/lib/proxy/proxyConfig.js +44 -24
  76. package/dist/lib/proxy/proxyEnv.d.ts +19 -0
  77. package/dist/lib/proxy/proxyEnv.js +73 -0
  78. package/dist/lib/proxy/proxyFetch.js +50 -4
  79. package/dist/lib/proxy/proxyTracer.d.ts +133 -0
  80. package/dist/lib/proxy/proxyTracer.js +645 -0
  81. package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
  82. package/dist/lib/proxy/rawStreamCapture.js +83 -0
  83. package/dist/lib/proxy/requestLogger.d.ts +32 -5
  84. package/dist/lib/proxy/requestLogger.js +406 -37
  85. package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
  86. package/dist/lib/proxy/sseInterceptor.js +402 -0
  87. package/dist/lib/proxy/usageStats.d.ts +4 -3
  88. package/dist/lib/proxy/usageStats.js +25 -12
  89. package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
  90. package/dist/lib/rag/chunking/markdownChunker.js +15 -6
  91. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +7 -2
  92. package/dist/lib/server/routes/claudeProxyRoutes.js +1737 -508
  93. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
  94. package/dist/lib/services/server/ai/observability/instrumentation.js +240 -40
  95. package/dist/lib/tasks/backends/bullmqBackend.d.ts +33 -0
  96. package/dist/lib/tasks/backends/bullmqBackend.js +196 -0
  97. package/dist/lib/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
  98. package/dist/lib/tasks/backends/nodeTimeoutBackend.js +141 -0
  99. package/dist/lib/tasks/backends/taskBackendRegistry.d.ts +31 -0
  100. package/dist/lib/tasks/backends/taskBackendRegistry.js +66 -0
  101. package/dist/lib/tasks/errors.d.ts +31 -0
  102. package/dist/lib/tasks/errors.js +18 -0
  103. package/dist/lib/tasks/store/fileTaskStore.d.ts +43 -0
  104. package/dist/lib/tasks/store/fileTaskStore.js +179 -0
  105. package/dist/lib/tasks/store/redisTaskStore.d.ts +43 -0
  106. package/dist/lib/tasks/store/redisTaskStore.js +197 -0
  107. package/dist/lib/tasks/taskExecutor.d.ts +21 -0
  108. package/dist/lib/tasks/taskExecutor.js +166 -0
  109. package/dist/lib/tasks/taskManager.d.ts +63 -0
  110. package/dist/lib/tasks/taskManager.js +426 -0
  111. package/dist/lib/tasks/tools/taskTools.d.ts +135 -0
  112. package/dist/lib/tasks/tools/taskTools.js +274 -0
  113. package/dist/lib/telemetry/index.d.ts +2 -1
  114. package/dist/lib/telemetry/index.js +2 -1
  115. package/dist/lib/telemetry/telemetryService.d.ts +3 -0
  116. package/dist/lib/telemetry/telemetryService.js +65 -5
  117. package/dist/lib/types/cli.d.ts +10 -0
  118. package/dist/lib/types/configTypes.d.ts +3 -0
  119. package/dist/lib/types/generateTypes.d.ts +13 -0
  120. package/dist/lib/types/index.d.ts +1 -0
  121. package/dist/lib/types/proxyTypes.d.ts +37 -5
  122. package/dist/lib/types/streamTypes.d.ts +25 -3
  123. package/dist/lib/types/taskTypes.d.ts +275 -0
  124. package/dist/lib/types/taskTypes.js +37 -0
  125. package/dist/lib/utils/messageBuilder.js +3 -2
  126. package/dist/lib/utils/providerHealth.d.ts +18 -0
  127. package/dist/lib/utils/providerHealth.js +240 -9
  128. package/dist/lib/utils/providerUtils.js +14 -8
  129. package/dist/lib/utils/toolChoice.d.ts +4 -0
  130. package/dist/lib/utils/toolChoice.js +7 -0
  131. package/dist/neurolink.d.ts +18 -1
  132. package/dist/neurolink.js +367 -484
  133. package/dist/observability/otelBridge.d.ts +2 -2
  134. package/dist/observability/otelBridge.js +12 -3
  135. package/dist/providers/amazonBedrock.js +2 -4
  136. package/dist/providers/anthropic.d.ts +9 -5
  137. package/dist/providers/anthropic.js +19 -14
  138. package/dist/providers/anthropicBaseProvider.d.ts +3 -3
  139. package/dist/providers/anthropicBaseProvider.js +5 -4
  140. package/dist/providers/azureOpenai.d.ts +1 -1
  141. package/dist/providers/azureOpenai.js +5 -4
  142. package/dist/providers/googleAiStudio.js +30 -1
  143. package/dist/providers/googleVertex.js +28 -6
  144. package/dist/providers/huggingFace.d.ts +3 -3
  145. package/dist/providers/huggingFace.js +6 -7
  146. package/dist/providers/litellm.js +41 -29
  147. package/dist/providers/mistral.js +2 -1
  148. package/dist/providers/ollama.js +80 -23
  149. package/dist/providers/openAI.js +3 -2
  150. package/dist/providers/openRouter.js +2 -1
  151. package/dist/providers/openaiCompatible.d.ts +4 -4
  152. package/dist/providers/openaiCompatible.js +4 -3
  153. package/dist/proxy/claudeFormat.d.ts +3 -2
  154. package/dist/proxy/claudeFormat.js +25 -20
  155. package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  156. package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  157. package/dist/proxy/modelRouter.js +3 -0
  158. package/dist/proxy/oauthFetch.d.ts +1 -1
  159. package/dist/proxy/oauthFetch.js +65 -72
  160. package/dist/proxy/proxyConfig.js +44 -24
  161. package/dist/proxy/proxyEnv.d.ts +19 -0
  162. package/dist/proxy/proxyEnv.js +72 -0
  163. package/dist/proxy/proxyFetch.js +50 -4
  164. package/dist/proxy/proxyTracer.d.ts +133 -0
  165. package/dist/proxy/proxyTracer.js +644 -0
  166. package/dist/proxy/rawStreamCapture.d.ts +10 -0
  167. package/dist/proxy/rawStreamCapture.js +82 -0
  168. package/dist/proxy/requestLogger.d.ts +32 -5
  169. package/dist/proxy/requestLogger.js +406 -37
  170. package/dist/proxy/sseInterceptor.d.ts +97 -0
  171. package/dist/proxy/sseInterceptor.js +401 -0
  172. package/dist/proxy/usageStats.d.ts +4 -3
  173. package/dist/proxy/usageStats.js +25 -12
  174. package/dist/rag/chunkers/MarkdownChunker.js +13 -5
  175. package/dist/rag/chunking/markdownChunker.js +15 -6
  176. package/dist/server/routes/claudeProxyRoutes.d.ts +7 -2
  177. package/dist/server/routes/claudeProxyRoutes.js +1737 -508
  178. package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
  179. package/dist/services/server/ai/observability/instrumentation.js +240 -40
  180. package/dist/tasks/backends/bullmqBackend.d.ts +33 -0
  181. package/dist/tasks/backends/bullmqBackend.js +195 -0
  182. package/dist/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
  183. package/dist/tasks/backends/nodeTimeoutBackend.js +140 -0
  184. package/dist/tasks/backends/taskBackendRegistry.d.ts +31 -0
  185. package/dist/tasks/backends/taskBackendRegistry.js +65 -0
  186. package/dist/tasks/errors.d.ts +31 -0
  187. package/dist/tasks/errors.js +17 -0
  188. package/dist/tasks/store/fileTaskStore.d.ts +43 -0
  189. package/dist/tasks/store/fileTaskStore.js +178 -0
  190. package/dist/tasks/store/redisTaskStore.d.ts +43 -0
  191. package/dist/tasks/store/redisTaskStore.js +196 -0
  192. package/dist/tasks/taskExecutor.d.ts +21 -0
  193. package/dist/tasks/taskExecutor.js +165 -0
  194. package/dist/tasks/taskManager.d.ts +63 -0
  195. package/dist/tasks/taskManager.js +425 -0
  196. package/dist/tasks/tools/taskTools.d.ts +135 -0
  197. package/dist/tasks/tools/taskTools.js +273 -0
  198. package/dist/telemetry/index.d.ts +2 -1
  199. package/dist/telemetry/index.js +2 -1
  200. package/dist/telemetry/telemetryService.d.ts +3 -0
  201. package/dist/telemetry/telemetryService.js +65 -5
  202. package/dist/types/cli.d.ts +10 -0
  203. package/dist/types/configTypes.d.ts +3 -0
  204. package/dist/types/generateTypes.d.ts +13 -0
  205. package/dist/types/index.d.ts +1 -0
  206. package/dist/types/proxyTypes.d.ts +37 -5
  207. package/dist/types/streamTypes.d.ts +25 -3
  208. package/dist/types/taskTypes.d.ts +275 -0
  209. package/dist/types/taskTypes.js +36 -0
  210. package/dist/utils/messageBuilder.js +3 -2
  211. package/dist/utils/providerHealth.d.ts +18 -0
  212. package/dist/utils/providerHealth.js +240 -9
  213. package/dist/utils/providerUtils.js +14 -8
  214. package/dist/utils/toolChoice.d.ts +4 -0
  215. package/dist/utils/toolChoice.js +6 -0
  216. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
  217. package/docs/changelog.md +252 -0
  218. package/package.json +19 -1
  219. package/scripts/observability/check-proxy-telemetry.mjs +235 -0
  220. package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
  221. package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
  222. package/scripts/observability/manage-local-openobserve.sh +184 -0
  223. package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
  224. package/scripts/observability/proxy-observability.env.example +23 -0
package/dist/neurolink.js CHANGED
@@ -22,7 +22,7 @@ import pLimit from "p-limit";
22
22
  import { ErrorCategory, ErrorSeverity } from "./constants/enums.js";
23
23
  import { CIRCUIT_BREAKER, CIRCUIT_BREAKER_RESET_MS, MEMORY_THRESHOLDS, NANOSECOND_TO_MS_DIVISOR, PERFORMANCE_THRESHOLDS, PROVIDER_TIMEOUTS, RETRY_ATTEMPTS, RETRY_DELAYS, TOOL_TIMEOUTS, } from "./constants/index.js";
24
24
  import { checkContextBudget } from "./context/budgetChecker.js";
25
- import { ContextCompactor, } from "./context/contextCompactor.js";
25
+ import { ContextCompactor } from "./context/contextCompactor.js";
26
26
  import { emergencyContentTruncation } from "./context/emergencyTruncation.js";
27
27
  import { getContextOverflowProvider, isContextOverflowError, parseProviderOverflowDetails, } from "./context/errorDetection.js";
28
28
  import { ContextBudgetExceededError } from "./context/errors.js";
@@ -44,24 +44,27 @@ import { ToolRouter } from "./mcp/routing/index.js";
44
44
  import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
45
45
  import { inferAnnotations, isSafeToRetry } from "./mcp/toolAnnotations.js";
46
46
  import { MCPToolRegistry } from "./mcp/toolRegistry.js";
47
- import { initializeHippocampus, } from "./memory/hippocampusInitializer.js";
47
+ import { initializeHippocampus } from "./memory/hippocampusInitializer.js";
48
48
  import { createMemoryRetrievalTools } from "./memory/memoryRetrievalTools.js";
49
- import { getMetricsAggregator, MetricsAggregator, } from "./observability/metricsAggregator.js";
49
+ import { getMetricsAggregator, MetricsAggregator } from "./observability/metricsAggregator.js";
50
50
  import { SpanStatus, SpanType } from "./observability/types/spanTypes.js";
51
51
  import { SpanSerializer } from "./observability/utils/spanSerializer.js";
52
52
  import { flushOpenTelemetry, getLangfuseHealthStatus, initializeOpenTelemetry, isOpenTelemetryInitialized, setLangfuseContext, shutdownOpenTelemetry, } from "./services/server/ai/observability/instrumentation.js";
53
+ import { TaskManager } from "./tasks/taskManager.js";
54
+ import { createTaskTools } from "./tasks/tools/taskTools.js";
53
55
  import { ATTR } from "./telemetry/attributes.js";
54
56
  import { tracers } from "./telemetry/tracers.js";
57
+ import { CircuitBreakerOpenError } from "./types/circuitBreakerErrors.js";
55
58
  import { ConversationMemoryError } from "./types/conversation.js";
56
- import { AuthenticationError, AuthorizationError, InvalidModelError, } from "./types/errors.js";
57
- import { getConversationMessages, storeConversationTurn, } from "./utils/conversationMemory.js";
59
+ import { AuthenticationError, AuthorizationError, InvalidModelError } from "./types/errors.js";
60
+ import { getConversationMessages, storeConversationTurn } from "./utils/conversationMemory.js";
58
61
  // Enhanced error handling imports
59
62
  import { CircuitBreaker, ERROR_CODES, ErrorFactory, isAbortError, isRetriableError, logStructuredError, NeuroLinkError, withRetry, withTimeout, } from "./utils/errorHandling.js";
60
- import { CircuitBreakerOpenError } from "./types/circuitBreakerErrors.js";
61
63
  // Factory processing imports
62
64
  import { createCleanStreamOptions, enhanceTextGenerationOptions, processFactoryOptions, processStreamingFactoryOptions, validateFactoryConfig, } from "./utils/factoryProcessing.js";
63
65
  import { logger, mcpLogger } from "./utils/logger.js";
64
- import { createCustomToolServerInfo, detectCategory, } from "./utils/mcpDefaults.js";
66
+ import { createCustomToolServerInfo, detectCategory } from "./utils/mcpDefaults.js";
67
+ import { resolveModel } from "./utils/modelAliasResolver.js";
65
68
  // Import orchestration components
66
69
  import { ModelRouter } from "./utils/modelRouter.js";
67
70
  import { getBestProvider } from "./utils/providerUtils.js";
@@ -72,7 +75,6 @@ import { BinaryTaskClassifier } from "./utils/taskClassifier.js";
72
75
  // Transformation utilities
73
76
  import { extractToolNames, optimizeToolForCollection, transformAvailableTools, transformParamsForLogging, transformToolExecutions, transformToolExecutionsForMCP, transformToolsForMCP, transformToolsToDescriptions, transformToolsToExpectedFormat, } from "./utils/transformationUtils.js";
74
77
  import { isNonNullObject } from "./utils/typeUtils.js";
75
- import { resolveModel } from "./utils/modelAliasResolver.js";
76
78
  import { getWorkflow } from "./workflow/core/workflowRegistry.js";
77
79
  import { runWorkflow } from "./workflow/core/workflowRunner.js";
78
80
  /**
@@ -95,9 +97,7 @@ function classifyMcpErrorMessage(text) {
95
97
  lower.includes("access denied")) {
96
98
  return "permission_denied";
97
99
  }
98
- if (lower.includes("timeout") ||
99
- lower.includes("timed out") ||
100
- lower.includes("deadline exceeded")) {
100
+ if (lower.includes("timeout") || lower.includes("timed out") || lower.includes("deadline exceeded")) {
101
101
  return "timeout";
102
102
  }
103
103
  if (lower.includes("rate limit") ||
@@ -154,11 +154,7 @@ function isNonRetryableProviderError(error) {
154
154
  // Check for HTTP status codes on error objects (e.g., from Vercel AI SDK)
155
155
  if (error && typeof error === "object") {
156
156
  const err = error;
157
- const status = typeof err.status === "number"
158
- ? err.status
159
- : typeof err.statusCode === "number"
160
- ? err.statusCode
161
- : undefined;
157
+ const status = typeof err.status === "number" ? err.status : typeof err.statusCode === "number" ? err.statusCode : undefined;
162
158
  if (status && NON_RETRYABLE_HTTP_STATUS_CODES.includes(status)) {
163
159
  return true;
164
160
  }
@@ -187,6 +183,9 @@ export class NeuroLink {
187
183
  mcpSkipped = false;
188
184
  mcpInitPromise = null;
189
185
  emitter = new EventEmitter();
186
+ // TaskManager — lazy-initialized on first access via `this.tasks`
187
+ _taskManager;
188
+ _taskManagerConfig;
190
189
  toolRegistry;
191
190
  autoDiscoveredServerInfos = [];
192
191
  // External MCP server management
@@ -201,8 +200,7 @@ export class NeuroLink {
201
200
  lastCompactionMessageCount = new Map();
202
201
  /** Extract sessionId from options context for compaction watermark keying */
203
202
  getCompactionSessionId(options) {
204
- return (options.context
205
- ?.sessionId || "__default__");
203
+ return options.context?.sessionId || "__default__";
206
204
  }
207
205
  // MCP Enhancement modules - wired into core execution path
208
206
  mcpToolResultCache;
@@ -265,28 +263,19 @@ export class NeuroLink {
265
263
  * Extract and set Langfuse context from options with proper async scoping
266
264
  */
267
265
  async setLangfuseContextFromOptions(options, callback) {
268
- if (options.context &&
269
- typeof options.context === "object" &&
270
- options.context !== null) {
266
+ if (options.context && typeof options.context === "object" && options.context !== null) {
271
267
  let callbackExecuted = false;
272
268
  try {
273
269
  const ctx = options.context;
274
270
  // Trigger context scoping if any meaningful Langfuse field is present
275
- if (ctx.userId ||
276
- ctx.sessionId ||
277
- ctx.conversationId ||
278
- ctx.requestId ||
279
- ctx.traceName ||
280
- ctx.metadata) {
271
+ if (ctx.userId || ctx.sessionId || ctx.conversationId || ctx.requestId || ctx.traceName || ctx.metadata) {
281
272
  // Build customAttributes from top-level metadata string/number/boolean fields
282
273
  let customAttributes;
283
274
  if (ctx.metadata && typeof ctx.metadata === "object") {
284
275
  const metaObj = ctx.metadata;
285
276
  const attrs = {};
286
277
  for (const [k, v] of Object.entries(metaObj)) {
287
- if (typeof v === "string" ||
288
- typeof v === "number" ||
289
- typeof v === "boolean") {
278
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
290
279
  attrs[k] = v;
291
280
  }
292
281
  }
@@ -298,14 +287,10 @@ export class NeuroLink {
298
287
  setLangfuseContext({
299
288
  userId: typeof ctx.userId === "string" ? ctx.userId : null,
300
289
  sessionId: typeof ctx.sessionId === "string" ? ctx.sessionId : null,
301
- conversationId: typeof ctx.conversationId === "string"
302
- ? ctx.conversationId
303
- : null,
290
+ conversationId: typeof ctx.conversationId === "string" ? ctx.conversationId : null,
304
291
  requestId: typeof ctx.requestId === "string" ? ctx.requestId : null,
305
292
  traceName: typeof ctx.traceName === "string" ? ctx.traceName : null,
306
- metadata: ctx.metadata && typeof ctx.metadata === "object"
307
- ? ctx.metadata
308
- : null,
293
+ metadata: ctx.metadata && typeof ctx.metadata === "object" ? ctx.metadata : null,
309
294
  ...(customAttributes !== undefined && { customAttributes }),
310
295
  }, async () => {
311
296
  try {
@@ -439,9 +424,7 @@ export class NeuroLink {
439
424
  logger.setEventEmitter(this.emitter);
440
425
  // Read tool cache duration from environment variables, with a default
441
426
  const cacheDurationEnv = process.env.NEUROLINK_TOOL_CACHE_DURATION;
442
- this.toolCacheDuration = cacheDurationEnv
443
- ? parseInt(cacheDurationEnv, 10)
444
- : 20000;
427
+ this.toolCacheDuration = cacheDurationEnv ? parseInt(cacheDurationEnv, 10) : 20000;
445
428
  const constructorStartTime = Date.now();
446
429
  const constructorHrTimeStart = process.hrtime.bigint();
447
430
  const constructorId = `neurolink-constructor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
@@ -459,6 +442,28 @@ export class NeuroLink {
459
442
  if (config?.auth) {
460
443
  this.pendingAuthConfig = config.auth;
461
444
  }
445
+ // Store task config for lazy initialization
446
+ this._taskManagerConfig = config?.tasks;
447
+ // Eagerly create TaskManager and register tools if config is provided
448
+ if (this._taskManagerConfig) {
449
+ this._taskManager = new TaskManager(this, this._taskManagerConfig);
450
+ this._taskManager.setEmitter(this.emitter);
451
+ this.registerTaskTools(this._taskManager);
452
+ }
453
+ }
454
+ /**
455
+ * TaskManager — scheduled and self-running tasks.
456
+ * Lazy-initialized on first access. Configurable via constructor `tasks` option.
457
+ * The actual async initialization (Redis connect, backend start) happens
458
+ * lazily inside TaskManager on first operation.
459
+ */
460
+ get tasks() {
461
+ if (!this._taskManager) {
462
+ this._taskManager = new TaskManager(this, this._taskManagerConfig);
463
+ this._taskManager.setEmitter(this.emitter);
464
+ this.registerTaskTools(this._taskManager);
465
+ }
466
+ return this._taskManager;
462
467
  }
463
468
  /**
464
469
  * Initialize provider registry with security settings
@@ -716,6 +721,52 @@ export class NeuroLink {
716
721
  logger.debug(`[NeuroLink] Registered ${Object.keys(fileTools).length} file reference tools`);
717
722
  });
718
723
  }
724
+ /**
725
+ * Register task management tools bound to a TaskManager instance.
726
+ * Follows the same factory + registry pattern as registerFileTools().
727
+ * Called when TaskManager is created (eagerly or lazily via the `tasks` getter).
728
+ */
729
+ registerTaskTools(manager) {
730
+ const taskTools = createTaskTools(manager);
731
+ for (const [toolName, toolDef] of Object.entries(taskTools)) {
732
+ const toolId = `direct.${toolName}`;
733
+ const toolInfo = {
734
+ name: toolName,
735
+ description: toolDef.description || `Task tool: ${toolName}`,
736
+ inputSchema: {},
737
+ serverId: "direct",
738
+ category: "built-in",
739
+ };
740
+ // registerTool is async but its core logic is synchronous (Map.set).
741
+ // We fire-and-forget here but tools are available immediately after
742
+ // the synchronous validation + map insertion completes.
743
+ void this.toolRegistry.registerTool(toolId, toolInfo, {
744
+ execute: async (params) => {
745
+ try {
746
+ const result = await toolDef.execute(params, {
747
+ toolCallId: "task-tool",
748
+ messages: [],
749
+ });
750
+ return {
751
+ success: true,
752
+ data: result,
753
+ metadata: { toolName, serverId: "direct", executionTime: 0 },
754
+ };
755
+ }
756
+ catch (error) {
757
+ return {
758
+ success: false,
759
+ error: error instanceof Error ? error.message : String(error),
760
+ metadata: { toolName, serverId: "direct", executionTime: 0 },
761
+ };
762
+ }
763
+ },
764
+ description: toolDef.description,
765
+ inputSchema: {},
766
+ });
767
+ }
768
+ logger.debug(`[NeuroLink] Registered ${Object.keys(taskTools).length} task tools`);
769
+ }
719
770
  /**
720
771
  * Register memory retrieval tools that allow the AI to access
721
772
  * conversation history, including full tool outputs.
@@ -728,9 +779,7 @@ export class NeuroLink {
728
779
  // memory manager supports getSessionRaw.
729
780
  const memConfig = this.conversationMemoryConfig?.conversationMemory;
730
781
  const hasRedisConfig = !!memConfig?.redisConfig ||
731
- (memConfig &&
732
- "redis" in memConfig &&
733
- !!memConfig.redis) ||
782
+ (memConfig && "redis" in memConfig && !!memConfig.redis) ||
734
783
  process.env.STORAGE_TYPE === "redis";
735
784
  if (!memConfig?.enabled || !hasRedisConfig) {
736
785
  logger.debug("[NeuroLink] Skipping memory retrieval tools — requires Redis conversation memory");
@@ -761,13 +810,8 @@ export class NeuroLink {
761
810
  messages: [],
762
811
  });
763
812
  // Check if the tool itself reported an error
764
- const hasError = result &&
765
- typeof result === "object" &&
766
- "error" in result &&
767
- !("messages" in result);
768
- const errorMsg = hasError
769
- ? result.error
770
- : undefined;
813
+ const hasError = result && typeof result === "object" && "error" in result && !("messages" in result);
814
+ const errorMsg = hasError ? result.error : undefined;
771
815
  return {
772
816
  success: !hasError,
773
817
  data: result,
@@ -844,8 +888,7 @@ Current user's request: ${currentInput}`;
844
888
  * Respects both the global memory SDK config and per-call overrides.
845
889
  */
846
890
  shouldReadMemory(perCallMemory, userId) {
847
- if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled ||
848
- !userId) {
891
+ if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled || !userId) {
849
892
  return false;
850
893
  }
851
894
  if (perCallMemory?.enabled === false) {
@@ -861,8 +904,7 @@ Current user's request: ${currentInput}`;
861
904
  * Respects both the global memory SDK config and per-call overrides.
862
905
  */
863
906
  shouldWriteMemory(perCallMemory, userId, content) {
864
- if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled ||
865
- !userId) {
907
+ if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled || !userId) {
866
908
  return false;
867
909
  }
868
910
  if (!content?.trim()) {
@@ -936,9 +978,7 @@ Current user's request: ${currentInput}`;
936
978
  const writeOps = [client.add(userId, content)];
937
979
  const writableAdditional = (additionalUsers || []).filter((u) => u.write !== false);
938
980
  for (const user of writableAdditional) {
939
- const addOptions = user.prompt || user.maxWords
940
- ? { prompt: user.prompt, maxWords: user.maxWords }
941
- : undefined;
981
+ const addOptions = user.prompt || user.maxWords ? { prompt: user.prompt, maxWords: user.maxWords } : undefined;
942
982
  writeOps.push(client.add(user.userId, content, addOptions));
943
983
  }
944
984
  await Promise.all(writeOps);
@@ -1097,8 +1137,7 @@ Current user's request: ${currentInput}`;
1097
1137
  try {
1098
1138
  const langfuseConfig = this.observabilityConfig?.langfuse;
1099
1139
  // Check if we should use external provider mode - bypass enabled check
1100
- const useExternalProvider = langfuseConfig?.autoDetectExternalProvider === true ||
1101
- langfuseConfig?.useExternalTracerProvider === true;
1140
+ const useExternalProvider = langfuseConfig?.autoDetectExternalProvider === true || langfuseConfig?.useExternalTracerProvider === true;
1102
1141
  if (langfuseConfig?.enabled || useExternalProvider) {
1103
1142
  logger.debug(`[NeuroLink] 📊 LOG_POINT_C019_LANGFUSE_INIT_START`, {
1104
1143
  logPoint: "C019_LANGFUSE_INIT_START",
@@ -1113,9 +1152,7 @@ Current user's request: ${currentInput}`;
1113
1152
  initializeOpenTelemetry(langfuseConfig);
1114
1153
  const healthStatus = getLangfuseHealthStatus();
1115
1154
  const langfuseInitDurationNs = process.hrtime.bigint() - langfuseInitStartTime;
1116
- if (healthStatus.initialized &&
1117
- healthStatus.hasProcessor &&
1118
- healthStatus.isHealthy) {
1155
+ if (healthStatus.initialized && healthStatus.hasProcessor && healthStatus.isHealthy) {
1119
1156
  logger.debug(`[NeuroLink] ✅ LOG_POINT_C020_LANGFUSE_INIT_SUCCESS`, {
1120
1157
  logPoint: "C020_LANGFUSE_INIT_SUCCESS",
1121
1158
  constructorId,
@@ -1391,9 +1428,7 @@ Current user's request: ${currentInput}`;
1391
1428
  }
1392
1429
  catch (configError) {
1393
1430
  mcpLogger.warn("[NeuroLink] MCP configuration loading failed", {
1394
- error: configError instanceof Error
1395
- ? configError.message
1396
- : String(configError),
1431
+ error: configError instanceof Error ? configError.message : String(configError),
1397
1432
  });
1398
1433
  }
1399
1434
  }
@@ -1518,9 +1553,7 @@ Current user's request: ${currentInput}`;
1518
1553
  taskType: classification.type,
1519
1554
  routedProvider: route.provider,
1520
1555
  routedModel: route.model,
1521
- reason: error instanceof Error
1522
- ? error.message
1523
- : "Ollama service check failed",
1556
+ reason: error instanceof Error ? error.message : "Ollama service check failed",
1524
1557
  orchestrationTime: `${Date.now() - startTime}ms`,
1525
1558
  });
1526
1559
  return {}; // Return empty object to preserve existing fallback behavior
@@ -1656,9 +1689,7 @@ Current user's request: ${currentInput}`;
1656
1689
  taskType: classification.type,
1657
1690
  routedProvider: route.provider,
1658
1691
  routedModel: route.model,
1659
- reason: error instanceof Error
1660
- ? error.message
1661
- : "Ollama service check failed",
1692
+ reason: error instanceof Error ? error.message : "Ollama service check failed",
1662
1693
  orchestrationTime: `${Date.now() - startTime}ms`,
1663
1694
  });
1664
1695
  return {}; // Return empty object to preserve existing fallback behavior
@@ -1709,9 +1740,7 @@ Current user's request: ${currentInput}`;
1709
1740
  const anyOptions = optionsOrPrompt;
1710
1741
  if (anyOptions.messages && anyOptions.messages.length > 0) {
1711
1742
  const lastMessage = anyOptions.messages[anyOptions.messages.length - 1];
1712
- return typeof lastMessage.content === "string"
1713
- ? lastMessage.content
1714
- : JSON.stringify(lastMessage.content);
1743
+ return typeof lastMessage.content === "string" ? lastMessage.content : JSON.stringify(lastMessage.content);
1715
1744
  }
1716
1745
  // Handle input.text format
1717
1746
  return optionsOrPrompt.input?.text || "";
@@ -1803,8 +1832,7 @@ Current user's request: ${currentInput}`;
1803
1832
  endpoint: otelConfig.endpoint,
1804
1833
  serviceName: otelConfig.serviceName,
1805
1834
  }
1806
- : isOpenTelemetryInitialized() ||
1807
- process.env.OTEL_EXPORTER_OTLP_ENDPOINT
1835
+ : isOpenTelemetryInitialized() || process.env.OTEL_EXPORTER_OTLP_ENDPOINT
1808
1836
  ? {
1809
1837
  enabled: isOpenTelemetryInitialized(),
1810
1838
  endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
@@ -1906,6 +1934,18 @@ Current user's request: ${currentInput}`;
1906
1934
  logger.warn("[NeuroLink] MCP servers shutdown failed:", error);
1907
1935
  }
1908
1936
  }
1937
+ // Shutdown TaskManager
1938
+ if (this._taskManager) {
1939
+ try {
1940
+ await withTimeout(this._taskManager.shutdown(), 5000, new Error("TaskManager shutdown timed out"));
1941
+ }
1942
+ catch (error) {
1943
+ logger.warn("[NeuroLink] TaskManager shutdown error:", error);
1944
+ }
1945
+ finally {
1946
+ this._taskManager = undefined;
1947
+ }
1948
+ }
1909
1949
  // Close conversation memory manager (release Redis connections, etc.)
1910
1950
  if (this.conversationMemory?.close) {
1911
1951
  try {
@@ -1934,9 +1974,7 @@ Current user's request: ${currentInput}`;
1934
1974
  const result = data.result;
1935
1975
  const usage = result?.usage;
1936
1976
  const analytics = result?.analytics;
1937
- const provider = data.provider ||
1938
- result?.provider ||
1939
- "unknown";
1977
+ const provider = data.provider || result?.provider || "unknown";
1940
1978
  const model = result?.model || "unknown";
1941
1979
  const responseTime = data.responseTime || 0;
1942
1980
  const traceCtx = this._metricsTraceContext;
@@ -1955,9 +1993,7 @@ Current user's request: ${currentInput}`;
1955
1993
  span.parentSpanId = undefined;
1956
1994
  }
1957
1995
  // Mark failed generations with ERROR status so metrics count them correctly
1958
- const spanStatus = data.success === false || data.error
1959
- ? SpanStatus.ERROR
1960
- : SpanStatus.OK;
1996
+ const spanStatus = data.success === false || data.error ? SpanStatus.ERROR : SpanStatus.OK;
1961
1997
  span = SpanSerializer.endSpan(span, spanStatus, data.error ? String(data.error) : undefined);
1962
1998
  span.durationMs = responseTime;
1963
1999
  if (usage) {
@@ -1993,9 +2029,7 @@ Current user's request: ${currentInput}`;
1993
2029
  const content = result?.content || result?.text;
1994
2030
  if (content) {
1995
2031
  span = SpanSerializer.updateAttributes(span, {
1996
- output: content.length > 5000
1997
- ? content.substring(0, 5000) + "...[truncated]"
1998
- : content,
2032
+ output: content.length > 5000 ? content.substring(0, 5000) + "...[truncated]" : content,
1999
2033
  });
2000
2034
  }
2001
2035
  this.metricsAggregator.recordSpan(span);
@@ -2034,18 +2068,14 @@ Current user's request: ${currentInput}`;
2034
2068
  if (data.prompt) {
2035
2069
  const promptStr = String(data.prompt);
2036
2070
  span = SpanSerializer.updateAttributes(span, {
2037
- input: promptStr.length > 5000
2038
- ? promptStr.substring(0, 5000) + "...[truncated]"
2039
- : promptStr,
2071
+ input: promptStr.length > 5000 ? promptStr.substring(0, 5000) + "...[truncated]" : promptStr,
2040
2072
  });
2041
2073
  }
2042
2074
  // Record streamed output (truncated for safety)
2043
2075
  const streamContent = data.content;
2044
2076
  if (streamContent) {
2045
2077
  span = SpanSerializer.updateAttributes(span, {
2046
- output: streamContent.length > 5000
2047
- ? streamContent.substring(0, 5000) + "...[truncated]"
2048
- : streamContent,
2078
+ output: streamContent.length > 5000 ? streamContent.substring(0, 5000) + "...[truncated]" : streamContent,
2049
2079
  });
2050
2080
  }
2051
2081
  // Enrich stream span with token usage if available
@@ -2062,8 +2092,7 @@ Current user's request: ${currentInput}`;
2062
2092
  const pricing = tokenTracker.getModelPricing(model);
2063
2093
  if (pricing) {
2064
2094
  const inputCost = ((usage.input || 0) / 1_000_000) * pricing.inputPricePerMillion;
2065
- const outputCost = ((usage.output || 0) / 1_000_000) *
2066
- pricing.outputPricePerMillion;
2095
+ const outputCost = ((usage.output || 0) / 1_000_000) * pricing.outputPricePerMillion;
2067
2096
  const totalCost = inputCost + outputCost;
2068
2097
  if (totalCost > 0) {
2069
2098
  span = SpanSerializer.enrichWithCost(span, {
@@ -2098,8 +2127,7 @@ Current user's request: ${currentInput}`;
2098
2127
  span = SpanSerializer.endSpan(span, success ? SpanStatus.OK : SpanStatus.ERROR);
2099
2128
  span.durationMs = responseTime;
2100
2129
  if (!success && data.error) {
2101
- span.statusMessage =
2102
- data.error.message || String(data.error);
2130
+ span.statusMessage = data.error.message || String(data.error);
2103
2131
  }
2104
2132
  if (data.result) {
2105
2133
  try {
@@ -2256,10 +2284,7 @@ Current user's request: ${currentInput}`;
2256
2284
  // The generation span will be the root (no parentSpanId).
2257
2285
  // Tool spans will be children of the root span via rootSpanId.
2258
2286
  const metricsTraceId = crypto.randomUUID().replace(/-/g, "");
2259
- const metricsRootSpanId = crypto
2260
- .randomUUID()
2261
- .replace(/-/g, "")
2262
- .substring(0, 16);
2287
+ const metricsRootSpanId = crypto.randomUUID().replace(/-/g, "").substring(0, 16);
2263
2288
  // Scope trace context to this request via AsyncLocalStorage
2264
2289
  // so concurrent generate/stream calls don't race.
2265
2290
  return metricsTraceContextStorage.run({ traceId: metricsTraceId, parentSpanId: metricsRootSpanId }, async () => {
@@ -2267,24 +2292,18 @@ Current user's request: ${currentInput}`;
2267
2292
  const originalPrompt = this._extractOriginalPrompt(optionsOrPrompt);
2268
2293
  // Convert string prompt to full options
2269
2294
  // Shallow-copy caller's object to avoid mutating their original reference
2270
- const options = typeof optionsOrPrompt === "string"
2271
- ? { input: { text: optionsOrPrompt } }
2272
- : { ...optionsOrPrompt };
2295
+ const options = typeof optionsOrPrompt === "string" ? { input: { text: optionsOrPrompt } } : { ...optionsOrPrompt };
2273
2296
  // NL-004: Resolve model aliases/deprecations before processing
2274
2297
  options.model = resolveModel(options.model, this.modelAliasConfig);
2275
2298
  // MCP Enhancement: propagate disableToolCache to tool execution
2276
- this._disableToolCacheForCurrentRequest =
2277
- !!options.disableToolCache;
2299
+ this._disableToolCacheForCurrentRequest = !!options.disableToolCache;
2278
2300
  // Set span attributes for observability
2279
2301
  generateSpan.setAttribute("neurolink.provider", options.provider || "default");
2280
2302
  generateSpan.setAttribute("neurolink.model", options.model || "default");
2281
- generateSpan.setAttribute("neurolink.input_length", typeof optionsOrPrompt === "string"
2282
- ? optionsOrPrompt.length
2283
- : options.input?.text?.length || 0);
2303
+ generateSpan.setAttribute("neurolink.input_length", typeof optionsOrPrompt === "string" ? optionsOrPrompt.length : options.input?.text?.length || 0);
2284
2304
  generateSpan.setAttribute("neurolink.has_tools", !!(options.tools && Object.keys(options.tools).length > 0));
2285
2305
  // Validate prompt
2286
- if (!options.input?.text ||
2287
- typeof options.input.text !== "string") {
2306
+ if (!options.input?.text || typeof options.input.text !== "string") {
2288
2307
  throw new Error("Input text is required and must be a non-empty string");
2289
2308
  }
2290
2309
  // Check budget limit before making API call
@@ -2314,10 +2333,9 @@ Current user's request: ${currentInput}`;
2314
2333
  ...options.middleware?.middlewareConfig?.lifecycle,
2315
2334
  enabled: true,
2316
2335
  config: {
2317
- ...options.middleware?.middlewareConfig?.lifecycle
2318
- ?.config,
2319
- onFinish: options.onFinish,
2320
- onError: options.onError,
2336
+ ...options.middleware?.middlewareConfig?.lifecycle?.config,
2337
+ ...(options.onFinish !== undefined ? { onFinish: options.onFinish } : {}),
2338
+ ...(options.onError !== undefined ? { onError: options.onError } : {}),
2321
2339
  },
2322
2340
  },
2323
2341
  },
@@ -2336,9 +2354,7 @@ Current user's request: ${currentInput}`;
2336
2354
  }
2337
2355
  catch (err) {
2338
2356
  // Rethrow auth errors as-is; wrap anything else
2339
- if (err instanceof Error &&
2340
- "feature" in err &&
2341
- err.feature === "Auth") {
2357
+ if (err instanceof Error && "feature" in err && err.feature === "Auth") {
2342
2358
  throw err;
2343
2359
  }
2344
2360
  throw AuthError.create("PROVIDER_ERROR", `Auth token validation failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -2398,9 +2414,7 @@ Current user's request: ${currentInput}`;
2398
2414
  return await this.setLangfuseContextFromOptions(options, async () => {
2399
2415
  const startTime = Date.now();
2400
2416
  // Apply orchestration if enabled and no specific provider/model requested
2401
- if (this.enableOrchestration &&
2402
- !options.provider &&
2403
- !options.model) {
2417
+ if (this.enableOrchestration && !options.provider && !options.model) {
2404
2418
  try {
2405
2419
  const orchestratedOptions = await this.applyOrchestration(options);
2406
2420
  logger.debug("Orchestration applied", {
@@ -2418,9 +2432,7 @@ Current user's request: ${currentInput}`;
2418
2432
  }
2419
2433
  catch (error) {
2420
2434
  logger.warn("Orchestration failed, continuing with original options", {
2421
- error: error instanceof Error
2422
- ? error.message
2423
- : String(error),
2435
+ error: error instanceof Error ? error.message : String(error),
2424
2436
  originalProvider: options.provider || "auto",
2425
2437
  });
2426
2438
  // Continue with original options if orchestration fails
@@ -2465,8 +2477,7 @@ Current user's request: ${currentInput}`;
2465
2477
  `This tool searches your local knowledge base of pre-loaded documents and is the primary source of truth.`,
2466
2478
  `Do NOT use websearchGrounding or any web search tools when the answer can be found in the loaded documents.`,
2467
2479
  ].join(" ");
2468
- options.systemPrompt =
2469
- (options.systemPrompt || "") + ragSystemInstruction;
2480
+ options.systemPrompt = (options.systemPrompt || "") + ragSystemInstruction;
2470
2481
  logger.info("[RAG] Tool injected into generate()", {
2471
2482
  toolName: ragResult.toolName,
2472
2483
  filesLoaded: ragResult.filesLoaded,
@@ -2475,15 +2486,12 @@ Current user's request: ${currentInput}`;
2475
2486
  }
2476
2487
  catch (error) {
2477
2488
  logger.warn("[RAG] Failed to prepare RAG tool, continuing without RAG", {
2478
- error: error instanceof Error
2479
- ? error.message
2480
- : String(error),
2489
+ error: error instanceof Error ? error.message : String(error),
2481
2490
  });
2482
2491
  }
2483
2492
  }
2484
2493
  // Memory retrieval for generate path
2485
- if (this.shouldReadMemory(options.memory, options.context?.userId) &&
2486
- options.context?.userId) {
2494
+ if (this.shouldReadMemory(options.memory, options.context?.userId) && options.context?.userId) {
2487
2495
  try {
2488
2496
  options.input.text = await this.retrieveMemory(options.input.text, options.context.userId, options.memory?.additionalUsers);
2489
2497
  logger.debug("Memory retrieval successful (generate)");
@@ -2521,6 +2529,8 @@ Current user's request: ${currentInput}`;
2521
2529
  abortSignal: options.abortSignal,
2522
2530
  skipToolPromptInjection: options.skipToolPromptInjection,
2523
2531
  middleware: options.middleware,
2532
+ // Pass through conversation messages for task continuation and external callers
2533
+ conversationMessages: options.conversationMessages,
2524
2534
  };
2525
2535
  // Auto-map top-level sessionId/userId to context for convenience
2526
2536
  // Tests and users may pass sessionId/userId as top-level options
@@ -2528,8 +2538,7 @@ Current user's request: ${currentInput}`;
2528
2538
  if (extraContext.sessionId || extraContext.userId) {
2529
2539
  baseOptions.context = {
2530
2540
  ...baseOptions.context,
2531
- ...(extraContext.sessionId &&
2532
- !baseOptions.context?.sessionId
2541
+ ...(extraContext.sessionId && !baseOptions.context?.sessionId
2533
2542
  ? { sessionId: extraContext.sessionId }
2534
2543
  : {}),
2535
2544
  ...(extraContext.userId && !baseOptions.context?.userId
@@ -2541,8 +2550,7 @@ Current user's request: ${currentInput}`;
2541
2550
  const textOptions = enhanceTextGenerationOptions(baseOptions, factoryResult);
2542
2551
  // Pass conversation memory config if available
2543
2552
  if (this.conversationMemory) {
2544
- textOptions.conversationMemoryConfig =
2545
- this.conversationMemory.config;
2553
+ textOptions.conversationMemoryConfig = this.conversationMemory.config;
2546
2554
  // Include original prompt for context summarization
2547
2555
  textOptions.originalPrompt = originalPrompt;
2548
2556
  }
@@ -2565,8 +2573,7 @@ Current user's request: ${currentInput}`;
2565
2573
  toolsUsed: textResult.toolsUsed,
2566
2574
  timestamp: Date.now(),
2567
2575
  result: textResult, // Enhanced: include full result
2568
- prompt: options.input?.text ||
2569
- options.prompt,
2576
+ prompt: options.input?.text || options.prompt,
2570
2577
  temperature: textOptions.temperature,
2571
2578
  maxTokens: textOptions.maxTokens,
2572
2579
  });
@@ -2599,10 +2606,8 @@ Current user's request: ${currentInput}`;
2599
2606
  ? {
2600
2607
  ...textResult.evaluation,
2601
2608
  isOffTopic: textResult.evaluation.isOffTopic ?? false,
2602
- alertSeverity: textResult.evaluation.alertSeverity ??
2603
- "none",
2604
- reasoning: textResult.evaluation.reasoning ??
2605
- "No evaluation provided",
2609
+ alertSeverity: textResult.evaluation.alertSeverity ?? "none",
2610
+ reasoning: textResult.evaluation.reasoning ?? "No evaluation provided",
2606
2611
  evaluationModel: textResult.evaluation.evaluationModel ?? "unknown",
2607
2612
  evaluationTime: textResult.evaluation.evaluationTime ?? Date.now(),
2608
2613
  evaluationDomain: textResult.evaluation.evaluationDomain ??
@@ -2617,8 +2622,7 @@ Current user's request: ${currentInput}`;
2617
2622
  ...(textResult.retries && { retries: textResult.retries }),
2618
2623
  };
2619
2624
  // Accumulate session cost for budget tracking
2620
- if (generateResult.analytics?.cost &&
2621
- generateResult.analytics.cost > 0) {
2625
+ if (generateResult.analytics?.cost && generateResult.analytics.cost > 0) {
2622
2626
  this._sessionCostUsd += generateResult.analytics.cost;
2623
2627
  }
2624
2628
  this.scheduleGenerateMemoryStorage(options, originalPrompt, generateResult);
@@ -2646,9 +2650,7 @@ Current user's request: ${currentInput}`;
2646
2650
  const errProvider = typeof optionsOrPrompt === "object"
2647
2651
  ? optionsOrPrompt.provider || "unknown"
2648
2652
  : "unknown";
2649
- const errModel = typeof optionsOrPrompt === "object"
2650
- ? optionsOrPrompt.model || "unknown"
2651
- : "unknown";
2653
+ const errModel = typeof optionsOrPrompt === "object" ? optionsOrPrompt.model || "unknown" : "unknown";
2652
2654
  try {
2653
2655
  this.emitter.emit("generation:end", {
2654
2656
  provider: errProvider,
@@ -2745,7 +2747,12 @@ Current user's request: ${currentInput}`;
2745
2747
  // Execute workflow
2746
2748
  const workflowResult = await runWorkflow(workflowConfig, {
2747
2749
  prompt: options.input.text,
2748
- conversationHistory: options.conversationHistory,
2750
+ conversationHistory: options.conversationMessages
2751
+ ?.filter((m) => m.role === "user" || m.role === "assistant")
2752
+ .map((m) => ({
2753
+ role: m.role,
2754
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
2755
+ })) ?? options.conversationHistory,
2749
2756
  timeout: options.timeout,
2750
2757
  verbose: false,
2751
2758
  metadata: options.context,
@@ -2755,10 +2762,8 @@ Current user's request: ${currentInput}`;
2755
2762
  // Primary output (backward compatible) - use the original best response
2756
2763
  content: workflowResult.content,
2757
2764
  // Provider info from selected response
2758
- provider: workflowResult.selectedResponse?.provider ||
2759
- workflowConfig.models[0]?.provider,
2760
- model: workflowResult.selectedResponse?.model ||
2761
- workflowConfig.models[0]?.model,
2765
+ provider: workflowResult.selectedResponse?.provider || workflowConfig.models[0]?.provider,
2766
+ model: workflowResult.selectedResponse?.model || workflowConfig.models[0]?.model,
2762
2767
  // Basic usage info
2763
2768
  usage: workflowResult.usage
2764
2769
  ? {
@@ -2836,7 +2841,12 @@ Current user's request: ${currentInput}`;
2836
2841
  // Execute workflow with progressive streaming
2837
2842
  const workflowStream = runWorkflowWithStreaming(workflowConfig, {
2838
2843
  prompt: options.input.text,
2839
- conversationHistory: options.conversationHistory,
2844
+ conversationHistory: options.conversationMessages
2845
+ ?.filter((m) => m.role === "user" || m.role === "assistant")
2846
+ .map((m) => ({
2847
+ role: m.role,
2848
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
2849
+ })) ?? options.conversationHistory,
2840
2850
  timeout: options.timeout,
2841
2851
  verbose: false,
2842
2852
  metadata: options.context,
@@ -2960,9 +2970,7 @@ Current user's request: ${currentInput}`;
2960
2970
  */
2961
2971
  async generateText(options) {
2962
2972
  // Validate required parameters for backward compatibility
2963
- if (!options.prompt ||
2964
- typeof options.prompt !== "string" ||
2965
- options.prompt.trim() === "") {
2973
+ if (!options.prompt || typeof options.prompt !== "string" || options.prompt.trim() === "") {
2966
2974
  throw new Error("GenerateText options must include prompt as a non-empty string");
2967
2975
  }
2968
2976
  // NL-004: Resolve model aliases/deprecations before processing
@@ -3045,9 +3053,7 @@ Current user's request: ${currentInput}`;
3045
3053
  // the catch block needs the originals for a more effective retry
3046
3054
  if (this.conversationMemory) {
3047
3055
  const originalMessages = await getConversationMessages(this.conversationMemory, options);
3048
- options._originalConversationMessages = originalMessages
3049
- ? [...originalMessages]
3050
- : undefined;
3056
+ options._originalConversationMessages = originalMessages ? [...originalMessages] : undefined;
3051
3057
  }
3052
3058
  const directResult = await this.directProviderGeneration(options);
3053
3059
  logger.debug(`[${functionTag}] Direct generation successful`);
@@ -3097,8 +3103,7 @@ Current user's request: ${currentInput}`;
3097
3103
  // IMPROVEMENT 1: Extract actual token count from provider error if available
3098
3104
  const actualOverflow = parseProviderOverflowDetails(error);
3099
3105
  // IMPROVEMENT 2: Use ORIGINAL messages (not already-compacted ones)
3100
- const originalMessages = options._originalConversationMessages ??
3101
- (await getConversationMessages(this.conversationMemory, options));
3106
+ const originalMessages = options._originalConversationMessages ?? (await getConversationMessages(this.conversationMemory, options));
3102
3107
  // IMPROVEMENT 3: Calculate precise reduction target
3103
3108
  const recoveryBudget = checkContextBudget({
3104
3109
  provider: options.provider || "openai",
@@ -3108,16 +3113,12 @@ Current user's request: ${currentInput}`;
3108
3113
  systemPrompt: options.systemPrompt,
3109
3114
  });
3110
3115
  // Use provider's reported token count if available (more accurate than our estimate)
3111
- const actualTokens = actualOverflow?.actualTokens ??
3112
- recoveryBudget.estimatedInputTokens;
3113
- const budgetTokens = actualOverflow?.budgetTokens ??
3114
- recoveryBudget.availableInputTokens;
3116
+ const actualTokens = actualOverflow?.actualTokens ?? recoveryBudget.estimatedInputTokens;
3117
+ const budgetTokens = actualOverflow?.budgetTokens ?? recoveryBudget.availableInputTokens;
3115
3118
  // Target = 70% of budget (aggressive safety margin for recovery)
3116
3119
  const compactionTarget = Math.floor(budgetTokens * 0.7);
3117
3120
  // IMPROVEMENT 4: Calculate adaptive truncation fraction from actual numbers
3118
- const requiredReduction = actualTokens > 0
3119
- ? (actualTokens - compactionTarget) / actualTokens
3120
- : 0.5;
3121
+ const requiredReduction = actualTokens > 0 ? (actualTokens - compactionTarget) / actualTokens : 0.5;
3121
3122
  const compactor = new ContextCompactor({
3122
3123
  enableSummarize: false, // Skip LLM call for recovery (speed)
3123
3124
  enablePrune: true,
@@ -3170,9 +3171,7 @@ Current user's request: ${currentInput}`;
3170
3171
  throw retryError;
3171
3172
  }
3172
3173
  logger.error(`[${functionTag}] Recovery attempt failed`, {
3173
- error: retryError instanceof Error
3174
- ? retryError.message
3175
- : String(retryError),
3174
+ error: retryError instanceof Error ? retryError.message : String(retryError),
3176
3175
  });
3177
3176
  }
3178
3177
  }
@@ -3185,8 +3184,7 @@ Current user's request: ${currentInput}`;
3185
3184
  logger.info(`[${functionTag}] Generation aborted — storing conversation turn for title generation`, {
3186
3185
  hasMemory: !!this.conversationMemory,
3187
3186
  memoryType: this.conversationMemory?.constructor?.name || "NONE",
3188
- sessionId: options.context?.sessionId ||
3189
- "unknown",
3187
+ sessionId: options.context?.sessionId || "unknown",
3190
3188
  });
3191
3189
  try {
3192
3190
  const abortedResult = {
@@ -3199,9 +3197,7 @@ Current user's request: ${currentInput}`;
3199
3197
  }
3200
3198
  catch (storeError) {
3201
3199
  logger.warn(`[${functionTag}] Failed to store conversation turn after abort`, {
3202
- error: storeError instanceof Error
3203
- ? storeError.message
3204
- : String(storeError),
3200
+ error: storeError instanceof Error ? storeError.message : String(storeError),
3205
3201
  });
3206
3202
  }
3207
3203
  }
@@ -3218,9 +3214,7 @@ Current user's request: ${currentInput}`;
3218
3214
  catch (spanError) {
3219
3215
  internalSpan.setStatus({
3220
3216
  code: SpanStatusCode.ERROR,
3221
- message: spanError instanceof Error
3222
- ? spanError.message
3223
- : String(spanError),
3217
+ message: spanError instanceof Error ? spanError.message : String(spanError),
3224
3218
  });
3225
3219
  throw spanError;
3226
3220
  }
@@ -3300,8 +3294,7 @@ Current user's request: ${currentInput}`;
3300
3294
  * Attempt MCP generation with retry logic
3301
3295
  */
3302
3296
  async attemptMCPGeneration(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag) {
3303
- if (!options.disableTools &&
3304
- !(options.tts?.enabled && !options.tts?.useAiResponse)) {
3297
+ if (!options.disableTools && !(options.tts?.enabled && !options.tts?.useAiResponse)) {
3305
3298
  return await this.performMCPGenerationRetries(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag);
3306
3299
  }
3307
3300
  return null;
@@ -3323,9 +3316,7 @@ Current user's request: ${currentInput}`;
3323
3316
  try {
3324
3317
  logger.debug(`[${functionTag}] Attempting MCP generation (attempt ${attempt}/${maxAttempts})...`);
3325
3318
  const mcpResult = await this.tryMCPGeneration(options);
3326
- if (mcpResult &&
3327
- (mcpResult.content ||
3328
- (mcpResult.toolExecutions && mcpResult.toolExecutions.length > 0))) {
3319
+ if (mcpResult && (mcpResult.content || (mcpResult.toolExecutions && mcpResult.toolExecutions.length > 0))) {
3329
3320
  logger.debug(`[${functionTag}] MCP generation successful on attempt ${attempt}`, {
3330
3321
  contentLength: mcpResult.content?.length || 0,
3331
3322
  toolsUsed: mcpResult.toolsUsed?.length || 0,
@@ -3356,11 +3347,7 @@ Current user's request: ${currentInput}`;
3356
3347
  // NL-007: Record retry error for observability
3357
3348
  retryCount++;
3358
3349
  const errMsg = error instanceof Error ? error.message : String(error);
3359
- const errCode = error instanceof NeuroLinkError
3360
- ? error.code
3361
- : error instanceof Error
3362
- ? error.name
3363
- : "UNKNOWN";
3350
+ const errCode = error instanceof NeuroLinkError ? error.code : error instanceof Error ? error.name : "UNKNOWN";
3364
3351
  retryErrors.push({ code: errCode, message: errMsg.substring(0, 500) });
3365
3352
  logger.debug(`[${functionTag}] MCP generation failed on attempt ${attempt}/${maxAttempts}`, {
3366
3353
  error: errMsg,
@@ -3379,11 +3366,8 @@ Current user's request: ${currentInput}`;
3379
3366
  const isNonRetryable = isContextOverflowError(error) ||
3380
3367
  isToolError ||
3381
3368
  isNonRetryableProviderError(error) ||
3382
- (error instanceof Error &&
3383
- error.isRetryable ===
3384
- false) ||
3385
- (error instanceof Error &&
3386
- error.statusCode === 400);
3369
+ (error instanceof Error && error.isRetryable === false) ||
3370
+ (error instanceof Error && error.statusCode === 400);
3387
3371
  if (isNonRetryable) {
3388
3372
  logger.debug(`[${functionTag}] Non-retryable error detected, skipping remaining retries`);
3389
3373
  break;
@@ -3419,8 +3403,7 @@ Current user's request: ${currentInput}`;
3419
3403
  throw new DOMException("The operation was aborted", "AbortError");
3420
3404
  }
3421
3405
  // 🚀 EXHAUSTIVE LOGGING POINT T001: TRY MCP GENERATION ENTRY
3422
- const requestId = options.context?.requestId ||
3423
- "unknown";
3406
+ const requestId = options.context?.requestId || "unknown";
3424
3407
  const tryMCPId = `try-mcp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
3425
3408
  const tryMCPStartTime = Date.now();
3426
3409
  const tryMCPHrTimeStart = process.hrtime.bigint();
@@ -3448,9 +3431,7 @@ Current user's request: ${currentInput}`;
3448
3431
  }
3449
3432
  // Context creation removed - was never used
3450
3433
  // Determine provider
3451
- const providerName = options.provider === "auto" || !options.provider
3452
- ? await getBestProvider()
3453
- : options.provider;
3434
+ const providerName = options.provider === "auto" || !options.provider ? await getBestProvider() : options.provider;
3454
3435
  // Get available tools
3455
3436
  let availableTools = await this.getAllAvailableTools();
3456
3437
  // NL-001: Filter out tools with OPEN circuit breakers
@@ -3460,8 +3441,7 @@ Current user's request: ${currentInput}`;
3460
3441
  availableTools = availableTools.filter((t) => cbFilteredNames.has(t.name));
3461
3442
  // Apply per-call tool filtering for system prompt tool descriptions
3462
3443
  availableTools = this.applyToolInfoFiltering(availableTools, options);
3463
- const targetTool = availableTools.find((t) => t.name.includes("SuccessRateSRByTime") ||
3464
- t.name.includes("juspay-analytics"));
3444
+ const targetTool = availableTools.find((t) => t.name.includes("SuccessRateSRByTime") || t.name.includes("juspay-analytics"));
3465
3445
  logger.debug("Available tools for AI prompt generation", {
3466
3446
  toolsCount: availableTools.length,
3467
3447
  toolNames: availableTools.map((t) => t.name),
@@ -3495,9 +3475,7 @@ Current user's request: ${currentInput}`;
3495
3475
  logger.debug("[Observability] System prompt metadata", {
3496
3476
  requestId,
3497
3477
  systemPromptLength: enhancedSystemPrompt.length,
3498
- systemPromptHash: enhancedSystemPrompt.length > 0
3499
- ? `sha256:${enhancedSystemPrompt.slice(0, 8)}...`
3500
- : "empty",
3478
+ systemPromptHash: enhancedSystemPrompt.length > 0 ? `sha256:${enhancedSystemPrompt.slice(0, 8)}...` : "empty",
3501
3479
  hasCustomSystemPrompt: !!options.systemPrompt,
3502
3480
  });
3503
3481
  // Get conversation messages for context
@@ -3524,9 +3502,7 @@ Current user's request: ${currentInput}`;
3524
3502
  index: i,
3525
3503
  role: msg.role,
3526
3504
  contentLength,
3527
- contentPreview: typeof msg.content === "string"
3528
- ? msg.content.substring(0, 200)
3529
- : "[multimodal]",
3505
+ contentPreview: typeof msg.content === "string" ? msg.content.substring(0, 200) : "[multimodal]",
3530
3506
  };
3531
3507
  }),
3532
3508
  });
@@ -3567,8 +3543,7 @@ Current user's request: ${currentInput}`;
3567
3543
  const compactionSessionId = this.getCompactionSessionId(options);
3568
3544
  if (budgetResult.shouldCompact &&
3569
3545
  this.conversationMemory &&
3570
- messageCount >
3571
- (this.lastCompactionMessageCount.get(compactionSessionId) ?? 0)) {
3546
+ messageCount > (this.lastCompactionMessageCount.get(compactionSessionId) ?? 0)) {
3572
3547
  logger.info("[NeuroLink] Context budget exceeded, triggering auto-compaction", {
3573
3548
  usageRatio: budgetResult.usageRatio,
3574
3549
  estimatedTokens: budgetResult.estimatedInputTokens,
@@ -3576,10 +3551,8 @@ Current user's request: ${currentInput}`;
3576
3551
  });
3577
3552
  const compactor = new ContextCompactor({
3578
3553
  provider: providerName,
3579
- summarizationProvider: this.conversationMemoryConfig?.conversationMemory
3580
- ?.summarizationProvider,
3581
- summarizationModel: this.conversationMemoryConfig?.conversationMemory
3582
- ?.summarizationModel,
3554
+ summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
3555
+ summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
3583
3556
  });
3584
3557
  const compactionResult = await compactor.compact(conversationMessages, budgetResult.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, requestId);
3585
3558
  if (compactionResult.compacted) {
@@ -3759,18 +3732,12 @@ Current user's request: ${currentInput}`;
3759
3732
  ];
3760
3733
  const requestedProvider = options.provider === "auto" ? undefined : options.provider;
3761
3734
  // Check for orchestrated preferred provider in context
3762
- const preferredOrchestrated = options.context &&
3763
- typeof options.context === "object" &&
3764
- "__orchestratedPreferredProvider" in options.context
3765
- ? options.context
3766
- .__orchestratedPreferredProvider
3735
+ const preferredOrchestrated = options.context && typeof options.context === "object" && "__orchestratedPreferredProvider" in options.context
3736
+ ? options.context.__orchestratedPreferredProvider
3767
3737
  : undefined;
3768
3738
  // Build provider list with orchestrated preference first, then fallback to full list
3769
3739
  const tryProviders = preferredOrchestrated
3770
- ? [
3771
- preferredOrchestrated,
3772
- ...providerPriority.filter((p) => p !== preferredOrchestrated),
3773
- ]
3740
+ ? [preferredOrchestrated, ...providerPriority.filter((p) => p !== preferredOrchestrated)]
3774
3741
  : requestedProvider
3775
3742
  ? [requestedProvider]
3776
3743
  : providerPriority;
@@ -3790,8 +3757,7 @@ Current user's request: ${currentInput}`;
3790
3757
  logger.debug(`[${functionTag}] Attempting provider: ${providerName}`);
3791
3758
  // Get conversation messages for context (use pre-compacted if provided)
3792
3759
  const optionsWithMessages = options;
3793
- let conversationMessages = optionsWithMessages.conversationMessages
3794
- ?.length
3760
+ let conversationMessages = optionsWithMessages.conversationMessages?.length
3795
3761
  ? optionsWithMessages.conversationMessages
3796
3762
  : await getConversationMessages(this.conversationMemory, options);
3797
3763
  // Pre-generation budget check
@@ -3802,22 +3768,17 @@ Current user's request: ${currentInput}`;
3802
3768
  systemPrompt: options.systemPrompt,
3803
3769
  conversationMessages: conversationMessages,
3804
3770
  currentPrompt: options.prompt,
3805
- toolDefinitions: options.tools
3806
- ? Object.values(options.tools)
3807
- : undefined,
3771
+ toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
3808
3772
  });
3809
3773
  const dpgMessageCount = conversationMessages?.length || 0;
3810
3774
  const dpgCompactionSessionId = this.getCompactionSessionId(options);
3811
3775
  if (budgetCheck.shouldCompact &&
3812
3776
  this.conversationMemory &&
3813
- dpgMessageCount >
3814
- (this.lastCompactionMessageCount.get(dpgCompactionSessionId) ?? 0)) {
3777
+ dpgMessageCount > (this.lastCompactionMessageCount.get(dpgCompactionSessionId) ?? 0)) {
3815
3778
  const compactor = new ContextCompactor({
3816
3779
  provider: providerName,
3817
- summarizationProvider: this.conversationMemoryConfig?.conversationMemory
3818
- ?.summarizationProvider,
3819
- summarizationModel: this.conversationMemoryConfig?.conversationMemory
3820
- ?.summarizationModel,
3780
+ summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
3781
+ summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
3821
3782
  });
3822
3783
  const compactionResult = await compactor.compact(conversationMessages, budgetCheck.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, options.context?.requestId);
3823
3784
  if (compactionResult.compacted) {
@@ -3833,9 +3794,7 @@ Current user's request: ${currentInput}`;
3833
3794
  systemPrompt: options.systemPrompt,
3834
3795
  conversationMessages: conversationMessages,
3835
3796
  currentPrompt: options.prompt,
3836
- toolDefinitions: options.tools
3837
- ? Object.values(options.tools)
3838
- : undefined,
3797
+ toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
3839
3798
  });
3840
3799
  if (!postCompactBudget.withinBudget) {
3841
3800
  logger.warn("[NeuroLink] directProviderGeneration: post-compaction still over budget, emergency truncation", {
@@ -3851,9 +3810,7 @@ Current user's request: ${currentInput}`;
3851
3810
  systemPrompt: options.systemPrompt,
3852
3811
  conversationMessages: conversationMessages,
3853
3812
  currentPrompt: options.prompt,
3854
- toolDefinitions: options.tools
3855
- ? Object.values(options.tools)
3856
- : undefined,
3813
+ toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
3857
3814
  });
3858
3815
  if (!finalBudget.withinBudget) {
3859
3816
  throw new ContextBudgetExceededError(`Context exceeds model budget after all compaction stages. ` +
@@ -4111,10 +4068,7 @@ Current user's request: ${currentInput}`;
4111
4068
  options = { ...options };
4112
4069
  // Set metrics trace context for parent-child span linking
4113
4070
  const metricsTraceId = crypto.randomUUID().replace(/-/g, "");
4114
- const metricsParentSpanId = crypto
4115
- .randomUUID()
4116
- .replace(/-/g, "")
4117
- .substring(0, 16);
4071
+ const metricsParentSpanId = crypto.randomUUID().replace(/-/g, "").substring(0, 16);
4118
4072
  // Scope trace context to this request via AsyncLocalStorage
4119
4073
  // so concurrent generate/stream calls don't race.
4120
4074
  return metricsTraceContextStorage.run({ traceId: metricsTraceId, parentSpanId: metricsParentSpanId }, async () => {
@@ -4173,9 +4127,7 @@ Current user's request: ${currentInput}`;
4173
4127
  }
4174
4128
  catch (err) {
4175
4129
  // Rethrow auth errors as-is; wrap anything else
4176
- if (err instanceof Error &&
4177
- "feature" in err &&
4178
- err.feature === "Auth") {
4130
+ if (err instanceof Error && "feature" in err && err.feature === "Auth") {
4179
4131
  throw err;
4180
4132
  }
4181
4133
  throw AuthError.create("PROVIDER_ERROR", `Auth token validation failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -4228,9 +4180,9 @@ Current user's request: ${currentInput}`;
4228
4180
  enabled: true,
4229
4181
  config: {
4230
4182
  ...options.middleware?.middlewareConfig?.lifecycle?.config,
4231
- onFinish: options.onFinish,
4232
- onError: options.onError,
4233
- onChunk: options.onChunk,
4183
+ ...(options.onFinish !== undefined ? { onFinish: options.onFinish } : {}),
4184
+ ...(options.onError !== undefined ? { onError: options.onError } : {}),
4185
+ ...(options.onChunk !== undefined ? { onChunk: options.onChunk } : {}),
4234
4186
  },
4235
4187
  },
4236
4188
  },
@@ -4269,7 +4221,12 @@ Current user's request: ${currentInput}`;
4269
4221
  try {
4270
4222
  // Prepare options: init memory, MCP, orchestration, Ollama auto-disable, tool detection
4271
4223
  const { enhancedOptions, factoryResult } = await this.prepareStreamOptions(options, streamId, startTime, hrTimeStart);
4272
- const { stream: mcpStream, provider: providerName, usage: streamUsage, model: streamModel, analytics: streamAnalytics, } = await this.createMCPStream(enhancedOptions);
4224
+ const { stream: mcpStream, provider: providerName, usage: streamUsage, model: streamModel, finishReason: streamFinishReason, toolCalls: streamToolCalls, toolResults: streamToolResults, analytics: streamAnalytics, } = await this.createMCPStream(enhancedOptions);
4225
+ const streamState = {
4226
+ finishReason: streamFinishReason ?? "stop",
4227
+ toolCalls: streamToolCalls,
4228
+ toolResults: streamToolResults,
4229
+ };
4273
4230
  // Update span with resolved provider name
4274
4231
  streamSpan.setAttribute(ATTR.NL_PROVIDER, providerName || "unknown");
4275
4232
  let accumulatedContent = "";
@@ -4291,9 +4248,7 @@ Current user's request: ${currentInput}`;
4291
4248
  try {
4292
4249
  for await (const chunk of mcpStream) {
4293
4250
  chunkCount++;
4294
- if (chunk &&
4295
- "content" in chunk &&
4296
- typeof chunk.content === "string") {
4251
+ if (chunk && "content" in chunk && typeof chunk.content === "string") {
4297
4252
  accumulatedContent += chunk.content;
4298
4253
  self.emitter.emit("response:chunk", chunk.content);
4299
4254
  // Emit stream:chunk event (Observability Solution 8)
@@ -4309,8 +4264,12 @@ Current user's request: ${currentInput}`;
4309
4264
  }
4310
4265
  yield chunk;
4311
4266
  }
4312
- if (chunkCount === 0 && !metadata.fallbackAttempted) {
4313
- yield* self.handleStreamFallback(metadata, originalPrompt, enhancedOptions, providerName, accumulatedContent, (content) => {
4267
+ if (chunkCount === 0 &&
4268
+ !metadata.fallbackAttempted &&
4269
+ !enhancedOptions.disableInternalFallback &&
4270
+ streamState.toolCalls.length === 0 &&
4271
+ streamState.toolResults.length === 0) {
4272
+ yield* self.handleStreamFallback(metadata, streamState, originalPrompt, enhancedOptions, providerName, accumulatedContent, (content) => {
4314
4273
  accumulatedContent += content;
4315
4274
  });
4316
4275
  }
@@ -4318,9 +4277,7 @@ Current user's request: ${currentInput}`;
4318
4277
  // When fallback took over, attribute the completion to the
4319
4278
  // fallback provider so downstream telemetry reflects reality.
4320
4279
  const effectiveProvider = metadata.fallbackProvider ?? providerName;
4321
- const effectiveModel = metadata.fallbackModel ??
4322
- streamModel ??
4323
- enhancedOptions.model;
4280
+ const effectiveModel = metadata.fallbackModel ?? streamModel ?? enhancedOptions.model;
4324
4281
  // Resolve analytics promise to get final token usage
4325
4282
  let resolvedUsage = streamUsage;
4326
4283
  if (!resolvedUsage && streamAnalytics) {
@@ -4339,8 +4296,7 @@ Current user's request: ${currentInput}`;
4339
4296
  content: accumulatedContent,
4340
4297
  provider: effectiveProvider,
4341
4298
  model: effectiveModel,
4342
- prompt: enhancedOptions.input?.text ||
4343
- enhancedOptions.prompt,
4299
+ prompt: enhancedOptions.input?.text || enhancedOptions.prompt,
4344
4300
  metadata: {
4345
4301
  chunkCount,
4346
4302
  totalLength: accumulatedContent.length,
@@ -4394,10 +4350,7 @@ Current user's request: ${currentInput}`;
4394
4350
  if (primaryFailed) {
4395
4351
  streamSpan.setStatus({
4396
4352
  code: SpanStatusCode.ERROR,
4397
- message: metadata.error ||
4398
- (streamError instanceof Error
4399
- ? streamError.message
4400
- : String(streamError)),
4353
+ message: metadata.error || (streamError instanceof Error ? streamError.message : String(streamError)),
4401
4354
  });
4402
4355
  }
4403
4356
  else {
@@ -4424,10 +4377,18 @@ Current user's request: ${currentInput}`;
4424
4377
  }
4425
4378
  })();
4426
4379
  const streamResult = await this.processStreamResult(processedStream, enhancedOptions, factoryResult);
4380
+ streamResult.finishReason = streamState.finishReason || streamResult.finishReason;
4381
+ streamResult.toolCalls = streamState.toolCalls;
4382
+ streamResult.toolResults = streamState.toolResults;
4383
+ if (!streamResult.usage) {
4384
+ streamResult.usage = streamUsage;
4385
+ }
4386
+ if (!streamResult.analytics) {
4387
+ streamResult.analytics = streamAnalytics instanceof Promise ? await streamAnalytics : streamAnalytics;
4388
+ }
4427
4389
  const responseTime = Date.now() - startTime;
4428
4390
  // Accumulate session cost for budget tracking
4429
- if (streamResult.analytics?.cost &&
4430
- streamResult.analytics.cost > 0) {
4391
+ if (streamResult.analytics?.cost && streamResult.analytics.cost > 0) {
4431
4392
  this._sessionCostUsd += streamResult.analytics.cost;
4432
4393
  }
4433
4394
  this.emitStreamEndEvents(streamResult);
@@ -4444,6 +4405,9 @@ Current user's request: ${currentInput}`;
4444
4405
  });
4445
4406
  }
4446
4407
  catch (error) {
4408
+ if (options.disableInternalFallback) {
4409
+ throw error;
4410
+ }
4447
4411
  return this.handleStreamError(error, options, startTime, streamId, undefined, undefined);
4448
4412
  }
4449
4413
  });
@@ -4472,8 +4436,7 @@ Current user's request: ${currentInput}`;
4472
4436
  // Initialize MCP
4473
4437
  await this.initializeMCP();
4474
4438
  // Memory retrieval
4475
- if (this.shouldReadMemory(options.memory, options.context?.userId) &&
4476
- options.context?.userId) {
4439
+ if (this.shouldReadMemory(options.memory, options.context?.userId) && options.context?.userId) {
4477
4440
  try {
4478
4441
  options.input.text = await this.retrieveMemory(options.input.text, options.context.userId, options.memory?.additionalUsers);
4479
4442
  logger.debug("Memory retrieval successful");
@@ -4518,8 +4481,7 @@ Current user's request: ${currentInput}`;
4518
4481
  if (!options.tools) {
4519
4482
  options.tools = {};
4520
4483
  }
4521
- options.tools[ragResult.toolName] =
4522
- ragResult.tool;
4484
+ options.tools[ragResult.toolName] = ragResult.tool;
4523
4485
  // Inject RAG-aware system prompt so the AI uses the RAG tool first
4524
4486
  const ragSystemInstruction = [
4525
4487
  `\n\nIMPORTANT: You have a tool called "${ragResult.toolName}" that searches through`,
@@ -4528,8 +4490,7 @@ Current user's request: ${currentInput}`;
4528
4490
  `This tool searches your local knowledge base of pre-loaded documents and is the primary source of truth.`,
4529
4491
  `Do NOT use websearchGrounding or any web search tools when the answer can be found in the loaded documents.`,
4530
4492
  ].join(" ");
4531
- options.systemPrompt =
4532
- (options.systemPrompt || "") + ragSystemInstruction;
4493
+ options.systemPrompt = (options.systemPrompt || "") + ragSystemInstruction;
4533
4494
  logger.info("[RAG] Tool injected into stream()", {
4534
4495
  toolName: ragResult.toolName,
4535
4496
  filesLoaded: ragResult.filesLoaded,
@@ -4557,8 +4518,7 @@ Current user's request: ${currentInput}`;
4557
4518
  * Prevents overwhelming smaller models with massive tool descriptions in the system message.
4558
4519
  */
4559
4520
  async autoDisableOllamaStreamTools(options) {
4560
- if ((options.provider === "ollama" ||
4561
- options.provider?.toLowerCase().includes("ollama")) &&
4521
+ if ((options.provider === "ollama" || options.provider?.toLowerCase().includes("ollama")) &&
4562
4522
  !options.disableTools) {
4563
4523
  const { ModelConfigurationManager } = await import("./core/modelConfiguration.js");
4564
4524
  const modelConfig = ModelConfigurationManager.getInstance();
@@ -4642,7 +4602,7 @@ Current user's request: ${currentInput}`;
4642
4602
  * Handle fallback when the primary stream returns 0 chunks.
4643
4603
  * Yields chunks from a fallback provider and updates metadata accordingly.
4644
4604
  */
4645
- async *handleStreamFallback(metadata, originalPrompt, enhancedOptions, providerName, _accumulatedContent, appendContent) {
4605
+ async *handleStreamFallback(metadata, streamState, originalPrompt, enhancedOptions, providerName, _accumulatedContent, appendContent) {
4646
4606
  metadata.fallbackAttempted = true;
4647
4607
  const errorMsg = "Stream completed with 0 chunks (possible guardrails block)";
4648
4608
  metadata.error = errorMsg;
@@ -4700,18 +4660,23 @@ Current user's request: ${currentInput}`;
4700
4660
  model: fallbackRoute.model,
4701
4661
  conversationMessages,
4702
4662
  });
4663
+ const fallbackToolCalls = fallbackResult.toolCalls ?? [];
4664
+ const fallbackToolResults = fallbackResult.toolResults ?? [];
4665
+ if (fallbackToolCalls.length > 0 || fallbackToolResults.length > 0) {
4666
+ streamState.toolCalls = fallbackToolCalls;
4667
+ streamState.toolResults = fallbackToolResults;
4668
+ streamState.finishReason = fallbackResult.finishReason ?? streamState.finishReason;
4669
+ }
4703
4670
  let fallbackChunkCount = 0;
4704
4671
  for await (const fallbackChunk of fallbackResult.stream) {
4705
4672
  fallbackChunkCount++;
4706
- if (fallbackChunk &&
4707
- "content" in fallbackChunk &&
4708
- typeof fallbackChunk.content === "string") {
4673
+ if (fallbackChunk && "content" in fallbackChunk && typeof fallbackChunk.content === "string") {
4709
4674
  appendContent(fallbackChunk.content);
4710
4675
  this.emitter.emit("response:chunk", fallbackChunk.content);
4711
4676
  }
4712
4677
  yield fallbackChunk;
4713
4678
  }
4714
- if (fallbackChunkCount === 0) {
4679
+ if (fallbackChunkCount === 0 && fallbackToolCalls.length === 0 && fallbackToolResults.length === 0) {
4715
4680
  throw new Error(`Fallback provider ${fallbackRoute.provider} also returned 0 chunks`);
4716
4681
  }
4717
4682
  // Fallback succeeded - likely guardrails blocked primary
@@ -4720,9 +4685,7 @@ Current user's request: ${currentInput}`;
4720
4685
  metadata.guardrailsBlocked = true;
4721
4686
  }
4722
4687
  catch (fallbackError) {
4723
- const fallbackErrorMsg = fallbackError instanceof Error
4724
- ? fallbackError.message
4725
- : String(fallbackError);
4688
+ const fallbackErrorMsg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
4726
4689
  metadata.error = `${errorMsg}; Fallback failed: ${fallbackErrorMsg}`;
4727
4690
  logger.error("Fallback provider failed", {
4728
4691
  fallbackProvider: fallbackRoute.provider,
@@ -4736,22 +4699,19 @@ Current user's request: ${currentInput}`;
4736
4699
  * Handles conversation memory storage in the background.
4737
4700
  */
4738
4701
  async storeStreamConversationMemory(params) {
4739
- const { enhancedOptions, providerName, originalPrompt, accumulatedContent, startTime, eventSequence, } = params;
4702
+ const { enhancedOptions, providerName, originalPrompt, accumulatedContent, startTime, eventSequence } = params;
4740
4703
  // Guard: skip storing if no meaningful content was produced (no text AND no tool activity)
4741
4704
  const hasToolEvents = eventSequence.some((e) => e.type === "tool:start" || e.type === "tool:end");
4742
4705
  if (!accumulatedContent.trim() && !hasToolEvents) {
4743
4706
  logger.warn("[NeuroLink.stream] Skipping conversation turn storage — no text content or tool activity", {
4744
- sessionId: enhancedOptions.context
4745
- ?.sessionId,
4707
+ sessionId: enhancedOptions.context?.sessionId,
4746
4708
  });
4747
4709
  return;
4748
4710
  }
4749
4711
  // Store memory after stream consumption is complete
4750
4712
  if (this.conversationMemory && enhancedOptions.context?.sessionId) {
4751
- const sessionId = enhancedOptions.context
4752
- ?.sessionId;
4753
- const userId = enhancedOptions.context
4754
- ?.userId;
4713
+ const sessionId = enhancedOptions.context?.sessionId;
4714
+ const userId = enhancedOptions.context?.userId;
4755
4715
  let providerDetails;
4756
4716
  if (enhancedOptions.model) {
4757
4717
  providerDetails = {
@@ -4770,8 +4730,7 @@ Current user's request: ${currentInput}`;
4770
4730
  providerDetails,
4771
4731
  enableSummarization: enhancedOptions.enableSummarization,
4772
4732
  events: eventSequence.length > 0 ? eventSequence : undefined,
4773
- requestId: enhancedOptions.context
4774
- ?.requestId,
4733
+ requestId: enhancedOptions.context?.requestId,
4775
4734
  });
4776
4735
  this.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": "stream" }, Date.now() - memStoreStart, SpanStatus.OK);
4777
4736
  logger.debug("[NeuroLink.stream] Stored conversation turn with events", {
@@ -4801,8 +4760,7 @@ Current user's request: ${currentInput}`;
4801
4760
  validationStartTimeNs: validationStartTime.toString(),
4802
4761
  message: "Starting comprehensive input validation process",
4803
4762
  });
4804
- const hasText = typeof options?.input?.text === "string" &&
4805
- options.input.text.trim().length > 0;
4763
+ const hasText = typeof options?.input?.text === "string" && options.input.text.trim().length > 0;
4806
4764
  // Accept audio when frames are present; sampleRateHz is optional (defaults applied later)
4807
4765
  const hasAudio = !!(options?.input?.audio &&
4808
4766
  options.input.audio.frames &&
@@ -4881,12 +4839,10 @@ Current user's request: ${currentInput}`;
4881
4839
  const streamCompactionSessionId = this.getCompactionSessionId(options);
4882
4840
  if (streamBudget.shouldCompact &&
4883
4841
  (hasCallerConversationHistory || this.conversationMemory) &&
4884
- streamMessageCount >
4885
- (this.lastCompactionMessageCount.get(streamCompactionSessionId) ?? 0)) {
4842
+ streamMessageCount > (this.lastCompactionMessageCount.get(streamCompactionSessionId) ?? 0)) {
4886
4843
  const compactor = new ContextCompactor({
4887
4844
  provider: providerName,
4888
- summarizationProvider: this.conversationMemoryConfig?.conversationMemory
4889
- ?.summarizationProvider,
4845
+ summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
4890
4846
  summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
4891
4847
  });
4892
4848
  const compactionResult = await compactor.compact(conversationMessages, streamBudget.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, options.context?.requestId);
@@ -4956,6 +4912,9 @@ Current user's request: ${currentInput}`;
4956
4912
  provider: providerName,
4957
4913
  usage: streamResult.usage,
4958
4914
  model: streamResult.model || options.model,
4915
+ finishReason: streamResult.finishReason,
4916
+ toolCalls: streamResult.toolCalls ?? [],
4917
+ toolResults: streamResult.toolResults ?? [],
4959
4918
  analytics: streamResult.analytics,
4960
4919
  };
4961
4920
  }
@@ -5028,8 +4987,7 @@ Current user's request: ${currentInput}`;
5028
4987
  parentSpanId: traceCtx?.parentSpanId,
5029
4988
  });
5030
4989
  failedSpan = SpanSerializer.endSpan(failedSpan, SpanStatus.ERROR);
5031
- failedSpan.statusMessage =
5032
- error instanceof Error ? error.message : String(error);
4990
+ failedSpan.statusMessage = error instanceof Error ? error.message : String(error);
5033
4991
  failedSpan.durationMs = Date.now() - startTime;
5034
4992
  this.metricsAggregator.recordSpan(failedSpan);
5035
4993
  getMetricsAggregator().recordSpan(failedSpan);
@@ -5046,15 +5004,14 @@ Current user's request: ${currentInput}`;
5046
5004
  model: options.model,
5047
5005
  temperature: options.temperature,
5048
5006
  maxTokens: options.maxTokens,
5007
+ conversationMessages: options.conversationMessages,
5049
5008
  });
5050
5009
  // Create a wrapper around the fallback stream that accumulates content
5051
5010
  let fallbackAccumulatedContent = "";
5052
5011
  const fallbackProcessedStream = (async function* (self) {
5053
5012
  try {
5054
5013
  for await (const chunk of fallbackStreamResult.stream) {
5055
- if (chunk &&
5056
- "content" in chunk &&
5057
- typeof chunk.content === "string") {
5014
+ if (chunk && "content" in chunk && typeof chunk.content === "string") {
5058
5015
  fallbackAccumulatedContent += chunk.content;
5059
5016
  // Emit chunk event
5060
5017
  self.emitter.emit("response:chunk", chunk.content);
@@ -5073,12 +5030,9 @@ Current user's request: ${currentInput}`;
5073
5030
  }
5074
5031
  // Store memory after fallback stream consumption is complete
5075
5032
  // Guard: skip storing if fallback accumulated content is empty
5076
- if (self.conversationMemory &&
5077
- enhancedOptions?.context?.sessionId &&
5078
- fallbackAccumulatedContent.trim()) {
5033
+ if (self.conversationMemory && enhancedOptions?.context?.sessionId && fallbackAccumulatedContent.trim()) {
5079
5034
  const sessionId = enhancedOptions?.context?.sessionId;
5080
- const userId = enhancedOptions?.context
5081
- ?.userId;
5035
+ const userId = enhancedOptions?.context?.userId;
5082
5036
  let providerDetails;
5083
5037
  if (options.model) {
5084
5038
  providerDetails = {
@@ -5097,8 +5051,7 @@ Current user's request: ${currentInput}`;
5097
5051
  providerDetails,
5098
5052
  enableSummarization: enhancedOptions?.enableSummarization,
5099
5053
  requestId: enhancedOptions?.context?.requestId ||
5100
- options.context
5101
- ?.requestId,
5054
+ options.context?.requestId,
5102
5055
  });
5103
5056
  self.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": "fallback-stream" }, Date.now() - memStoreStart, SpanStatus.OK);
5104
5057
  }
@@ -5497,7 +5450,8 @@ Current user's request: ${currentInput}`;
5497
5450
  // (direct executeTool() or AI SDK generateText() tool calling).
5498
5451
  if (options?.timeout !== undefined &&
5499
5452
  options.timeout > 0 &&
5500
- Number.isFinite(options.timeout)) {
5453
+ Number.isFinite(options.timeout) &&
5454
+ typeof convertedTool.execute === "function") {
5501
5455
  const originalExecute = convertedTool.execute;
5502
5456
  const toolTimeout = options.timeout;
5503
5457
  const toolName = name;
@@ -5506,9 +5460,7 @@ Current user's request: ${currentInput}`;
5506
5460
  // Compose with any parent abortSignal from ToolExecutionOptions
5507
5461
  const execOptions = args[1];
5508
5462
  const parentSignal = execOptions?.abortSignal;
5509
- const composedSignal = parentSignal
5510
- ? AbortSignal.any([parentSignal, timeoutSignal])
5511
- : timeoutSignal;
5463
+ const composedSignal = parentSignal ? AbortSignal.any([parentSignal, timeoutSignal]) : timeoutSignal;
5512
5464
  // Replace the abortSignal in execution options
5513
5465
  const augmentedContext = {
5514
5466
  ...execOptions,
@@ -5519,7 +5471,7 @@ Current user's request: ${currentInput}`;
5519
5471
  new Promise((_, reject) => {
5520
5472
  composedSignal.addEventListener("abort", () => {
5521
5473
  if (timeoutSignal.aborted) {
5522
- reject(new Error(`Tool '${toolName}' timed out after ${toolTimeout}ms (configured at registration)`));
5474
+ reject(ErrorFactory.toolTimeout(toolName, toolTimeout));
5523
5475
  }
5524
5476
  else {
5525
5477
  reject(new DOMException("The operation was aborted", "AbortError"));
@@ -5565,9 +5517,7 @@ Current user's request: ${currentInput}`;
5565
5517
  * @returns Current context or undefined if not set
5566
5518
  */
5567
5519
  getToolContext() {
5568
- return this.toolExecutionContext
5569
- ? { ...this.toolExecutionContext }
5570
- : undefined;
5520
+ return this.toolExecutionContext ? { ...this.toolExecutionContext } : undefined;
5571
5521
  }
5572
5522
  /**
5573
5523
  * Clear the tool execution context
@@ -5671,8 +5621,7 @@ Current user's request: ${currentInput}`;
5671
5621
  typeof this.conversationMemory.updateAgenticLoopReport !== "function") {
5672
5622
  throw new ConversationMemoryError("updateAgenticLoopReport is only supported with Redis conversation memory.", "CONFIG_ERROR");
5673
5623
  }
5674
- await withTimeout(this
5675
- .conversationMemory.updateAgenticLoopReport(sessionId, userId, report), 5000);
5624
+ await withTimeout(this.conversationMemory.updateAgenticLoopReport(sessionId, userId, report), 5000);
5676
5625
  }
5677
5626
  /**
5678
5627
  * Get all registered custom tools
@@ -5690,14 +5639,10 @@ Current user's request: ${currentInput}`;
5690
5639
  description: tool.description,
5691
5640
  hasParameters: !!tool.parameters,
5692
5641
  parametersType: typeof tool.parameters,
5693
- parametersKeys: tool.parameters && typeof tool.parameters === "object"
5694
- ? Object.keys(tool.parameters)
5695
- : "NOT_OBJECT",
5642
+ parametersKeys: tool.parameters && typeof tool.parameters === "object" ? Object.keys(tool.parameters) : "NOT_OBJECT",
5696
5643
  hasInputSchema: !!tool.inputSchema,
5697
5644
  inputSchemaType: typeof tool.inputSchema,
5698
- inputSchemaKeys: tool.inputSchema && typeof tool.inputSchema === "object"
5699
- ? Object.keys(tool.inputSchema)
5700
- : "NOT_OBJECT",
5645
+ inputSchemaKeys: tool.inputSchema && typeof tool.inputSchema === "object" ? Object.keys(tool.inputSchema) : "NOT_OBJECT",
5701
5646
  hasEffectiveSchema: !!effectiveSchema,
5702
5647
  effectiveSchemaType: typeof effectiveSchema,
5703
5648
  effectiveSchemaHasProperties: !!effectiveSchema?.properties,
@@ -5718,18 +5663,14 @@ Current user's request: ${currentInput}`;
5718
5663
  execute: async (params, context) => {
5719
5664
  // CONTEXT MERGING: Combine all available contexts for maximum information
5720
5665
  const storedContext = this.toolExecutionContext || {};
5721
- const runtimeContext = context && isNonNullObject(context)
5722
- ? context
5723
- : {};
5666
+ const runtimeContext = context && isNonNullObject(context) ? context : {};
5724
5667
  // Merge contexts with runtime context taking precedence
5725
5668
  // This ensures we have the richest possible context for tool execution
5726
5669
  const executionContext = {
5727
5670
  ...storedContext, // Base context from setToolContext (session, tokens, etc.)
5728
5671
  ...runtimeContext, // Runtime context from AI model (if any)
5729
5672
  // Ensure we always have at least a sessionId for tracing
5730
- sessionId: runtimeContext.sessionId ||
5731
- storedContext.sessionId ||
5732
- `fallback-${Date.now()}`,
5673
+ sessionId: runtimeContext.sessionId || storedContext.sessionId || `fallback-${Date.now()}`,
5733
5674
  };
5734
5675
  // Enhanced logging for context debugging
5735
5676
  logger.debug("Tool execution context merged", {
@@ -5737,8 +5678,7 @@ Current user's request: ${currentInput}`;
5737
5678
  storedContextKeys: Object.keys(storedContext),
5738
5679
  runtimeContextKeys: Object.keys(runtimeContext),
5739
5680
  finalContextKeys: Object.keys(executionContext),
5740
- hasJuspayToken: !!executionContext
5741
- .juspayToken,
5681
+ hasJuspayToken: !!executionContext.juspayToken,
5742
5682
  hasShopId: !!executionContext.shopId,
5743
5683
  sessionId: executionContext.sessionId,
5744
5684
  });
@@ -5766,9 +5706,7 @@ Current user's request: ${currentInput}`;
5766
5706
  toolMap.set(toolName, {
5767
5707
  name: toolName,
5768
5708
  description: toolDef.description || `File tool: ${toolName}`,
5769
- inputSchema: typeof toolParams === "object" && toolParams !== null
5770
- ? toolParams
5771
- : { type: "object", properties: {} },
5709
+ inputSchema: typeof toolParams === "object" && toolParams !== null ? toolParams : { type: "object", properties: {} },
5772
5710
  execute: async (params) => {
5773
5711
  return await toolDef.execute(params, {
5774
5712
  toolCallId: `file-tool-${Date.now()}`,
@@ -5879,17 +5817,9 @@ Current user's request: ${currentInput}`;
5879
5817
  // Determine tool type for span attributes
5880
5818
  const externalTools = this.externalServerManager.getAllTools();
5881
5819
  const externalTool = externalTools.find((tool) => tool.name === toolName);
5882
- const toolType = externalTool
5883
- ? "mcp"
5884
- : this.getCustomTools().has(toolName)
5885
- ? "custom"
5886
- : "external";
5820
+ const toolType = externalTool ? "mcp" : this.getCustomTools().has(toolName) ? "custom" : "external";
5887
5821
  // Compute truncated input size for the span
5888
- const inputStr = typeof params === "string"
5889
- ? params
5890
- : params
5891
- ? JSON.stringify(params)
5892
- : "";
5822
+ const inputStr = typeof params === "string" ? params : params ? JSON.stringify(params) : "";
5893
5823
  const inputSize = inputStr.length;
5894
5824
  const truncatedInput = inputStr.length > 2048 ? inputStr.substring(0, 2048) : inputStr;
5895
5825
  return tracers.mcp.startActiveSpan("neurolink.tool.execute", {
@@ -5904,9 +5834,7 @@ Current user's request: ${currentInput}`;
5904
5834
  // Debug: Log tool execution attempt
5905
5835
  logger.debug(`[${functionTag}] Tool execution requested:`, {
5906
5836
  toolName,
5907
- params: isNonNullObject(params)
5908
- ? transformParamsForLogging(params)
5909
- : params,
5837
+ params: isNonNullObject(params) ? transformParamsForLogging(params) : params,
5910
5838
  hasExternalManager: !!this.externalServerManager,
5911
5839
  });
5912
5840
  // 🔧 PARAMETER TRACE: Log tool execution details for debugging
@@ -5917,15 +5845,9 @@ Current user's request: ${currentInput}`;
5917
5845
  type: typeof params,
5918
5846
  isNull: params === null,
5919
5847
  isUndefined: params === undefined,
5920
- isEmpty: params &&
5921
- typeof params === "object" &&
5922
- Object.keys(params).length === 0,
5923
- keys: params && typeof params === "object"
5924
- ? Object.keys(params)
5925
- : "NOT_OBJECT",
5926
- keysLength: params && typeof params === "object"
5927
- ? Object.keys(params).length
5928
- : 0,
5848
+ isEmpty: params && typeof params === "object" && Object.keys(params).length === 0,
5849
+ keys: params && typeof params === "object" ? Object.keys(params) : "NOT_OBJECT",
5850
+ keysLength: params && typeof params === "object" ? Object.keys(params).length : 0,
5929
5851
  },
5930
5852
  isTargetTool: toolName === "juspay-analytics_SuccessRateSRByTime",
5931
5853
  options,
@@ -5945,12 +5867,8 @@ Current user's request: ${currentInput}`;
5945
5867
  const registeredTimeout = toolInfo?.tool?.timeoutMs;
5946
5868
  const registeredMaxRetries = toolInfo?.tool?.maxRetries;
5947
5869
  const finalOptions = {
5948
- timeout: options?.timeout ??
5949
- registeredTimeout ??
5950
- TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS,
5951
- maxRetries: options?.maxRetries ??
5952
- registeredMaxRetries ??
5953
- RETRY_ATTEMPTS.DEFAULT,
5870
+ timeout: options?.timeout ?? registeredTimeout ?? TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS,
5871
+ maxRetries: options?.maxRetries ?? registeredMaxRetries ?? RETRY_ATTEMPTS.DEFAULT,
5954
5872
  retryDelayMs: options?.retryDelayMs || RETRY_DELAYS.BASE_MS,
5955
5873
  authContext: options?.authContext,
5956
5874
  disableToolCache: options?.disableToolCache,
@@ -6013,9 +5931,7 @@ Current user's request: ${currentInput}`;
6013
5931
  metrics.successfulExecutions++;
6014
5932
  metrics.lastExecutionTime = executionTime;
6015
5933
  metrics.averageExecutionTime =
6016
- (metrics.averageExecutionTime *
6017
- (metrics.successfulExecutions - 1) +
6018
- executionTime) /
5934
+ (metrics.averageExecutionTime * (metrics.successfulExecutions - 1) + executionTime) /
6019
5935
  metrics.successfulExecutions;
6020
5936
  }
6021
5937
  // Track memory usage
@@ -6037,15 +5953,9 @@ Current user's request: ${currentInput}`;
6037
5953
  // Set span success attributes
6038
5954
  // Check if result has isError flag (MCP tool error result)
6039
5955
  // Also detect toolRegistry-wrapped errors that return { success: false }
6040
- const resultObj = result && typeof result === "object"
6041
- ? result
6042
- : undefined;
6043
- const isToolError = (resultObj &&
6044
- "isError" in resultObj &&
6045
- resultObj.isError === true) ||
6046
- (resultObj &&
6047
- "success" in resultObj &&
6048
- resultObj.success === false);
5956
+ const resultObj = result && typeof result === "object" ? result : undefined;
5957
+ const isToolError = (resultObj && "isError" in resultObj && resultObj.isError === true) ||
5958
+ (resultObj && "success" in resultObj && resultObj.success === false);
6049
5959
  // NL-001: Count isError:true results as circuit breaker failures
6050
5960
  // This ensures tools that return error results (not just thrown errors) are tracked
6051
5961
  // TODO(NL-009): This records a failure AFTER the circuit breaker already recorded
@@ -6076,10 +5986,7 @@ Current user's request: ${currentInput}`;
6076
5986
  const errorText = contentArr
6077
5987
  ?.filter((c) => c.type === "text" && c.text)
6078
5988
  .map((c) => c.text)
6079
- .join(" ") ||
6080
- (typeof resultObj.error === "string"
6081
- ? resultObj.error
6082
- : "Unknown error");
5989
+ .join(" ") || (typeof resultObj.error === "string" ? resultObj.error : "Unknown error");
6083
5990
  const errorCategory = classifyMcpErrorMessage(errorText);
6084
5991
  const prefix = `[TOOL_ERROR: ${toolName} failed (${errorCategory})] `;
6085
5992
  // NL-002: Clone content array to avoid mutating shared objects, then prefix error
@@ -6108,17 +6015,14 @@ Current user's request: ${currentInput}`;
6108
6015
  // which was incorrectly included as a success
6109
6016
  if (prevSuccessful > 1) {
6110
6017
  metrics.averageExecutionTime =
6111
- (metrics.averageExecutionTime * prevSuccessful -
6112
- executionTime) /
6113
- (prevSuccessful - 1);
6018
+ (metrics.averageExecutionTime * prevSuccessful - executionTime) / (prevSuccessful - 1);
6114
6019
  }
6115
6020
  else {
6116
6021
  // No remaining successful executions, reset to 0
6117
6022
  metrics.averageExecutionTime = 0;
6118
6023
  }
6119
6024
  const mappedCategory = mcpCategoryToErrorCategory(errorCategory);
6120
- metrics.errorCategories[mappedCategory] =
6121
- (metrics.errorCategories[mappedCategory] || 0) + 1;
6025
+ metrics.errorCategories[mappedCategory] = (metrics.errorCategories[mappedCategory] || 0) + 1;
6122
6026
  }
6123
6027
  }
6124
6028
  // Emit tool end event AFTER isError check so success flag is correct
@@ -6147,8 +6051,7 @@ Current user's request: ${currentInput}`;
6147
6051
  });
6148
6052
  if (metrics) {
6149
6053
  const category = ErrorCategory.EXECUTION;
6150
- metrics.errorCategories[category] =
6151
- (metrics.errorCategories[category] || 0) + 1;
6054
+ metrics.errorCategories[category] = (metrics.errorCategories[category] || 0) + 1;
6152
6055
  }
6153
6056
  // Emit tool end event for circuit breaker open
6154
6057
  this.emitToolEndEvent(toolName, executionStartTime, false, undefined);
@@ -6192,12 +6095,10 @@ Current user's request: ${currentInput}`;
6192
6095
  const availableTools = await this.getAllAvailableTools();
6193
6096
  structuredError = ErrorFactory.toolNotFound(toolName, extractToolNames(availableTools.map((t) => ({ name: t.name }))));
6194
6097
  }
6195
- else if (error.message.includes("validation") ||
6196
- error.message.includes("parameter")) {
6098
+ else if (error.message.includes("validation") || error.message.includes("parameter")) {
6197
6099
  structuredError = ErrorFactory.invalidParameters(toolName, error, params);
6198
6100
  }
6199
- else if (error.message.includes("network") ||
6200
- error.message.includes("connection")) {
6101
+ else if (error.message.includes("network") || error.message.includes("connection")) {
6201
6102
  structuredError = ErrorFactory.networkError(toolName, error);
6202
6103
  }
6203
6104
  else {
@@ -6209,8 +6110,7 @@ Current user's request: ${currentInput}`;
6209
6110
  }
6210
6111
  if (metrics) {
6211
6112
  const category = structuredError.category || ErrorCategory.EXECUTION;
6212
- metrics.errorCategories[category] =
6213
- (metrics.errorCategories[category] || 0) + 1;
6113
+ metrics.errorCategories[category] = (metrics.errorCategories[category] || 0) + 1;
6214
6114
  }
6215
6115
  // Emit tool end event BEFORE the error event.
6216
6116
  // Node.js EventEmitter throws on unhandled 'error' events,
@@ -6247,9 +6147,7 @@ Current user's request: ${currentInput}`;
6247
6147
  catch (outerError) {
6248
6148
  // If the error was not already recorded on the span (from inner catch), record it
6249
6149
  if (!(outerError instanceof NeuroLinkError)) {
6250
- const errMsg = outerError instanceof Error
6251
- ? outerError.message
6252
- : String(outerError);
6150
+ const errMsg = outerError instanceof Error ? outerError.message : String(outerError);
6253
6151
  toolSpan.recordException(outerError instanceof Error ? outerError : new Error(errMsg));
6254
6152
  toolSpan.setStatus({ code: SpanStatusCode.ERROR, message: errMsg });
6255
6153
  }
@@ -6275,9 +6173,17 @@ Current user's request: ${currentInput}`;
6275
6173
  !options.disableToolCache &&
6276
6174
  !this._disableToolCacheForCurrentRequest &&
6277
6175
  !toolAnnotations?.destructiveHint;
6176
+ const toolResultCache = this.mcpToolResultCache;
6278
6177
  // === MCP ENHANCEMENT: Cache check (before execution) ===
6279
- if (isCacheEnabled) {
6280
- const cached = this.mcpToolResultCache.getCachedResult(toolName, params);
6178
+ // Scope cache key by auth context to prevent cross-user cache leaks
6179
+ const cacheParams = options.authContext || this.toolExecutionContext
6180
+ ? {
6181
+ __args: params,
6182
+ __ctx: options.authContext ?? this.toolExecutionContext,
6183
+ }
6184
+ : params;
6185
+ if (isCacheEnabled && toolResultCache) {
6186
+ const cached = toolResultCache.getCachedResult(toolName, cacheParams);
6281
6187
  if (cached !== undefined) {
6282
6188
  logger.debug(`[${functionTag}] Cache HIT for tool: ${toolName}`);
6283
6189
  return cached;
@@ -6328,9 +6234,7 @@ Current user's request: ${currentInput}`;
6328
6234
  inputSchema: {},
6329
6235
  };
6330
6236
  const decision = this.mcpToolRouter.route(mcpTool);
6331
- externalTool =
6332
- matchingTools.find((t) => t.serverId === decision.serverId) ||
6333
- matchingTools[0];
6237
+ externalTool = matchingTools.find((t) => t.serverId === decision.serverId) || matchingTools[0];
6334
6238
  logger.debug(`[${functionTag}] Router selected server: ${decision.serverId}`, {
6335
6239
  strategy: decision.strategy,
6336
6240
  confidence: decision.confidence,
@@ -6386,10 +6290,7 @@ Current user's request: ${currentInput}`;
6386
6290
  });
6387
6291
  const result = (await this.toolRegistry.executeTool(toolName, params, context));
6388
6292
  // Check if result indicates a failure and emit error event
6389
- if (result &&
6390
- typeof result === "object" &&
6391
- "success" in result &&
6392
- result.success === false) {
6293
+ if (result && typeof result === "object" && "success" in result && result.success === false) {
6393
6294
  const errorMessage = result.error || "Tool execution failed";
6394
6295
  const errorToEmit = new Error(errorMessage);
6395
6296
  this.emitter.emit("error", errorToEmit);
@@ -6411,8 +6312,8 @@ Current user's request: ${currentInput}`;
6411
6312
  try {
6412
6313
  const result = await executeWithMiddleware(executeCore);
6413
6314
  // === MCP ENHANCEMENT: Cache store (after successful execution) ===
6414
- if (isCacheEnabled && result !== undefined) {
6415
- this.mcpToolResultCache.cacheResult(toolName, params, result);
6315
+ if (isCacheEnabled && toolResultCache && result !== undefined) {
6316
+ toolResultCache.cacheResult(toolName, cacheParams, result);
6416
6317
  logger.debug(`[${functionTag}] Cached result for tool: ${toolName}`);
6417
6318
  }
6418
6319
  return result;
@@ -6427,16 +6328,13 @@ Current user's request: ${currentInput}`;
6427
6328
  execute: async () => ({}),
6428
6329
  }
6429
6330
  : undefined;
6430
- if (toolStubForRetry &&
6431
- isSafeToRetry(toolStubForRetry) &&
6432
- error instanceof Error &&
6433
- isRetriableError(error)) {
6331
+ if (toolStubForRetry && isSafeToRetry(toolStubForRetry) && error instanceof Error && isRetriableError(error)) {
6434
6332
  logger.debug(`[${functionTag}] Tool ${toolName} is safe to retry, attempting once more`);
6435
6333
  try {
6436
6334
  const retryResult = await executeWithMiddleware(executeCore);
6437
6335
  // Cache the retry result
6438
- if (isCacheEnabled && retryResult !== undefined) {
6439
- this.mcpToolResultCache.cacheResult(toolName, params, retryResult);
6336
+ if (isCacheEnabled && toolResultCache && retryResult !== undefined) {
6337
+ toolResultCache.cacheResult(toolName, cacheParams, retryResult);
6440
6338
  }
6441
6339
  return retryResult;
6442
6340
  }
@@ -6475,8 +6373,7 @@ Current user's request: ${currentInput}`;
6475
6373
  }
6476
6374
  async getAllAvailableTools() {
6477
6375
  // Return from cache if available and not stale
6478
- if (this.toolCache &&
6479
- Date.now() - this.toolCache.timestamp < this.toolCacheDuration) {
6376
+ if (this.toolCache && Date.now() - this.toolCache.timestamp < this.toolCacheDuration) {
6480
6377
  logger.debug("Returning available tools from cache");
6481
6378
  return this.toolCache.tools;
6482
6379
  }
@@ -6557,9 +6454,7 @@ Current user's request: ${currentInput}`;
6557
6454
  if (!allTools.has(tool.name)) {
6558
6455
  const optimizedTool = optimizeToolForCollection(tool, {
6559
6456
  category: detectCategory({
6560
- existingCategory: typeof tool.metadata?.category === "string"
6561
- ? tool.metadata.category
6562
- : undefined,
6457
+ existingCategory: typeof tool.metadata?.category === "string" ? tool.metadata.category : undefined,
6563
6458
  isExternal: true,
6564
6459
  serverId: tool.serverId,
6565
6460
  }),
@@ -6715,9 +6610,7 @@ Current user's request: ${currentInput}`;
6715
6610
  status: "failed",
6716
6611
  configured: false,
6717
6612
  authenticated: false,
6718
- error: error instanceof Error
6719
- ? error.message
6720
- : "Ollama service not running",
6613
+ error: error instanceof Error ? error.message : "Ollama service not running",
6721
6614
  responseTime: Date.now() - startTime,
6722
6615
  };
6723
6616
  }
@@ -6840,9 +6733,7 @@ Current user's request: ${currentInput}`;
6840
6733
  inMemoryServerInfos.length +
6841
6734
  builtInServerInfos.length +
6842
6735
  autoDiscoveredServerInfos.length;
6843
- const availableServers = externalStats.connectedServers +
6844
- inMemoryServerInfos.length +
6845
- builtInServerInfos.length; // in-memory and built-in always available
6736
+ const availableServers = externalStats.connectedServers + inMemoryServerInfos.length + builtInServerInfos.length; // in-memory and built-in always available
6846
6737
  const totalTools = allTools.length + externalStats.totalTools;
6847
6738
  return {
6848
6739
  mcpInitialized: this.mcpInitialized,
@@ -6911,8 +6802,7 @@ Current user's request: ${currentInput}`;
6911
6802
  // Test external MCP servers
6912
6803
  const externalServer = this.externalServerManager.getServer(serverId);
6913
6804
  if (externalServer) {
6914
- return (externalServer.status === "connected" &&
6915
- externalServer.client !== null);
6805
+ return externalServer.status === "connected" && externalServer.client !== null;
6916
6806
  }
6917
6807
  return false;
6918
6808
  }
@@ -7032,9 +6922,7 @@ Current user's request: ${currentInput}`;
7032
6922
  metrics[toolName] = {
7033
6923
  ...toolMetrics,
7034
6924
  errorCategories: { ...toolMetrics.errorCategories },
7035
- successRate: toolMetrics.totalExecutions > 0
7036
- ? toolMetrics.successfulExecutions / toolMetrics.totalExecutions
7037
- : 0,
6925
+ successRate: toolMetrics.totalExecutions > 0 ? toolMetrics.successfulExecutions / toolMetrics.totalExecutions : 0,
7038
6926
  };
7039
6927
  }
7040
6928
  return metrics;
@@ -7054,7 +6942,7 @@ Current user's request: ${currentInput}`;
7054
6942
  */
7055
6943
  getToolCircuitBreakerStatus() {
7056
6944
  const status = {};
7057
- for (const [toolName, circuitBreaker,] of this.toolCircuitBreakers.entries()) {
6945
+ for (const [toolName, circuitBreaker] of this.toolCircuitBreakers.entries()) {
7058
6946
  status[toolName] = {
7059
6947
  state: circuitBreaker.getState(),
7060
6948
  failureCount: circuitBreaker.getFailureCount(),
@@ -7107,8 +6995,7 @@ Current user's request: ${currentInput}`;
7107
6995
  ? metrics.successfulExecutions / metrics.totalExecutions
7108
6996
  : 0
7109
6997
  : 0;
7110
- const isHealthy = (!circuitBreaker || circuitBreaker.getState() === "closed") &&
7111
- successRate >= 0.8;
6998
+ const isHealthy = (!circuitBreaker || circuitBreaker.getState() === "closed") && successRate >= 0.8;
7112
6999
  if (isHealthy) {
7113
7000
  healthyCount++;
7114
7001
  }
@@ -7149,9 +7036,7 @@ Current user's request: ${currentInput}`;
7149
7036
  successRate,
7150
7037
  averageExecutionTime: metrics?.averageExecutionTime || 0,
7151
7038
  lastExecutionTime: metrics?.lastExecutionTime || 0,
7152
- errorCategories: metrics?.errorCategories
7153
- ? { ...metrics.errorCategories }
7154
- : {},
7039
+ errorCategories: metrics?.errorCategories ? { ...metrics.errorCategories } : {},
7155
7040
  },
7156
7041
  circuitBreaker: {
7157
7042
  state: circuitBreaker?.getState() || "closed",
@@ -7303,8 +7188,7 @@ Current user's request: ${currentInput}`;
7303
7188
  */
7304
7189
  async storeToolExecutions(sessionId, userId, toolCalls, toolResults, currentTime) {
7305
7190
  // Check if tools are not empty
7306
- const hasToolData = (toolCalls && toolCalls.length > 0) ||
7307
- (toolResults && toolResults.length > 0);
7191
+ const hasToolData = (toolCalls && toolCalls.length > 0) || (toolResults && toolResults.length > 0);
7308
7192
  if (!hasToolData) {
7309
7193
  logger.debug("Tool execution storage skipped", {
7310
7194
  hasToolData,
@@ -7314,8 +7198,7 @@ Current user's request: ${currentInput}`;
7314
7198
  return;
7315
7199
  }
7316
7200
  // Type guard to ensure it's Redis conversation memory manager
7317
- const redisMemory = this
7318
- .conversationMemory;
7201
+ const redisMemory = this.conversationMemory;
7319
7202
  try {
7320
7203
  await redisMemory.storeToolExecution(sessionId, userId, toolCalls, toolResults, currentTime);
7321
7204
  }
@@ -7334,9 +7217,7 @@ Current user's request: ${currentInput}`;
7334
7217
  */
7335
7218
  isToolExecutionStorageAvailable() {
7336
7219
  const isRedisStorage = process.env.STORAGE_TYPE === "redis";
7337
- const hasRedisConversationMemory = this.conversationMemory &&
7338
- this.conversationMemory.constructor.name ===
7339
- "RedisConversationMemoryManager";
7220
+ const hasRedisConversationMemory = this.conversationMemory && this.conversationMemory.constructor.name === "RedisConversationMemoryManager";
7340
7221
  return !!(isRedisStorage && hasRedisConversationMemory);
7341
7222
  }
7342
7223
  /**
@@ -7855,8 +7736,7 @@ Current user's request: ${currentInput}`;
7855
7736
  return null;
7856
7737
  }
7857
7738
  // Check for explicit annotations set on the tool first
7858
- const explicitAnnotations = toolInfo.tool
7859
- .annotations;
7739
+ const explicitAnnotations = toolInfo.tool.annotations;
7860
7740
  // Infer annotations from the tool name/description as fallback
7861
7741
  const inferredAnnotations = inferAnnotations({
7862
7742
  name: toolInfo.tool.name,
@@ -7888,9 +7768,7 @@ Current user's request: ${currentInput}`;
7888
7768
  const result = await this.externalServerManager.executeTool(tool.serverId, tool.name, params, { timeout: 30000 });
7889
7769
  mcpLogger.debug(`[NeuroLink] External MCP tool execution result: ${tool.name}`, {
7890
7770
  success: !!result,
7891
- hasData: !!(result &&
7892
- typeof result === "object" &&
7893
- "content" in result),
7771
+ hasData: !!(result && typeof result === "object" && "content" in result),
7894
7772
  });
7895
7773
  return result;
7896
7774
  }
@@ -8306,9 +8184,7 @@ Current user's request: ${currentInput}`;
8306
8184
  logger.debug("[NeuroLink] OpenTelemetry shutdown successfully");
8307
8185
  }
8308
8186
  catch (error) {
8309
- const err = error instanceof Error
8310
- ? error
8311
- : new Error(`OpenTelemetry shutdown error: ${String(error)}`);
8187
+ const err = error instanceof Error ? error : new Error(`OpenTelemetry shutdown error: ${String(error)}`);
8312
8188
  cleanupErrors.push(err);
8313
8189
  logger.warn("[NeuroLink] Error shutting down OpenTelemetry:", error);
8314
8190
  }
@@ -8320,9 +8196,7 @@ Current user's request: ${currentInput}`;
8320
8196
  logger.debug("[NeuroLink] External MCP servers shutdown successfully");
8321
8197
  }
8322
8198
  catch (error) {
8323
- const err = error instanceof Error
8324
- ? error
8325
- : new Error(`External server shutdown error: ${String(error)}`);
8199
+ const err = error instanceof Error ? error : new Error(`External server shutdown error: ${String(error)}`);
8326
8200
  cleanupErrors.push(err);
8327
8201
  logger.warn("[NeuroLink] Error shutting down external MCP servers:", error);
8328
8202
  }
@@ -8336,9 +8210,7 @@ Current user's request: ${currentInput}`;
8336
8210
  logger.debug("[NeuroLink] Event listeners removed successfully");
8337
8211
  }
8338
8212
  catch (error) {
8339
- const err = error instanceof Error
8340
- ? error
8341
- : new Error(`Event emitter cleanup error: ${String(error)}`);
8213
+ const err = error instanceof Error ? error : new Error(`Event emitter cleanup error: ${String(error)}`);
8342
8214
  cleanupErrors.push(err);
8343
8215
  logger.warn("[NeuroLink] Error removing event listeners:", error);
8344
8216
  }
@@ -8351,9 +8223,7 @@ Current user's request: ${currentInput}`;
8351
8223
  logger.debug("[NeuroLink] Circuit breakers cleared successfully");
8352
8224
  }
8353
8225
  catch (error) {
8354
- const err = error instanceof Error
8355
- ? error
8356
- : new Error(`Circuit breaker cleanup error: ${String(error)}`);
8226
+ const err = error instanceof Error ? error : new Error(`Circuit breaker cleanup error: ${String(error)}`);
8357
8227
  cleanupErrors.push(err);
8358
8228
  logger.warn("[NeuroLink] Error clearing circuit breakers:", error);
8359
8229
  }
@@ -8390,12 +8260,23 @@ Current user's request: ${currentInput}`;
8390
8260
  logger.debug("[NeuroLink] Maps and caches cleared successfully");
8391
8261
  }
8392
8262
  catch (error) {
8393
- const err = error instanceof Error
8394
- ? error
8395
- : new Error(`Cache cleanup error: ${String(error)}`);
8263
+ const err = error instanceof Error ? error : new Error(`Cache cleanup error: ${String(error)}`);
8396
8264
  cleanupErrors.push(err);
8397
8265
  logger.warn("[NeuroLink] Error clearing caches:", error);
8398
8266
  }
8267
+ // 5b. Shutdown TaskManager
8268
+ if (this._taskManager) {
8269
+ try {
8270
+ logger.debug("[NeuroLink] Shutting down TaskManager...");
8271
+ await withTimeout(this._taskManager.shutdown(), 5000, new Error("TaskManager shutdown timed out"));
8272
+ }
8273
+ catch (error) {
8274
+ logger.warn("[NeuroLink] TaskManager shutdown error:", error);
8275
+ }
8276
+ finally {
8277
+ this._taskManager = undefined;
8278
+ }
8279
+ }
8399
8280
  // 6. Reset initialization flags
8400
8281
  try {
8401
8282
  logger.debug("[NeuroLink] Resetting initialization state...");
@@ -8405,9 +8286,7 @@ Current user's request: ${currentInput}`;
8405
8286
  logger.debug("[NeuroLink] Initialization state reset successfully");
8406
8287
  }
8407
8288
  catch (error) {
8408
- const err = error instanceof Error
8409
- ? error
8410
- : new Error(`State reset error: ${String(error)}`);
8289
+ const err = error instanceof Error ? error : new Error(`State reset error: ${String(error)}`);
8411
8290
  cleanupErrors.push(err);
8412
8291
  logger.warn("[NeuroLink] Error resetting state:", error);
8413
8292
  }
@@ -8451,11 +8330,8 @@ Current user's request: ${currentInput}`;
8451
8330
  }
8452
8331
  const compactor = new ContextCompactor({
8453
8332
  ...config,
8454
- summarizationProvider: config?.summarizationProvider ??
8455
- this.conversationMemoryConfig?.conversationMemory
8456
- ?.summarizationProvider,
8457
- summarizationModel: config?.summarizationModel ??
8458
- this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
8333
+ summarizationProvider: config?.summarizationProvider ?? this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
8334
+ summarizationModel: config?.summarizationModel ?? this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
8459
8335
  });
8460
8336
  // Use actual context window to determine target, not arbitrary heuristic
8461
8337
  const budgetInfo = checkContextBudget({
@@ -8524,28 +8400,32 @@ Current user's request: ${currentInput}`;
8524
8400
  async setAuthProvider(config) {
8525
8401
  // Clear any pending lazy-init promise so it does not race with this call.
8526
8402
  this.authInitPromise = undefined;
8403
+ await this.initializeAuthProviderFromConfig(config);
8404
+ }
8405
+ async initializeAuthProviderFromConfig(config) {
8406
+ let provider;
8407
+ let providerType;
8527
8408
  // Duck-type check: direct MastraAuthProvider instance
8528
- if ("authenticateToken" in config &&
8529
- typeof config.authenticateToken === "function") {
8530
- this.authProvider = config;
8531
- logger.info(`Auth provider set: ${this.authProvider.type}`);
8409
+ if ("authenticateToken" in config && typeof config.authenticateToken === "function") {
8410
+ provider = config;
8411
+ providerType = provider.type;
8532
8412
  }
8533
8413
  else if ("provider" in config) {
8534
- this.authProvider = config.provider;
8535
- logger.info(`Auth provider set: ${this.authProvider.type}`);
8414
+ provider = config.provider;
8415
+ providerType = provider.type;
8536
8416
  }
8537
8417
  else {
8538
8418
  const typedConfig = config;
8539
8419
  const { AuthProviderFactory } = await import("./auth/AuthProviderFactory.js");
8540
- this.authProvider = await AuthProviderFactory.createProvider(typedConfig.type, typedConfig.config);
8541
- logger.info(`Auth provider created and set: ${typedConfig.type}`);
8542
- }
8543
- if (this.authProvider) {
8544
- this.emitter.emit("auth:provider:set", {
8545
- type: this.authProvider.type,
8546
- timestamp: Date.now(),
8547
- });
8420
+ provider = await AuthProviderFactory.createProvider(typedConfig.type, typedConfig.config);
8421
+ providerType = typedConfig.type;
8548
8422
  }
8423
+ this.authProvider = provider;
8424
+ logger.info(`Auth provider set: ${providerType}`);
8425
+ this.emitter.emit("auth:provider:set", {
8426
+ type: provider.type,
8427
+ timestamp: Date.now(),
8428
+ });
8549
8429
  }
8550
8430
  /**
8551
8431
  * Get the currently configured authentication provider
@@ -8562,14 +8442,17 @@ Current user's request: ${currentInput}`;
8562
8442
  if (this.authProvider || !this.pendingAuthConfig) {
8563
8443
  return;
8564
8444
  }
8445
+ const pendingAuthConfig = this.pendingAuthConfig;
8565
8446
  this.authInitPromise ??= (async () => {
8566
8447
  try {
8567
- await this.setAuthProvider(this.pendingAuthConfig);
8448
+ await this.initializeAuthProviderFromConfig(pendingAuthConfig);
8568
8449
  this.pendingAuthConfig = undefined;
8569
8450
  }
8570
- catch (err) {
8571
- this.authInitPromise = undefined;
8572
- throw err;
8451
+ finally {
8452
+ if (this.authInitPromise &&
8453
+ (this.pendingAuthConfig === undefined || this.pendingAuthConfig === pendingAuthConfig)) {
8454
+ this.authInitPromise = undefined;
8455
+ }
8573
8456
  }
8574
8457
  })();
8575
8458
  await this.authInitPromise;