@juspay/neurolink 7.6.0 → 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.
- package/CHANGELOG.md +14 -2
- package/README.md +79 -4
- package/dist/cli/commands/config.d.ts +275 -3
- package/dist/cli/commands/config.js +121 -0
- package/dist/cli/commands/mcp.js +77 -28
- package/dist/cli/factories/commandFactory.js +359 -6
- package/dist/core/analytics.js +7 -27
- package/dist/core/baseProvider.js +43 -4
- package/dist/core/constants.d.ts +46 -0
- package/dist/core/constants.js +47 -0
- package/dist/core/dynamicModels.d.ts +16 -4
- package/dist/core/dynamicModels.js +130 -26
- package/dist/core/evaluation.js +5 -1
- package/dist/core/evaluationProviders.d.ts +6 -2
- package/dist/core/evaluationProviders.js +41 -125
- package/dist/core/factory.d.ts +5 -0
- package/dist/core/factory.js +62 -50
- package/dist/core/modelConfiguration.d.ts +246 -0
- package/dist/core/modelConfiguration.js +775 -0
- package/dist/core/types.d.ts +22 -3
- package/dist/core/types.js +5 -1
- package/dist/factories/providerRegistry.js +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/core/analytics.js +7 -27
- package/dist/lib/core/baseProvider.js +43 -4
- package/dist/lib/core/constants.d.ts +46 -0
- package/dist/lib/core/constants.js +47 -0
- package/dist/lib/core/dynamicModels.d.ts +16 -4
- package/dist/lib/core/dynamicModels.js +130 -26
- package/dist/lib/core/evaluation.js +5 -1
- package/dist/lib/core/evaluationProviders.d.ts +6 -2
- package/dist/lib/core/evaluationProviders.js +41 -125
- package/dist/lib/core/factory.d.ts +5 -0
- package/dist/lib/core/factory.js +63 -50
- package/dist/lib/core/modelConfiguration.d.ts +246 -0
- package/dist/lib/core/modelConfiguration.js +775 -0
- package/dist/lib/core/types.d.ts +22 -3
- package/dist/lib/core/types.js +5 -1
- package/dist/lib/factories/providerRegistry.js +3 -3
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/mcp/factory.d.ts +5 -5
- package/dist/lib/mcp/factory.js +2 -2
- package/dist/lib/mcp/servers/utilities/utilityServer.d.ts +1 -1
- package/dist/lib/mcp/servers/utilities/utilityServer.js +1 -1
- package/dist/lib/mcp/toolRegistry.js +2 -2
- package/dist/lib/neurolink.d.ts +168 -12
- package/dist/lib/neurolink.js +685 -123
- package/dist/lib/providers/anthropic.js +52 -2
- package/dist/lib/providers/googleAiStudio.js +4 -0
- package/dist/lib/providers/googleVertex.d.ts +75 -9
- package/dist/lib/providers/googleVertex.js +365 -46
- package/dist/lib/providers/huggingFace.d.ts +52 -11
- package/dist/lib/providers/huggingFace.js +180 -42
- package/dist/lib/providers/litellm.d.ts +9 -9
- package/dist/lib/providers/litellm.js +103 -16
- package/dist/lib/providers/ollama.d.ts +52 -17
- package/dist/lib/providers/ollama.js +276 -68
- package/dist/lib/sdk/toolRegistration.d.ts +42 -0
- package/dist/lib/sdk/toolRegistration.js +269 -27
- package/dist/lib/telemetry/telemetryService.d.ts +6 -0
- package/dist/lib/telemetry/telemetryService.js +38 -3
- package/dist/lib/types/contextTypes.d.ts +75 -11
- package/dist/lib/types/contextTypes.js +227 -1
- package/dist/lib/types/domainTypes.d.ts +62 -0
- package/dist/lib/types/domainTypes.js +5 -0
- package/dist/lib/types/generateTypes.d.ts +52 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/mcpTypes.d.ts +1 -1
- package/dist/lib/types/mcpTypes.js +1 -1
- package/dist/lib/types/streamTypes.d.ts +14 -0
- package/dist/lib/types/universalProviderOptions.d.ts +1 -1
- package/dist/lib/utils/errorHandling.d.ts +142 -0
- package/dist/lib/utils/errorHandling.js +316 -0
- package/dist/lib/utils/factoryProcessing.d.ts +74 -0
- package/dist/lib/utils/factoryProcessing.js +588 -0
- package/dist/lib/utils/optionsConversion.d.ts +54 -0
- package/dist/lib/utils/optionsConversion.js +126 -0
- package/dist/lib/utils/optionsUtils.d.ts +246 -0
- package/dist/lib/utils/optionsUtils.js +960 -0
- package/dist/lib/utils/providerHealth.d.ts +107 -0
- package/dist/lib/utils/providerHealth.js +507 -0
- package/dist/lib/utils/providerUtils.d.ts +17 -0
- package/dist/lib/utils/providerUtils.js +271 -16
- package/dist/lib/utils/timeout.js +1 -1
- package/dist/lib/utils/tokenLimits.d.ts +33 -0
- package/dist/lib/utils/tokenLimits.js +118 -0
- package/dist/mcp/factory.d.ts +5 -5
- package/dist/mcp/factory.js +2 -2
- package/dist/mcp/servers/utilities/utilityServer.d.ts +1 -1
- package/dist/mcp/servers/utilities/utilityServer.js +1 -1
- package/dist/mcp/toolRegistry.js +2 -2
- package/dist/neurolink.d.ts +168 -12
- package/dist/neurolink.js +685 -123
- package/dist/providers/anthropic.js +52 -2
- package/dist/providers/googleAiStudio.js +4 -0
- package/dist/providers/googleVertex.d.ts +75 -9
- package/dist/providers/googleVertex.js +365 -46
- package/dist/providers/huggingFace.d.ts +52 -11
- package/dist/providers/huggingFace.js +181 -43
- package/dist/providers/litellm.d.ts +9 -9
- package/dist/providers/litellm.js +103 -16
- package/dist/providers/ollama.d.ts +52 -17
- package/dist/providers/ollama.js +276 -68
- package/dist/sdk/toolRegistration.d.ts +42 -0
- package/dist/sdk/toolRegistration.js +269 -27
- package/dist/telemetry/telemetryService.d.ts +6 -0
- package/dist/telemetry/telemetryService.js +38 -3
- package/dist/types/contextTypes.d.ts +75 -11
- package/dist/types/contextTypes.js +227 -2
- package/dist/types/domainTypes.d.ts +62 -0
- package/dist/types/domainTypes.js +5 -0
- package/dist/types/generateTypes.d.ts +52 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mcpTypes.d.ts +1 -1
- package/dist/types/mcpTypes.js +1 -1
- package/dist/types/streamTypes.d.ts +14 -0
- package/dist/types/universalProviderOptions.d.ts +1 -1
- package/dist/types/universalProviderOptions.js +0 -1
- package/dist/utils/errorHandling.d.ts +142 -0
- package/dist/utils/errorHandling.js +316 -0
- package/dist/utils/factoryProcessing.d.ts +74 -0
- package/dist/utils/factoryProcessing.js +588 -0
- package/dist/utils/optionsConversion.d.ts +54 -0
- package/dist/utils/optionsConversion.js +126 -0
- package/dist/utils/optionsUtils.d.ts +246 -0
- package/dist/utils/optionsUtils.js +960 -0
- package/dist/utils/providerHealth.d.ts +107 -0
- package/dist/utils/providerHealth.js +507 -0
- package/dist/utils/providerUtils.d.ts +17 -0
- package/dist/utils/providerUtils.js +271 -16
- package/dist/utils/timeout.js +1 -1
- package/dist/utils/tokenLimits.d.ts +33 -0
- package/dist/utils/tokenLimits.js +118 -0
- package/package.json +2 -2
package/dist/neurolink.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
82
|
-
const
|
|
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,
|
|
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:
|
|
117
|
-
input: teRecord.input ||
|
|
118
|
-
|
|
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();
|
|
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
|
-
|
|
233
|
-
|
|
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.
|
|
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;
|
|
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
|
-
//
|
|
411
|
-
const
|
|
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,
|
|
429
|
-
evaluation: streamResult.evaluation
|
|
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
|
-
|
|
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,
|
|
456
|
-
evaluation: streamResult.evaluation
|
|
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
|
-
|
|
501
|
-
|
|
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
|
|
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
|
-
//
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
|
|
905
|
+
const context = {
|
|
906
|
+
sessionId: `direct-execution-${Date.now()}`,
|
|
907
|
+
logger,
|
|
908
|
+
};
|
|
634
909
|
try {
|
|
635
|
-
|
|
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
|
-
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
throw
|
|
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
|
-
//
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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();
|