@juspay/neurolink 7.6.1 → 7.7.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 (136) hide show
  1. package/CHANGELOG.md +9 -4
  2. package/README.md +78 -3
  3. package/dist/cli/commands/config.d.ts +275 -3
  4. package/dist/cli/commands/config.js +121 -0
  5. package/dist/cli/commands/mcp.js +77 -28
  6. package/dist/cli/factories/commandFactory.js +359 -6
  7. package/dist/core/analytics.js +7 -27
  8. package/dist/core/baseProvider.js +43 -4
  9. package/dist/core/constants.d.ts +46 -0
  10. package/dist/core/constants.js +47 -0
  11. package/dist/core/dynamicModels.d.ts +16 -4
  12. package/dist/core/dynamicModels.js +130 -26
  13. package/dist/core/evaluation.js +5 -1
  14. package/dist/core/evaluationProviders.d.ts +6 -2
  15. package/dist/core/evaluationProviders.js +41 -125
  16. package/dist/core/factory.d.ts +5 -0
  17. package/dist/core/factory.js +62 -50
  18. package/dist/core/modelConfiguration.d.ts +246 -0
  19. package/dist/core/modelConfiguration.js +775 -0
  20. package/dist/core/types.d.ts +22 -3
  21. package/dist/core/types.js +5 -1
  22. package/dist/factories/providerRegistry.js +3 -3
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/lib/core/analytics.js +7 -27
  26. package/dist/lib/core/baseProvider.js +43 -4
  27. package/dist/lib/core/constants.d.ts +46 -0
  28. package/dist/lib/core/constants.js +47 -0
  29. package/dist/lib/core/dynamicModels.d.ts +16 -4
  30. package/dist/lib/core/dynamicModels.js +130 -26
  31. package/dist/lib/core/evaluation.js +5 -1
  32. package/dist/lib/core/evaluationProviders.d.ts +6 -2
  33. package/dist/lib/core/evaluationProviders.js +41 -125
  34. package/dist/lib/core/factory.d.ts +5 -0
  35. package/dist/lib/core/factory.js +63 -50
  36. package/dist/lib/core/modelConfiguration.d.ts +246 -0
  37. package/dist/lib/core/modelConfiguration.js +775 -0
  38. package/dist/lib/core/types.d.ts +22 -3
  39. package/dist/lib/core/types.js +5 -1
  40. package/dist/lib/factories/providerRegistry.js +3 -3
  41. package/dist/lib/index.d.ts +1 -1
  42. package/dist/lib/index.js +1 -1
  43. package/dist/lib/mcp/factory.d.ts +5 -5
  44. package/dist/lib/mcp/factory.js +2 -2
  45. package/dist/lib/mcp/servers/utilities/utilityServer.d.ts +1 -1
  46. package/dist/lib/mcp/servers/utilities/utilityServer.js +1 -1
  47. package/dist/lib/mcp/toolRegistry.js +2 -2
  48. package/dist/lib/neurolink.d.ts +168 -12
  49. package/dist/lib/neurolink.js +685 -123
  50. package/dist/lib/providers/anthropic.js +52 -2
  51. package/dist/lib/providers/googleAiStudio.js +4 -0
  52. package/dist/lib/providers/googleVertex.d.ts +75 -9
  53. package/dist/lib/providers/googleVertex.js +365 -46
  54. package/dist/lib/providers/huggingFace.d.ts +52 -11
  55. package/dist/lib/providers/huggingFace.js +180 -42
  56. package/dist/lib/providers/litellm.d.ts +9 -9
  57. package/dist/lib/providers/litellm.js +103 -16
  58. package/dist/lib/providers/ollama.d.ts +52 -17
  59. package/dist/lib/providers/ollama.js +276 -68
  60. package/dist/lib/sdk/toolRegistration.d.ts +42 -0
  61. package/dist/lib/sdk/toolRegistration.js +269 -27
  62. package/dist/lib/telemetry/telemetryService.d.ts +6 -0
  63. package/dist/lib/telemetry/telemetryService.js +38 -3
  64. package/dist/lib/types/contextTypes.d.ts +75 -11
  65. package/dist/lib/types/contextTypes.js +227 -1
  66. package/dist/lib/types/domainTypes.d.ts +62 -0
  67. package/dist/lib/types/domainTypes.js +5 -0
  68. package/dist/lib/types/generateTypes.d.ts +52 -0
  69. package/dist/lib/types/index.d.ts +1 -0
  70. package/dist/lib/types/mcpTypes.d.ts +1 -1
  71. package/dist/lib/types/mcpTypes.js +1 -1
  72. package/dist/lib/types/streamTypes.d.ts +14 -0
  73. package/dist/lib/types/universalProviderOptions.d.ts +1 -1
  74. package/dist/lib/utils/errorHandling.d.ts +142 -0
  75. package/dist/lib/utils/errorHandling.js +316 -0
  76. package/dist/lib/utils/factoryProcessing.d.ts +74 -0
  77. package/dist/lib/utils/factoryProcessing.js +588 -0
  78. package/dist/lib/utils/optionsConversion.d.ts +54 -0
  79. package/dist/lib/utils/optionsConversion.js +126 -0
  80. package/dist/lib/utils/optionsUtils.d.ts +246 -0
  81. package/dist/lib/utils/optionsUtils.js +960 -0
  82. package/dist/lib/utils/providerHealth.d.ts +107 -0
  83. package/dist/lib/utils/providerHealth.js +507 -0
  84. package/dist/lib/utils/providerUtils.d.ts +17 -0
  85. package/dist/lib/utils/providerUtils.js +271 -16
  86. package/dist/lib/utils/timeout.js +1 -1
  87. package/dist/lib/utils/tokenLimits.d.ts +33 -0
  88. package/dist/lib/utils/tokenLimits.js +118 -0
  89. package/dist/mcp/factory.d.ts +5 -5
  90. package/dist/mcp/factory.js +2 -2
  91. package/dist/mcp/servers/utilities/utilityServer.d.ts +1 -1
  92. package/dist/mcp/servers/utilities/utilityServer.js +1 -1
  93. package/dist/mcp/toolRegistry.js +2 -2
  94. package/dist/neurolink.d.ts +168 -12
  95. package/dist/neurolink.js +685 -123
  96. package/dist/providers/anthropic.js +52 -2
  97. package/dist/providers/googleAiStudio.js +4 -0
  98. package/dist/providers/googleVertex.d.ts +75 -9
  99. package/dist/providers/googleVertex.js +365 -46
  100. package/dist/providers/huggingFace.d.ts +52 -11
  101. package/dist/providers/huggingFace.js +181 -43
  102. package/dist/providers/litellm.d.ts +9 -9
  103. package/dist/providers/litellm.js +103 -16
  104. package/dist/providers/ollama.d.ts +52 -17
  105. package/dist/providers/ollama.js +276 -68
  106. package/dist/sdk/toolRegistration.d.ts +42 -0
  107. package/dist/sdk/toolRegistration.js +269 -27
  108. package/dist/telemetry/telemetryService.d.ts +6 -0
  109. package/dist/telemetry/telemetryService.js +38 -3
  110. package/dist/types/contextTypes.d.ts +75 -11
  111. package/dist/types/contextTypes.js +227 -2
  112. package/dist/types/domainTypes.d.ts +62 -0
  113. package/dist/types/domainTypes.js +5 -0
  114. package/dist/types/generateTypes.d.ts +52 -0
  115. package/dist/types/index.d.ts +1 -0
  116. package/dist/types/mcpTypes.d.ts +1 -1
  117. package/dist/types/mcpTypes.js +1 -1
  118. package/dist/types/streamTypes.d.ts +14 -0
  119. package/dist/types/universalProviderOptions.d.ts +1 -1
  120. package/dist/types/universalProviderOptions.js +0 -1
  121. package/dist/utils/errorHandling.d.ts +142 -0
  122. package/dist/utils/errorHandling.js +316 -0
  123. package/dist/utils/factoryProcessing.d.ts +74 -0
  124. package/dist/utils/factoryProcessing.js +588 -0
  125. package/dist/utils/optionsConversion.d.ts +54 -0
  126. package/dist/utils/optionsConversion.js +126 -0
  127. package/dist/utils/optionsUtils.d.ts +246 -0
  128. package/dist/utils/optionsUtils.js +960 -0
  129. package/dist/utils/providerHealth.d.ts +107 -0
  130. package/dist/utils/providerHealth.js +507 -0
  131. package/dist/utils/providerUtils.d.ts +17 -0
  132. package/dist/utils/providerUtils.js +271 -16
  133. package/dist/utils/timeout.js +1 -1
  134. package/dist/utils/tokenLimits.d.ts +33 -0
  135. package/dist/utils/tokenLimits.js +118 -0
  136. package/package.json +2 -2
@@ -22,12 +22,19 @@ import { logger } from "./utils/logger.js";
22
22
  import { getBestProvider } from "./utils/providerUtils.js";
23
23
  import { ProviderRegistry } from "./factories/providerRegistry.js";
24
24
  import { validateTool, createMCPServerFromTools, } from "./sdk/toolRegistration.js";
25
+ // Factory processing imports
26
+ import { processFactoryOptions, enhanceTextGenerationOptions, validateFactoryConfig, processStreamingFactoryOptions, createCleanStreamOptions, } from "./utils/factoryProcessing.js";
27
+ // Enhanced error handling imports
28
+ import { ErrorFactory, NeuroLinkError, withTimeout, withRetry, isRetriableError, logStructuredError, CircuitBreaker, } from "./utils/errorHandling.js";
25
29
  // Core types imported from core/types.js
26
30
  export class NeuroLink {
27
31
  mcpInitialized = false;
28
32
  // Tool registration support
29
33
  customTools = new Map();
30
34
  inMemoryServers = new Map();
35
+ // Enhanced error handling support
36
+ toolCircuitBreakers = new Map();
37
+ toolExecutionMetrics = new Map();
31
38
  constructor() {
32
39
  // SDK always disables manual MCP config for security
33
40
  ProviderRegistry.setOptions({
@@ -42,6 +49,10 @@ export class NeuroLink {
42
49
  if (this.mcpInitialized) {
43
50
  return;
44
51
  }
52
+ // Track memory usage during MCP initialization
53
+ const { MemoryManager } = await import("./utils/performance.js");
54
+ const startMemory = MemoryManager.getMemoryUsageMB();
55
+ const initStartTime = Date.now();
45
56
  try {
46
57
  mcpLogger.debug("[NeuroLink] Starting isolated MCP initialization...");
47
58
  // Initialize tool registry with timeout protection
@@ -55,7 +66,18 @@ export class NeuroLink {
55
66
  // Register all providers with lazy loading support
56
67
  await ProviderRegistry.registerAllProviders();
57
68
  this.mcpInitialized = true;
58
- mcpLogger.debug("[NeuroLink] MCP initialization completed successfully");
69
+ // Monitor memory usage and provide cleanup suggestions
70
+ const endMemory = MemoryManager.getMemoryUsageMB();
71
+ const memoryDelta = endMemory.heapUsed - startMemory.heapUsed;
72
+ const initTime = Date.now() - initStartTime;
73
+ mcpLogger.debug("[NeuroLink] MCP initialization completed successfully", {
74
+ initTime: `${initTime}ms`,
75
+ memoryUsed: `${memoryDelta}MB`,
76
+ });
77
+ // Suggest cleanup if initialization used significant memory
78
+ if (memoryDelta > 30) {
79
+ mcpLogger.debug("💡 Memory cleanup suggestion: MCP initialization used significant memory. Consider calling MemoryManager.forceGC() after heavy operations.");
80
+ }
59
81
  }
60
82
  catch (error) {
61
83
  mcpLogger.warn("[NeuroLink] MCP initialization failed", {
@@ -78,22 +100,46 @@ export class NeuroLink {
78
100
  if (!options.input?.text || typeof options.input.text !== "string") {
79
101
  throw new Error("Input text is required and must be a non-empty string");
80
102
  }
81
- // Convert to internal TextGenerationOptions for compatibility
82
- const textOptions = {
103
+ // Process factory configuration
104
+ const factoryResult = processFactoryOptions(options);
105
+ // Validate factory configuration if present
106
+ if (factoryResult.hasFactoryConfig && options.factoryConfig) {
107
+ const validation = validateFactoryConfig(options.factoryConfig);
108
+ if (!validation.isValid) {
109
+ logger.warn("Invalid factory configuration detected", {
110
+ errors: validation.errors,
111
+ });
112
+ // Continue with warning rather than throwing - graceful degradation
113
+ }
114
+ }
115
+ // Convert to TextGenerationOptions using factory utilities
116
+ const baseOptions = {
83
117
  prompt: options.input.text,
84
118
  provider: options.provider,
85
119
  model: options.model,
86
120
  temperature: options.temperature,
87
121
  maxTokens: options.maxTokens,
88
122
  systemPrompt: options.systemPrompt,
89
- disableTools: options.disableTools, // FIX: Pass disableTools flag
90
- // 🔧 FIX: Include analytics and evaluation options!
123
+ disableTools: options.disableTools,
91
124
  enableAnalytics: options.enableAnalytics,
92
125
  enableEvaluation: options.enableEvaluation,
93
126
  context: options.context,
94
127
  evaluationDomain: options.evaluationDomain,
95
128
  toolUsageContext: options.toolUsageContext,
96
129
  };
130
+ // Apply factory enhancement using centralized utilities
131
+ const textOptions = enhanceTextGenerationOptions(baseOptions, factoryResult);
132
+ // Detect and execute domain-specific tools
133
+ const { toolResults, enhancedPrompt } = await this.detectAndExecuteTools(textOptions.prompt || options.input.text, factoryResult.domainType);
134
+ // Update prompt with tool results if available
135
+ if (enhancedPrompt !== textOptions.prompt) {
136
+ textOptions.prompt = enhancedPrompt;
137
+ logger.debug("Enhanced prompt with tool results", {
138
+ originalLength: options.input.text.length,
139
+ enhancedLength: enhancedPrompt.length,
140
+ toolResults: toolResults.length,
141
+ });
142
+ }
97
143
  // Use redesigned generation logic
98
144
  const textResult = await this.generateTextInternal(textOptions);
99
145
  // Convert back to GenerateResult
@@ -113,22 +159,19 @@ export class NeuroLink {
113
159
  toolExecutions: textResult.toolExecutions?.map((te) => {
114
160
  const teRecord = te;
115
161
  return {
116
- name: te.toolName || teRecord.name || "",
117
- input: teRecord.input ||
118
- teRecord.args ||
119
- {},
120
- output: teRecord.output ||
121
- teRecord.result ||
122
- (te.success ? "success" : "failed"),
123
- duration: te.executionTime || teRecord.duration || 0,
162
+ name: teRecord.name || "", // ✅ BaseProvider now provides 'name'
163
+ input: teRecord.input || {},
164
+ output: teRecord.output || "success",
165
+ duration: teRecord.duration || 0,
124
166
  };
125
- }),
167
+ }) || [],
126
168
  enhancedWithTools: textResult.enhancedWithTools,
127
169
  availableTools: textResult.availableTools?.map((tool) => {
128
170
  const toolRecord = tool;
129
171
  return {
130
172
  name: tool.name || "",
131
173
  description: tool.description || "",
174
+ server: tool.server || "", // ✅ FIX: Include server property
132
175
  parameters: toolRecord.parameters ||
133
176
  toolRecord.schema ||
134
177
  {},
@@ -147,6 +190,11 @@ export class NeuroLink {
147
190
  .evaluationModel ?? "unknown",
148
191
  evaluationTime: textResult.evaluation
149
192
  .evaluationTime ?? Date.now(),
193
+ // Include evaluationDomain from original options
194
+ evaluationDomain: textResult.evaluation
195
+ .evaluationDomain ??
196
+ textOptions.evaluationDomain ??
197
+ factoryResult.domainType,
150
198
  }
151
199
  : undefined,
152
200
  };
@@ -214,7 +262,7 @@ export class NeuroLink {
214
262
  */
215
263
  async tryMCPGeneration(options) {
216
264
  const functionTag = "NeuroLink.tryMCPGeneration";
217
- const startTime = Date.now(); // 🔧 FIX: Add proper timing
265
+ const startTime = Date.now();
218
266
  try {
219
267
  // Initialize MCP if needed
220
268
  await this.initializeMCP();
@@ -229,13 +277,28 @@ export class NeuroLink {
229
277
  // Get available tools
230
278
  let availableTools = [];
231
279
  try {
232
- const allTools = await toolRegistry.listTools();
233
- availableTools = allTools.map((tool) => ({
280
+ // 1. Get MCP server tools (existing functionality)
281
+ const mcpTools = await toolRegistry.listTools();
282
+ const mappedMcpTools = mcpTools.map((tool) => ({
234
283
  name: tool.name || "Unknown",
235
284
  description: tool.description || "No description available",
236
- server: tool.server || "Unknown",
285
+ server: tool.serverId || "Unknown", // Fix: use serverId instead of server
237
286
  category: tool.category,
238
287
  }));
288
+ // 2. ✅ NEW: Get custom tools from this NeuroLink instance
289
+ const customTools = Array.from(this.customTools.entries()).map(([name, tool]) => ({
290
+ name,
291
+ description: tool.description || "Custom tool",
292
+ server: "custom",
293
+ category: "user-defined",
294
+ }));
295
+ // 3. ✅ NEW: Combine all tools for AI generation
296
+ availableTools = [...mappedMcpTools, ...customTools];
297
+ logger.debug(`[${functionTag}] Available tools for AI generation:`, {
298
+ mcpTools: mappedMcpTools.length,
299
+ customTools: customTools.length,
300
+ total: availableTools.length,
301
+ });
239
302
  }
240
303
  catch (error) {
241
304
  mcpLogger.warn(`[${functionTag}] Failed to get tools`, { error });
@@ -249,7 +312,7 @@ export class NeuroLink {
249
312
  ...options,
250
313
  systemPrompt: enhancedSystemPrompt,
251
314
  });
252
- const responseTime = Date.now() - startTime; // 🔧 FIX: Proper timing calculation
315
+ const responseTime = Date.now() - startTime;
253
316
  // Check if result is meaningful
254
317
  if (!result || !result.content || result.content.trim().length === 0) {
255
318
  return null; // Let caller fall back to direct generation
@@ -261,6 +324,15 @@ export class NeuroLink {
261
324
  usage: result.usage,
262
325
  responseTime,
263
326
  toolsUsed: result.toolsUsed || [],
327
+ toolExecutions: result.toolExecutions?.map((te) => {
328
+ const teRecord = te;
329
+ return {
330
+ toolName: teRecord.name || "",
331
+ executionTime: teRecord.duration || 0,
332
+ success: true, // Assume success if tool executed (AI providers handle failures differently)
333
+ serverId: teRecord.serverId || undefined,
334
+ };
335
+ }) || [], // ✅ NEW: Add missing toolExecutions with proper format
264
336
  enhancedWithTools: true,
265
337
  availableTools: availableTools.length > 0 ? availableTools : undefined,
266
338
  // Include analytics and evaluation from BaseProvider
@@ -360,6 +432,50 @@ export class NeuroLink {
360
432
  const toolPrompt = `\n\nYou have access to these additional tools if needed:\n${toolDescriptions}\n\nIMPORTANT: You are a general-purpose AI assistant. Answer all requests directly and creatively. These tools are optional helpers - use them only when they would genuinely improve your response. For creative tasks like storytelling, writing, or general conversation, respond naturally without requiring tools.`;
361
433
  return (originalSystemPrompt || "") + toolPrompt;
362
434
  }
435
+ /**
436
+ * Execute tools if available through centralized registry
437
+ * Simplified approach without domain detection - relies on tool registry
438
+ */
439
+ async detectAndExecuteTools(prompt, domainType) {
440
+ const functionTag = "NeuroLink.detectAndExecuteTools";
441
+ try {
442
+ // Simplified: Just return original prompt without complex detection
443
+ // Tools will be available through normal MCP flow when AI decides to use them
444
+ logger.debug(`[${functionTag}] Skipping automatic tool execution - relying on centralized registry`);
445
+ return { toolResults: [], enhancedPrompt: prompt };
446
+ }
447
+ catch (error) {
448
+ logger.error(`[${functionTag}] Tool detection/execution failed`, {
449
+ error: error instanceof Error ? error.message : String(error),
450
+ });
451
+ return { toolResults: [], enhancedPrompt: prompt };
452
+ }
453
+ }
454
+ /**
455
+ * Enhance prompt with tool results (domain-agnostic)
456
+ */
457
+ enhancePromptWithToolResults(prompt, toolResults) {
458
+ if (toolResults.length === 0) {
459
+ return prompt;
460
+ }
461
+ let enhancedPrompt = prompt;
462
+ for (const result of toolResults) {
463
+ if (result && typeof result === "object") {
464
+ enhancedPrompt += `\n\nTool Results:\n`;
465
+ // Handle any structured result generically
466
+ try {
467
+ const resultStr = typeof result === "string"
468
+ ? result
469
+ : JSON.stringify(result, null, 2);
470
+ enhancedPrompt += resultStr + "\n";
471
+ }
472
+ catch {
473
+ enhancedPrompt += "Tool execution completed\n";
474
+ }
475
+ }
476
+ }
477
+ return enhancedPrompt;
478
+ }
363
479
  /**
364
480
  * BACKWARD COMPATIBILITY: Legacy streamText method
365
481
  * Internally calls stream() and converts result format
@@ -393,6 +509,27 @@ export class NeuroLink {
393
509
  options.input.text.trim() === "") {
394
510
  throw new Error("Stream options must include input.text as a non-empty string");
395
511
  }
512
+ // Process factory configuration for streaming
513
+ const factoryResult = processFactoryOptions(options);
514
+ const streamingResult = processStreamingFactoryOptions(options);
515
+ // Validate factory configuration if present
516
+ if (factoryResult.hasFactoryConfig && options.factoryConfig) {
517
+ const validation = validateFactoryConfig(options.factoryConfig);
518
+ if (!validation.isValid) {
519
+ mcpLogger.warn("Invalid factory configuration detected in stream", {
520
+ errors: validation.errors,
521
+ });
522
+ // Continue with warning rather than throwing - graceful degradation
523
+ }
524
+ }
525
+ // Log factory processing results
526
+ if (factoryResult.hasFactoryConfig) {
527
+ mcpLogger.debug(`[${functionTag}] Factory configuration detected`, {
528
+ domainType: factoryResult.domainType,
529
+ enhancementType: factoryResult.enhancementType,
530
+ hasStreamingConfig: streamingResult.hasStreamingConfig,
531
+ });
532
+ }
396
533
  // Initialize MCP if needed
397
534
  await this.initializeMCP();
398
535
  // Context creation removed - was never used
@@ -400,6 +537,27 @@ export class NeuroLink {
400
537
  const providerName = options.provider === "auto" || !options.provider
401
538
  ? await getBestProvider()
402
539
  : options.provider;
540
+ // Prepare enhanced options for both success and fallback paths
541
+ let enhancedOptions = options;
542
+ if (factoryResult.hasFactoryConfig) {
543
+ enhancedOptions = {
544
+ ...options,
545
+ // Merge contexts instead of overriding to preserve provider-required context
546
+ context: {
547
+ ...(options.context || {}),
548
+ ...(factoryResult.processedContext || {}),
549
+ },
550
+ // Ensure evaluation is enabled when using factory patterns
551
+ enableEvaluation: options.enableEvaluation ?? true,
552
+ // Use domain type for evaluation if available
553
+ evaluationDomain: factoryResult.domainType || options.evaluationDomain,
554
+ };
555
+ mcpLogger.debug(`[${functionTag}] Enhanced stream options with factory config`, {
556
+ domainType: factoryResult.domainType,
557
+ enhancementType: factoryResult.enhancementType,
558
+ hasProcessedContext: !!factoryResult.processedContext,
559
+ });
560
+ }
403
561
  try {
404
562
  mcpLogger.debug(`[${functionTag}] Starting MCP-enabled streaming`, {
405
563
  provider: providerName,
@@ -407,8 +565,10 @@ export class NeuroLink {
407
565
  });
408
566
  // Create provider using the same factory pattern as generate
409
567
  const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true, this);
410
- // Call the provider's stream method directly
411
- const streamResult = await provider.stream(options);
568
+ // Create clean options for provider (remove factoryConfig)
569
+ const cleanOptions = createCleanStreamOptions(enhancedOptions);
570
+ // Call the provider's stream method with clean options
571
+ const streamResult = await provider.stream(cleanOptions);
412
572
  // Extract the stream from the result
413
573
  const stream = streamResult.stream;
414
574
  const responseTime = Date.now() - startTime;
@@ -425,8 +585,17 @@ export class NeuroLink {
425
585
  finishReason: streamResult.finishReason,
426
586
  toolCalls: streamResult.toolCalls,
427
587
  toolResults: streamResult.toolResults,
428
- analytics: streamResult.analytics, // 🔧 FIX: Pass through analytics data
429
- evaluation: streamResult.evaluation, // 🔧 FIX: Pass through evaluation data
588
+ analytics: streamResult.analytics,
589
+ evaluation: streamResult.evaluation
590
+ ? {
591
+ ...streamResult.evaluation,
592
+ // Include evaluationDomain from factory configuration
593
+ evaluationDomain: streamResult.evaluation
594
+ ?.evaluationDomain ??
595
+ enhancedOptions.evaluationDomain ??
596
+ factoryResult.domainType,
597
+ }
598
+ : undefined,
430
599
  metadata: {
431
600
  streamId: `neurolink-${Date.now()}`,
432
601
  startTime,
@@ -442,7 +611,9 @@ export class NeuroLink {
442
611
  // Use factory to create provider without MCP
443
612
  const provider = await AIProviderFactory.createBestProvider(providerName, options.model, false, // Disable MCP for fallback
444
613
  this);
445
- const streamResult = await provider.stream(options);
614
+ // Create clean options for fallback provider (remove factoryConfig)
615
+ const cleanOptions = createCleanStreamOptions(enhancedOptions);
616
+ const streamResult = await provider.stream(cleanOptions);
446
617
  const responseTime = Date.now() - startTime;
447
618
  return {
448
619
  stream: streamResult.stream,
@@ -452,8 +623,17 @@ export class NeuroLink {
452
623
  finishReason: streamResult.finishReason,
453
624
  toolCalls: streamResult.toolCalls,
454
625
  toolResults: streamResult.toolResults,
455
- analytics: streamResult.analytics, // 🔧 FIX: Pass through analytics data in fallback
456
- evaluation: streamResult.evaluation, // 🔧 FIX: Pass through evaluation data in fallback
626
+ analytics: streamResult.analytics,
627
+ evaluation: streamResult.evaluation
628
+ ? {
629
+ ...streamResult.evaluation,
630
+ // Include evaluationDomain in fallback stream
631
+ evaluationDomain: streamResult.evaluation
632
+ ?.evaluationDomain ??
633
+ enhancedOptions.evaluationDomain ??
634
+ factoryResult.domainType,
635
+ }
636
+ : undefined,
457
637
  metadata: {
458
638
  streamId: `neurolink-${Date.now()}`,
459
639
  startTime,
@@ -493,12 +673,24 @@ export class NeuroLink {
493
673
  }
494
674
  }
495
675
  /**
496
- * Register multiple tools at once
497
- * @param tools - Object mapping tool names to configurations
676
+ * Register multiple tools at once - Supports both object and array formats
677
+ * @param tools - Object mapping tool names to configurations OR Array of tools with names
678
+ *
679
+ * Object format (existing): { toolName: SimpleTool, ... }
680
+ * Array format (Lighthouse compatible): [{ name: string, tool: SimpleTool }, ...]
498
681
  */
499
682
  registerTools(tools) {
500
- for (const [name, tool] of Object.entries(tools)) {
501
- this.registerTool(name, tool);
683
+ if (Array.isArray(tools)) {
684
+ // Handle array format (Lighthouse compatible)
685
+ for (const { name, tool } of tools) {
686
+ this.registerTool(name, tool);
687
+ }
688
+ }
689
+ else {
690
+ // Handle object format (existing compatibility)
691
+ for (const [name, tool] of Object.entries(tools)) {
692
+ this.registerTool(name, tool);
693
+ }
502
694
  }
503
695
  }
504
696
  /**
@@ -556,8 +748,8 @@ export class NeuroLink {
556
748
  return new Map(this.inMemoryServers);
557
749
  }
558
750
  /**
559
- * Execute a specific tool by name (from git diff)
560
- * Supports both custom tools and MCP server tools
751
+ * Execute a specific tool by name with robust error handling
752
+ * Supports both custom tools and MCP server tools with timeout, retry, and circuit breaker patterns
561
753
  * @param toolName - Name of the tool to execute
562
754
  * @param params - Parameters to pass to the tool
563
755
  * @param options - Execution options
@@ -565,98 +757,206 @@ export class NeuroLink {
565
757
  */
566
758
  async executeTool(toolName, params = {}, options) {
567
759
  const functionTag = "NeuroLink.executeTool";
760
+ const executionStartTime = Date.now();
761
+ // Set default options
762
+ const finalOptions = {
763
+ timeout: options?.timeout || 30000, // 30 second default timeout
764
+ maxRetries: options?.maxRetries || 2, // Default 2 retries for retriable errors
765
+ retryDelayMs: options?.retryDelayMs || 1000, // 1 second delay between retries
766
+ };
767
+ // Track memory usage for tool execution
768
+ const { MemoryManager } = await import("./utils/performance.js");
769
+ const startMemory = MemoryManager.getMemoryUsageMB();
770
+ // Get or create circuit breaker for this tool
771
+ if (!this.toolCircuitBreakers.has(toolName)) {
772
+ this.toolCircuitBreakers.set(toolName, new CircuitBreaker(5, 60000)); // 5 failures, 1 minute timeout
773
+ }
774
+ const circuitBreaker = this.toolCircuitBreakers.get(toolName);
775
+ // Initialize metrics for this tool if not exists
776
+ if (!this.toolExecutionMetrics.has(toolName)) {
777
+ this.toolExecutionMetrics.set(toolName, {
778
+ totalExecutions: 0,
779
+ successfulExecutions: 0,
780
+ failedExecutions: 0,
781
+ averageExecutionTime: 0,
782
+ lastExecutionTime: 0,
783
+ });
784
+ }
785
+ const metrics = this.toolExecutionMetrics.get(toolName);
786
+ metrics.totalExecutions++;
568
787
  try {
569
788
  mcpLogger.debug(`[${functionTag}] Executing tool: ${toolName}`, {
570
789
  toolName,
571
790
  params,
572
- options,
791
+ options: finalOptions,
792
+ circuitBreakerState: circuitBreaker.getState(),
573
793
  });
574
- // First check custom tools
575
- if (this.customTools.has(toolName)) {
576
- const tool = this.customTools.get(toolName);
577
- const context = {
578
- sessionId: `direct-execution-${Date.now()}`,
579
- logger,
580
- };
581
- const startTime = Date.now();
582
- const result = await tool.execute(params, context);
583
- const executionTime = Date.now() - startTime;
584
- mcpLogger.debug(`[${functionTag}] Custom tool executed successfully`, {
794
+ // Execute with circuit breaker, timeout, and retry logic
795
+ const result = await circuitBreaker.execute(async () => {
796
+ return await withRetry(async () => {
797
+ return await withTimeout(this.executeToolInternal(toolName, params, finalOptions), finalOptions.timeout, ErrorFactory.toolTimeout(toolName, finalOptions.timeout));
798
+ }, {
799
+ maxAttempts: finalOptions.maxRetries + 1, // +1 for initial attempt
800
+ delayMs: finalOptions.retryDelayMs,
801
+ isRetriable: isRetriableError,
802
+ onRetry: (attempt, error) => {
803
+ mcpLogger.warn(`[${functionTag}] Retrying tool execution (attempt ${attempt})`, {
804
+ toolName,
805
+ error: error.message,
806
+ attempt,
807
+ });
808
+ },
809
+ });
810
+ });
811
+ // Update success metrics
812
+ const executionTime = Date.now() - executionStartTime;
813
+ metrics.successfulExecutions++;
814
+ metrics.lastExecutionTime = executionTime;
815
+ metrics.averageExecutionTime =
816
+ (metrics.averageExecutionTime * (metrics.successfulExecutions - 1) +
817
+ executionTime) /
818
+ metrics.successfulExecutions;
819
+ // Track memory usage
820
+ const endMemory = MemoryManager.getMemoryUsageMB();
821
+ const memoryDelta = endMemory.heapUsed - startMemory.heapUsed;
822
+ if (memoryDelta > 20) {
823
+ mcpLogger.warn(`Tool '${toolName}' used excessive memory: ${memoryDelta}MB`, {
585
824
  toolName,
825
+ memoryDelta,
586
826
  executionTime,
587
827
  });
588
- return result;
589
828
  }
590
- // Then check in-memory servers
591
- for (const [serverId, serverConfig] of this.inMemoryServers.entries()) {
592
- const server = serverConfig.server;
593
- // Check if this server has the requested tool
594
- if (server && server.tools) {
595
- const tool = server.tools instanceof Map
596
- ? server.tools.get(toolName)
597
- : server.tools[toolName];
598
- if (tool && typeof tool.execute === "function") {
599
- mcpLogger.debug(`[${functionTag}] Found tool in in-memory server: ${serverId}`);
600
- const startTime = Date.now();
601
- try {
602
- const result = await tool.execute(params);
603
- const executionTime = Date.now() - startTime;
604
- mcpLogger.debug(`[${functionTag}] Tool executed successfully`, {
605
- toolName,
606
- serverId,
607
- executionTime,
608
- });
609
- // Handle MCP-style results
610
- if (result && typeof result === "object" && "success" in result) {
611
- if (result.success) {
612
- return result.data;
613
- }
614
- else {
615
- throw new Error(result.error || "Tool execution failed");
616
- }
617
- }
618
- return result;
619
- }
620
- catch (toolError) {
621
- mcpLogger.error(`[${functionTag}] Tool execution failed`, {
622
- toolName,
623
- serverId,
624
- error: toolError instanceof Error
625
- ? toolError.message
626
- : String(toolError),
627
- });
628
- throw toolError;
629
- }
630
- }
829
+ mcpLogger.debug(`[${functionTag}] Tool executed successfully`, {
830
+ toolName,
831
+ executionTime,
832
+ memoryDelta,
833
+ circuitBreakerState: circuitBreaker.getState(),
834
+ });
835
+ return result;
836
+ }
837
+ catch (error) {
838
+ // Update failure metrics
839
+ metrics.failedExecutions++;
840
+ const executionTime = Date.now() - executionStartTime;
841
+ // Create structured error
842
+ let structuredError;
843
+ if (error instanceof NeuroLinkError) {
844
+ structuredError = error;
845
+ }
846
+ else if (error instanceof Error) {
847
+ // Categorize the error based on the message
848
+ if (error.message.includes("timeout")) {
849
+ structuredError = ErrorFactory.toolTimeout(toolName, finalOptions.timeout);
850
+ }
851
+ else if (error.message.includes("not found")) {
852
+ const availableTools = Array.from(this.customTools.keys());
853
+ structuredError = ErrorFactory.toolNotFound(toolName, availableTools);
854
+ }
855
+ else if (error.message.includes("validation") ||
856
+ error.message.includes("parameter")) {
857
+ structuredError = ErrorFactory.invalidParameters(toolName, error, params);
858
+ }
859
+ else if (error.message.includes("network") ||
860
+ error.message.includes("connection")) {
861
+ structuredError = ErrorFactory.networkError(toolName, error);
862
+ }
863
+ else {
864
+ structuredError = ErrorFactory.toolExecutionFailed(toolName, error);
865
+ }
866
+ }
867
+ else {
868
+ structuredError = ErrorFactory.toolExecutionFailed(toolName, new Error(String(error)));
869
+ }
870
+ // Add execution context to structured error
871
+ structuredError = new NeuroLinkError({
872
+ ...structuredError,
873
+ context: {
874
+ ...structuredError.context,
875
+ executionTime,
876
+ params,
877
+ options: finalOptions,
878
+ circuitBreakerState: circuitBreaker.getState(),
879
+ circuitBreakerFailures: circuitBreaker.getFailureCount(),
880
+ metrics: { ...metrics },
881
+ },
882
+ });
883
+ // Log structured error
884
+ logStructuredError(structuredError);
885
+ throw structuredError;
886
+ }
887
+ }
888
+ /**
889
+ * Internal tool execution method (extracted for better error handling)
890
+ */
891
+ async executeToolInternal(toolName, params, options) {
892
+ const functionTag = "NeuroLink.executeToolInternal";
893
+ // First check custom tools
894
+ if (this.customTools.has(toolName)) {
895
+ const tool = this.customTools.get(toolName);
896
+ // Validate parameters if schema is available
897
+ if (tool.parameters) {
898
+ try {
899
+ tool.parameters.parse(params);
900
+ }
901
+ catch (validationError) {
902
+ throw ErrorFactory.invalidParameters(toolName, validationError, params);
631
903
  }
632
904
  }
633
- // If not found in custom tools or in-memory servers, try unified registry
905
+ const context = {
906
+ sessionId: `direct-execution-${Date.now()}`,
907
+ logger,
908
+ };
634
909
  try {
635
- // Built-in tools initialization simplified
636
- mcpLogger.debug(`[${functionTag}] MCP initialization simplified`);
637
- // Create minimal execution context for external tools
638
- const context = {
639
- sessionId: `neurolink-tool-${Date.now()}`,
640
- userId: "neurolink-user",
641
- };
642
- const result = (await toolRegistry.executeTool(toolName, params, context));
910
+ const result = await tool.execute(params, context);
643
911
  return result;
644
912
  }
645
913
  catch (error) {
646
- mcpLogger.warn(`[${functionTag}] External tool execution failed`, {
647
- toolName,
648
- error: error instanceof Error ? error.message : String(error),
649
- });
650
- throw error;
914
+ throw ErrorFactory.toolExecutionFailed(toolName, error instanceof Error ? error : new Error(String(error)));
651
915
  }
652
916
  }
917
+ // Then check in-memory servers
918
+ for (const [serverId, serverConfig] of this.inMemoryServers.entries()) {
919
+ const server = serverConfig.server;
920
+ if (server && server.tools) {
921
+ const tool = server.tools instanceof Map
922
+ ? server.tools.get(toolName)
923
+ : server.tools[toolName];
924
+ if (tool && typeof tool.execute === "function") {
925
+ try {
926
+ const result = await tool.execute(params);
927
+ // Handle MCP-style results
928
+ if (result && typeof result === "object" && "success" in result) {
929
+ if (result.success) {
930
+ return result.data;
931
+ }
932
+ else {
933
+ throw ErrorFactory.toolExecutionFailed(toolName, new Error(result.error || "Tool execution failed"), serverId);
934
+ }
935
+ }
936
+ return result;
937
+ }
938
+ catch (error) {
939
+ throw ErrorFactory.toolExecutionFailed(toolName, error instanceof Error ? error : new Error(String(error)), serverId);
940
+ }
941
+ }
942
+ }
943
+ }
944
+ // If not found in custom tools or in-memory servers, try unified registry
945
+ try {
946
+ const context = {
947
+ sessionId: `neurolink-tool-${Date.now()}`,
948
+ userId: "neurolink-user",
949
+ };
950
+ const result = (await toolRegistry.executeTool(toolName, params, context));
951
+ return result;
952
+ }
653
953
  catch (error) {
654
- const errorMessage = error instanceof Error ? error.message : String(error);
655
- mcpLogger.error(`[${functionTag}] Tool execution failed: ${errorMessage}`, {
656
- toolName,
657
- error: errorMessage,
658
- });
659
- throw new Error(`Failed to execute tool '${toolName}': ${errorMessage}`);
954
+ // Check if tool was not found
955
+ if (error instanceof Error && error.message.includes("not found")) {
956
+ const availableTools = await this.getAllAvailableTools();
957
+ throw ErrorFactory.toolNotFound(toolName, availableTools.map((t) => t.name));
958
+ }
959
+ throw ErrorFactory.toolExecutionFailed(toolName, error instanceof Error ? error : new Error(String(error)));
660
960
  }
661
961
  }
662
962
  /**
@@ -664,10 +964,71 @@ export class NeuroLink {
664
964
  * @returns Array of available tools with metadata
665
965
  */
666
966
  async getAllAvailableTools() {
667
- // MCP registry already includes direct tools, so just return MCP tools
668
- // This prevents duplication since direct tools are auto-registered in MCP
669
- const mcpTools = await toolRegistry.listTools();
670
- return mcpTools;
967
+ // Track memory usage for tool listing operations
968
+ const { MemoryManager } = await import("./utils/performance.js");
969
+ const startMemory = MemoryManager.getMemoryUsageMB();
970
+ try {
971
+ // 1. Get MCP server tools (built-in direct tools)
972
+ const mcpToolsRaw = await toolRegistry.listTools();
973
+ const mcpTools = mcpToolsRaw.map((tool) => ({
974
+ ...tool,
975
+ toolName: tool.name, // Add toolName property for compatibility with tests
976
+ serverId: tool.serverId === "direct" ? "neurolink-direct" : tool.serverId, // Update serverId for test compatibility
977
+ }));
978
+ // 2. Get custom tools from this NeuroLink instance
979
+ const customTools = Array.from(this.customTools.entries()).map(([name, tool]) => ({
980
+ name,
981
+ toolName: name, // Add toolName property for compatibility with tests
982
+ description: tool.description || "Custom tool",
983
+ serverId: `custom-tool-${name}`, // Match the serverId pattern used in registerTool
984
+ category: "user-defined",
985
+ inputSchema: {},
986
+ }));
987
+ // 3. Get tools from in-memory MCP servers
988
+ const inMemoryTools = [];
989
+ for (const [serverId, serverConfig] of this.inMemoryServers.entries()) {
990
+ const server = serverConfig.server;
991
+ if (server && server.tools) {
992
+ const tools = server.tools instanceof Map
993
+ ? Object.fromEntries(server.tools)
994
+ : server.tools;
995
+ for (const [toolName, toolDef] of Object.entries(tools)) {
996
+ const toolRecord = toolDef;
997
+ inMemoryTools.push({
998
+ name: toolName,
999
+ toolName, // Add toolName property for compatibility with tests
1000
+ description: toolRecord.description || "In-memory MCP tool",
1001
+ serverId,
1002
+ category: serverConfig.category || "mcp-server",
1003
+ inputSchema: {},
1004
+ });
1005
+ }
1006
+ }
1007
+ }
1008
+ // 4. Combine all tools
1009
+ const allTools = [...mcpTools, ...customTools, ...inMemoryTools];
1010
+ mcpLogger.debug("Tool discovery results", {
1011
+ mcpTools: mcpTools.length,
1012
+ customTools: customTools.length,
1013
+ inMemoryTools: inMemoryTools.length,
1014
+ total: allTools.length,
1015
+ });
1016
+ // Check memory usage after tool enumeration
1017
+ const endMemory = MemoryManager.getMemoryUsageMB();
1018
+ const memoryDelta = endMemory.heapUsed - startMemory.heapUsed;
1019
+ if (memoryDelta > 10) {
1020
+ mcpLogger.debug(`🔍 Tool listing used ${memoryDelta}MB memory (large tool registry detected)`);
1021
+ // Suggest periodic cleanup for large tool registries
1022
+ if (allTools.length > 100) {
1023
+ mcpLogger.debug("💡 Suggestion: Consider using tool categories or lazy loading for large tool sets");
1024
+ }
1025
+ }
1026
+ return allTools;
1027
+ }
1028
+ catch (error) {
1029
+ mcpLogger.error("Failed to list available tools", { error });
1030
+ return [];
1031
+ }
671
1032
  }
672
1033
  // ============================================================================
673
1034
  // PROVIDER DIAGNOSTICS - SDK-First Architecture
@@ -677,10 +1038,10 @@ export class NeuroLink {
677
1038
  * Primary method for provider health checking and diagnostics
678
1039
  */
679
1040
  async getProviderStatus(options) {
680
- // 🔧 PERFORMANCE: Track memory and timing for provider status checks
1041
+ // Track memory and timing for provider status checks
681
1042
  const { MemoryManager } = await import("./utils/performance.js");
682
1043
  const startMemory = MemoryManager.getMemoryUsageMB();
683
- // CRITICAL FIX: Ensure providers are registered before testing
1044
+ // Ensure providers are registered before testing
684
1045
  if (!options?.quiet) {
685
1046
  mcpLogger.debug("🔍 DEBUG: Initializing MCP for provider status...");
686
1047
  }
@@ -703,7 +1064,7 @@ export class NeuroLink {
703
1064
  "mistral",
704
1065
  "litellm",
705
1066
  ];
706
- // 🚀 PERFORMANCE FIX: Test providers with controlled concurrency
1067
+ // Test providers with controlled concurrency
707
1068
  // This reduces total time from 16s (sequential) to ~3s (parallel) while preventing resource exhaustion
708
1069
  const limit = pLimit(SYSTEM_LIMITS.DEFAULT_CONCURRENCY_LIMIT);
709
1070
  const providerTests = providers.map((providerName) => limit(async () => {
@@ -797,7 +1158,7 @@ export class NeuroLink {
797
1158
  }));
798
1159
  // Wait for all provider tests to complete in parallel
799
1160
  const results = await Promise.all(providerTests);
800
- // 🔧 PERFORMANCE: Track memory usage and suggest cleanup if needed
1161
+ // Track memory usage and suggest cleanup if needed
801
1162
  const endMemory = MemoryManager.getMemoryUsageMB();
802
1163
  const memoryDelta = endMemory.heapUsed - startMemory.heapUsed;
803
1164
  if (!options?.quiet && memoryDelta > 20) {
@@ -835,15 +1196,6 @@ export class NeuroLink {
835
1196
  disableTools: true,
836
1197
  });
837
1198
  }
838
- /**
839
- * Check if a provider has required environment variables configured
840
- * @param providerName - Name of the provider to check
841
- * @returns True if provider has required environment variables
842
- */
843
- async hasProviderEnvVars(providerName) {
844
- const { hasProviderEnvVars } = await import("./utils/providerUtils.js");
845
- return hasProviderEnvVars(providerName);
846
- }
847
1199
  /**
848
1200
  * Get the best available AI provider based on configuration and availability
849
1201
  * @param requestedProvider - Optional preferred provider name
@@ -923,6 +1275,216 @@ export class NeuroLink {
923
1275
  // Simplified MCP server testing - unified registry removed
924
1276
  return false; // No auto-discovery servers available
925
1277
  }
1278
+ // ==================== PROVIDER HEALTH CHECKING ====================
1279
+ /**
1280
+ * Check if a provider has the required environment variables configured
1281
+ * @param providerName - Name of the provider to check
1282
+ * @returns Promise resolving to true if provider has required env vars
1283
+ */
1284
+ async hasProviderEnvVars(providerName) {
1285
+ const { ProviderHealthChecker } = await import("./utils/providerHealth.js");
1286
+ try {
1287
+ const health = await ProviderHealthChecker.checkProviderHealth(providerName, { includeConnectivityTest: false, cacheResults: false });
1288
+ return health.isConfigured && health.hasApiKey;
1289
+ }
1290
+ catch (error) {
1291
+ logger.warn(`Provider env var check failed for ${providerName}`, {
1292
+ error: error instanceof Error ? error.message : String(error),
1293
+ });
1294
+ return false;
1295
+ }
1296
+ }
1297
+ /**
1298
+ * Perform comprehensive health check on a specific provider
1299
+ * @param providerName - Name of the provider to check
1300
+ * @param options - Health check options
1301
+ * @returns Promise resolving to detailed health status
1302
+ */
1303
+ async checkProviderHealth(providerName, options = {}) {
1304
+ const { ProviderHealthChecker } = await import("./utils/providerHealth.js");
1305
+ const health = await ProviderHealthChecker.checkProviderHealth(providerName, options);
1306
+ return {
1307
+ provider: health.provider,
1308
+ isHealthy: health.isHealthy,
1309
+ isConfigured: health.isConfigured,
1310
+ hasApiKey: health.hasApiKey,
1311
+ lastChecked: health.lastChecked,
1312
+ error: health.error,
1313
+ warning: health.warning,
1314
+ responseTime: health.responseTime,
1315
+ configurationIssues: health.configurationIssues,
1316
+ recommendations: health.recommendations,
1317
+ };
1318
+ }
1319
+ /**
1320
+ * Check health of all supported providers
1321
+ * @param options - Health check options
1322
+ * @returns Promise resolving to array of health statuses for all providers
1323
+ */
1324
+ async checkAllProvidersHealth(options = {}) {
1325
+ const { ProviderHealthChecker } = await import("./utils/providerHealth.js");
1326
+ const healthStatuses = await ProviderHealthChecker.checkAllProvidersHealth(options);
1327
+ return healthStatuses.map((health) => ({
1328
+ provider: health.provider,
1329
+ isHealthy: health.isHealthy,
1330
+ isConfigured: health.isConfigured,
1331
+ hasApiKey: health.hasApiKey,
1332
+ lastChecked: health.lastChecked,
1333
+ error: health.error,
1334
+ warning: health.warning,
1335
+ responseTime: health.responseTime,
1336
+ configurationIssues: health.configurationIssues,
1337
+ recommendations: health.recommendations,
1338
+ }));
1339
+ }
1340
+ /**
1341
+ * Get a summary of provider health across all supported providers
1342
+ * @returns Promise resolving to health summary statistics
1343
+ */
1344
+ async getProviderHealthSummary() {
1345
+ const { ProviderHealthChecker } = await import("./utils/providerHealth.js");
1346
+ const healthStatuses = await ProviderHealthChecker.checkAllProvidersHealth({
1347
+ cacheResults: true,
1348
+ includeConnectivityTest: false,
1349
+ });
1350
+ const summary = ProviderHealthChecker.getHealthSummary(healthStatuses);
1351
+ // Add recommendations based on the overall health
1352
+ const recommendations = [];
1353
+ if (summary.healthy === 0) {
1354
+ recommendations.push("No providers are healthy. Check your environment configuration.");
1355
+ }
1356
+ else if (summary.healthy < 2) {
1357
+ recommendations.push("Consider configuring additional providers for better reliability.");
1358
+ }
1359
+ if (summary.hasIssues > 0) {
1360
+ recommendations.push("Some providers have configuration issues. Run checkAllProvidersHealth() for details.");
1361
+ }
1362
+ return {
1363
+ ...summary,
1364
+ recommendations,
1365
+ };
1366
+ }
1367
+ /**
1368
+ * Clear provider health cache (useful for re-testing after configuration changes)
1369
+ * @param providerName - Optional specific provider to clear cache for
1370
+ */
1371
+ async clearProviderHealthCache(providerName) {
1372
+ const { ProviderHealthChecker } = await import("./utils/providerHealth.js");
1373
+ ProviderHealthChecker.clearHealthCache(providerName);
1374
+ }
1375
+ // ==================== TOOL EXECUTION DIAGNOSTICS ====================
1376
+ /**
1377
+ * Get execution metrics for all tools
1378
+ * @returns Object with execution metrics for each tool
1379
+ */
1380
+ getToolExecutionMetrics() {
1381
+ const metrics = {};
1382
+ for (const [toolName, toolMetrics] of this.toolExecutionMetrics.entries()) {
1383
+ metrics[toolName] = {
1384
+ ...toolMetrics,
1385
+ successRate: toolMetrics.totalExecutions > 0
1386
+ ? toolMetrics.successfulExecutions / toolMetrics.totalExecutions
1387
+ : 0,
1388
+ };
1389
+ }
1390
+ return metrics;
1391
+ }
1392
+ /**
1393
+ * Get circuit breaker status for all tools
1394
+ * @returns Object with circuit breaker status for each tool
1395
+ */
1396
+ getToolCircuitBreakerStatus() {
1397
+ const status = {};
1398
+ for (const [toolName, circuitBreaker,] of this.toolCircuitBreakers.entries()) {
1399
+ status[toolName] = {
1400
+ state: circuitBreaker.getState(),
1401
+ failureCount: circuitBreaker.getFailureCount(),
1402
+ isHealthy: circuitBreaker.getState() === "closed",
1403
+ };
1404
+ }
1405
+ return status;
1406
+ }
1407
+ /**
1408
+ * Reset circuit breaker for a specific tool
1409
+ * @param toolName - Name of the tool to reset circuit breaker for
1410
+ */
1411
+ resetToolCircuitBreaker(toolName) {
1412
+ if (this.toolCircuitBreakers.has(toolName)) {
1413
+ // Create a new circuit breaker (effectively resets it)
1414
+ this.toolCircuitBreakers.set(toolName, new CircuitBreaker(5, 60000));
1415
+ mcpLogger.info(`Circuit breaker reset for tool: ${toolName}`);
1416
+ }
1417
+ }
1418
+ /**
1419
+ * Clear all tool execution metrics
1420
+ */
1421
+ clearToolExecutionMetrics() {
1422
+ this.toolExecutionMetrics.clear();
1423
+ mcpLogger.info("All tool execution metrics cleared");
1424
+ }
1425
+ /**
1426
+ * Get comprehensive tool health report
1427
+ * @returns Detailed health report for all tools
1428
+ */
1429
+ getToolHealthReport() {
1430
+ const tools = {};
1431
+ let healthyCount = 0;
1432
+ // Get all tool names from all sources
1433
+ const allToolNames = new Set([
1434
+ ...this.customTools.keys(),
1435
+ ...Array.from(this.inMemoryServers.values()).flatMap((server) => server.server?.tools ? Object.keys(server.server.tools) : []),
1436
+ ]);
1437
+ for (const toolName of allToolNames) {
1438
+ const metrics = this.toolExecutionMetrics.get(toolName);
1439
+ const circuitBreaker = this.toolCircuitBreakers.get(toolName);
1440
+ const successRate = metrics
1441
+ ? metrics.totalExecutions > 0
1442
+ ? metrics.successfulExecutions / metrics.totalExecutions
1443
+ : 0
1444
+ : 0;
1445
+ const isHealthy = (!circuitBreaker || circuitBreaker.getState() === "closed") &&
1446
+ successRate >= 0.8;
1447
+ if (isHealthy) {
1448
+ healthyCount++;
1449
+ }
1450
+ const issues = [];
1451
+ const recommendations = [];
1452
+ if (circuitBreaker && circuitBreaker.getState() === "open") {
1453
+ issues.push("Circuit breaker is open due to repeated failures");
1454
+ recommendations.push("Check tool implementation and fix underlying issues");
1455
+ }
1456
+ if (successRate < 0.8 && metrics && metrics.totalExecutions > 0) {
1457
+ issues.push(`Low success rate: ${(successRate * 100).toFixed(1)}%`);
1458
+ recommendations.push("Review error logs and improve tool reliability");
1459
+ }
1460
+ if (metrics && metrics.averageExecutionTime > 10000) {
1461
+ issues.push("High average execution time");
1462
+ recommendations.push("Optimize tool performance or increase timeout");
1463
+ }
1464
+ tools[toolName] = {
1465
+ name: toolName,
1466
+ isHealthy,
1467
+ metrics: {
1468
+ totalExecutions: metrics?.totalExecutions || 0,
1469
+ successRate,
1470
+ averageExecutionTime: metrics?.averageExecutionTime || 0,
1471
+ lastExecutionTime: metrics?.lastExecutionTime || 0,
1472
+ },
1473
+ circuitBreaker: {
1474
+ state: circuitBreaker?.getState() || "closed",
1475
+ failureCount: circuitBreaker?.getFailureCount() || 0,
1476
+ },
1477
+ issues,
1478
+ recommendations,
1479
+ };
1480
+ }
1481
+ return {
1482
+ totalTools: allToolNames.size,
1483
+ healthyTools: healthyCount,
1484
+ unhealthyTools: allToolNames.size - healthyCount,
1485
+ tools,
1486
+ };
1487
+ }
926
1488
  }
927
1489
  // Create default instance
928
1490
  export const neurolink = new NeuroLink();