@juspay/neurolink 7.14.7 → 7.15.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 +12 -0
- package/README.md +58 -2
- package/dist/agent/directTools.d.ts +133 -0
- package/dist/agent/directTools.js +134 -0
- package/dist/core/baseProvider.js +34 -5
- package/dist/lib/agent/directTools.d.ts +133 -0
- package/dist/lib/agent/directTools.js +134 -0
- package/dist/lib/core/baseProvider.js +34 -5
- package/dist/lib/mcp/servers/agent/directToolsServer.js +2 -0
- package/dist/lib/mcp/toolDiscoveryService.js +24 -40
- package/dist/lib/neurolink.js +88 -24
- package/dist/lib/utils/transformationUtils.js +103 -10
- package/dist/mcp/servers/agent/directToolsServer.js +2 -0
- package/dist/mcp/toolDiscoveryService.js +24 -40
- package/dist/neurolink.js +88 -24
- package/dist/utils/transformationUtils.js +103 -10
- package/package.json +2 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
7
|
import { mcpLogger } from "../utils/logger.js";
|
|
8
8
|
import { globalCircuitBreakerManager } from "./mcpCircuitBreaker.js";
|
|
9
|
-
import { isObject,
|
|
9
|
+
import { isObject, isNullish, } from "../utils/typeUtils.js";
|
|
10
10
|
import { validateToolName, validateToolDescription, } from "../utils/parameterValidation.js";
|
|
11
11
|
/**
|
|
12
12
|
* ToolDiscoveryService
|
|
@@ -342,7 +342,7 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
342
342
|
// Update tool statistics
|
|
343
343
|
this.updateToolStats(toolKey, true, duration);
|
|
344
344
|
// Validate output if requested
|
|
345
|
-
if (options.validateOutput !== false
|
|
345
|
+
if (options.validateOutput !== false) {
|
|
346
346
|
this.validateToolOutput(result);
|
|
347
347
|
}
|
|
348
348
|
mcpLogger.debug(`[ToolDiscoveryService] Tool execution completed: ${toolName}`, {
|
|
@@ -445,46 +445,30 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
445
445
|
* Validate tool output with enhanced type safety
|
|
446
446
|
*/
|
|
447
447
|
validateToolOutput(result) {
|
|
448
|
-
//
|
|
448
|
+
// GENERIC ERROR HANDLING FOR ALL MCP TOOLS
|
|
449
|
+
// Different MCP servers return different error formats, so we should be permissive
|
|
450
|
+
// and let the AI handle any response format instead of throwing errors
|
|
451
|
+
// Only throw for truly invalid responses (null/undefined)
|
|
449
452
|
if (isNullish(result)) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (isObject(result)) {
|
|
454
|
-
// Check for explicit error property
|
|
455
|
-
if (result.error !== undefined) {
|
|
456
|
-
const errorMessage = isString(result.error)
|
|
457
|
-
? result.error
|
|
458
|
-
: "Tool execution failed with error";
|
|
459
|
-
throw new Error(`Tool execution error: ${errorMessage}`);
|
|
460
|
-
}
|
|
461
|
-
// Check for boolean error flag
|
|
462
|
-
if (isBoolean(result.isError) && result.isError === true) {
|
|
463
|
-
const errorDetail = isString(result.message)
|
|
464
|
-
? `: ${result.message}`
|
|
465
|
-
: "";
|
|
466
|
-
throw new Error(`Tool execution failed${errorDetail}`);
|
|
467
|
-
}
|
|
468
|
-
// Check for common error status patterns
|
|
469
|
-
if (isString(result.status) &&
|
|
470
|
-
(result.status === "error" || result.status === "failed")) {
|
|
471
|
-
const errorDetail = isString(result.message) || isString(result.reason)
|
|
472
|
-
? `: ${result.message || result.reason}`
|
|
473
|
-
: "";
|
|
474
|
-
throw new Error(`Tool execution failed${errorDetail}`);
|
|
475
|
-
}
|
|
476
|
-
// Check for success: false pattern
|
|
477
|
-
if (isBoolean(result.success) && result.success === false) {
|
|
478
|
-
const errorDetail = isString(result.message) || isString(result.error)
|
|
479
|
-
? `: ${result.message || result.error}`
|
|
480
|
-
: "";
|
|
481
|
-
throw new Error(`Tool execution unsuccessful${errorDetail}`);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
// Validate that string results are not empty
|
|
485
|
-
if (isString(result) && result.trim() === "") {
|
|
486
|
-
throw new Error("Tool returned empty string result");
|
|
453
|
+
mcpLogger.debug("[ToolDiscoveryService] Tool returned null/undefined, treating as empty response");
|
|
454
|
+
// Even null responses can be valid for some tools - don't throw
|
|
455
|
+
return;
|
|
487
456
|
}
|
|
457
|
+
// Log what we received for debugging, but don't validate specific formats
|
|
458
|
+
mcpLogger.debug("[ToolDiscoveryService] Tool response received", {
|
|
459
|
+
type: typeof result,
|
|
460
|
+
isArray: Array.isArray(result),
|
|
461
|
+
isObject: isObject(result),
|
|
462
|
+
hasKeys: isObject(result) ? Object.keys(result).length : 0,
|
|
463
|
+
fullResponse: result // Log the complete response, not a truncated sample
|
|
464
|
+
});
|
|
465
|
+
// COMPLETELY PERMISSIVE APPROACH:
|
|
466
|
+
// - Any response format is valid (objects, strings, arrays, booleans, numbers)
|
|
467
|
+
// - Even error responses are passed to the AI to handle
|
|
468
|
+
// - The AI can interpret error messages and retry with different approaches
|
|
469
|
+
// - This works with any MCP server regardless of their response format
|
|
470
|
+
// No validation or throwing - let the AI handle everything
|
|
471
|
+
return;
|
|
488
472
|
}
|
|
489
473
|
/**
|
|
490
474
|
* Update tool statistics
|
package/dist/neurolink.js
CHANGED
|
@@ -32,6 +32,8 @@ import { EventEmitter } from "events";
|
|
|
32
32
|
import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
|
|
33
33
|
import { applyConversationMemoryDefaults, getConversationMessages, storeConversationTurn, } from "./utils/conversationMemoryUtils.js";
|
|
34
34
|
import { ExternalServerManager } from "./mcp/externalServerManager.js";
|
|
35
|
+
// Import direct tools server for automatic registration
|
|
36
|
+
import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
|
|
35
37
|
import { ContextManager } from "./context/ContextManager.js";
|
|
36
38
|
import { defaultContextConfig } from "./context/config.js";
|
|
37
39
|
import { isNonNullObject } from "./utils/typeUtils.js";
|
|
@@ -130,6 +132,19 @@ export class NeuroLink {
|
|
|
130
132
|
]);
|
|
131
133
|
// Register all providers with lazy loading support
|
|
132
134
|
await ProviderRegistry.registerAllProviders();
|
|
135
|
+
// Register the direct tools server to make websearch and other tools available
|
|
136
|
+
try {
|
|
137
|
+
// Use the server ID string for registration instead of the server object
|
|
138
|
+
await toolRegistry.registerServer("neurolink-direct", directToolsServer);
|
|
139
|
+
mcpLogger.debug("[NeuroLink] Direct tools server registered successfully", {
|
|
140
|
+
serverId: "neurolink-direct",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
mcpLogger.warn("[NeuroLink] Failed to register direct tools server", {
|
|
145
|
+
error: error instanceof Error ? error.message : String(error),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
133
148
|
// Load MCP configuration from .mcp-config.json using ExternalServerManager
|
|
134
149
|
try {
|
|
135
150
|
const configResult = await this.externalServerManager.loadMCPConfiguration();
|
|
@@ -356,27 +371,55 @@ export class NeuroLink {
|
|
|
356
371
|
}
|
|
357
372
|
// Try MCP-enhanced generation first (if not explicitly disabled)
|
|
358
373
|
if (!options.disableTools) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
logger.debug(`[${functionTag}] MCP generation
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
374
|
+
let mcpAttempts = 0;
|
|
375
|
+
const maxMcpRetries = 2; // Allow retries for tool-related failures
|
|
376
|
+
while (mcpAttempts <= maxMcpRetries) {
|
|
377
|
+
try {
|
|
378
|
+
logger.debug(`[${functionTag}] Attempting MCP generation (attempt ${mcpAttempts + 1}/${maxMcpRetries + 1})...`);
|
|
379
|
+
const mcpResult = await this.tryMCPGeneration(options);
|
|
380
|
+
if (mcpResult && mcpResult.content) {
|
|
381
|
+
logger.debug(`[${functionTag}] MCP generation successful on attempt ${mcpAttempts + 1}`, {
|
|
382
|
+
contentLength: mcpResult.content.length,
|
|
383
|
+
toolsUsed: mcpResult.toolsUsed?.length || 0,
|
|
384
|
+
toolExecutions: mcpResult.toolExecutions?.length || 0,
|
|
385
|
+
});
|
|
386
|
+
// Store conversation turn
|
|
387
|
+
await storeConversationTurn(this.conversationMemory, options, mcpResult);
|
|
388
|
+
return mcpResult;
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
logger.debug(`[${functionTag}] MCP generation returned empty result on attempt ${mcpAttempts + 1}:`, {
|
|
392
|
+
hasResult: !!mcpResult,
|
|
393
|
+
hasContent: !!(mcpResult && mcpResult.content),
|
|
394
|
+
contentLength: mcpResult?.content?.length || 0,
|
|
395
|
+
toolExecutions: mcpResult?.toolExecutions?.length || 0,
|
|
396
|
+
});
|
|
397
|
+
// If we got a result but no content, and we have tool executions, this might be a tool success case
|
|
398
|
+
if (mcpResult &&
|
|
399
|
+
mcpResult.toolExecutions &&
|
|
400
|
+
mcpResult.toolExecutions.length > 0) {
|
|
401
|
+
logger.debug(`[${functionTag}] Found tool executions but no content, continuing with result`);
|
|
402
|
+
// Store conversation turn even with empty content if tools executed
|
|
403
|
+
await storeConversationTurn(this.conversationMemory, options, mcpResult);
|
|
404
|
+
return mcpResult;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
367
407
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
408
|
+
catch (error) {
|
|
409
|
+
mcpAttempts++;
|
|
410
|
+
logger.debug(`[${functionTag}] MCP generation failed on attempt ${mcpAttempts}/${maxMcpRetries + 1}`, {
|
|
411
|
+
error: error instanceof Error ? error.message : String(error),
|
|
412
|
+
willRetry: mcpAttempts <= maxMcpRetries,
|
|
373
413
|
});
|
|
414
|
+
// If this was the last attempt, break and fall back
|
|
415
|
+
if (mcpAttempts > maxMcpRetries) {
|
|
416
|
+
logger.debug(`[${functionTag}] All MCP attempts exhausted, falling back to direct generation`);
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
// Small delay before retry to allow transient issues to resolve
|
|
420
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
374
421
|
}
|
|
375
|
-
|
|
376
|
-
catch (error) {
|
|
377
|
-
logger.debug(`[${functionTag}] MCP generation failed, falling back`, {
|
|
378
|
-
error: error instanceof Error ? error.message : String(error),
|
|
379
|
-
});
|
|
422
|
+
mcpAttempts++;
|
|
380
423
|
}
|
|
381
424
|
}
|
|
382
425
|
// Fall back to direct provider generation
|
|
@@ -430,19 +473,40 @@ export class NeuroLink {
|
|
|
430
473
|
conversationMessages, // Inject conversation history
|
|
431
474
|
});
|
|
432
475
|
const responseTime = Date.now() - startTime;
|
|
433
|
-
//
|
|
434
|
-
|
|
476
|
+
// Enhanced result validation - consider tool executions as valid results
|
|
477
|
+
const hasContent = result && result.content && result.content.trim().length > 0;
|
|
478
|
+
const hasToolExecutions = result && result.toolExecutions && result.toolExecutions.length > 0;
|
|
479
|
+
// Log detailed result analysis for debugging
|
|
480
|
+
mcpLogger.debug(`[${functionTag}] Result validation:`, {
|
|
481
|
+
hasResult: !!result,
|
|
482
|
+
hasContent,
|
|
483
|
+
hasToolExecutions,
|
|
484
|
+
contentLength: result?.content?.length || 0,
|
|
485
|
+
toolExecutionsCount: result?.toolExecutions?.length || 0,
|
|
486
|
+
toolsUsedCount: result?.toolsUsed?.length || 0,
|
|
487
|
+
});
|
|
488
|
+
// Accept result if it has content OR successful tool executions
|
|
489
|
+
if (!hasContent && !hasToolExecutions) {
|
|
490
|
+
mcpLogger.debug(`[${functionTag}] Result rejected: no content and no tool executions`);
|
|
435
491
|
return null; // Let caller fall back to direct generation
|
|
436
492
|
}
|
|
437
|
-
//
|
|
493
|
+
// Transform tool executions with enhanced preservation
|
|
494
|
+
const transformedToolExecutions = transformToolExecutionsForMCP(result.toolExecutions);
|
|
495
|
+
// Log transformation results
|
|
496
|
+
mcpLogger.debug(`[${functionTag}] Tool execution transformation:`, {
|
|
497
|
+
originalCount: result?.toolExecutions?.length || 0,
|
|
498
|
+
transformedCount: transformedToolExecutions.length,
|
|
499
|
+
transformedTools: transformedToolExecutions.map((te) => te.toolName),
|
|
500
|
+
});
|
|
501
|
+
// Return enhanced result with preserved tool information
|
|
438
502
|
return {
|
|
439
|
-
content: result.content,
|
|
503
|
+
content: result.content || "", // Ensure content is never undefined
|
|
440
504
|
provider: providerName,
|
|
441
505
|
usage: result.usage,
|
|
442
506
|
responseTime,
|
|
443
507
|
toolsUsed: result.toolsUsed || [],
|
|
444
|
-
toolExecutions:
|
|
445
|
-
enhancedWithTools:
|
|
508
|
+
toolExecutions: transformedToolExecutions,
|
|
509
|
+
enhancedWithTools: Boolean(hasToolExecutions), // Mark as enhanced if tools were actually used
|
|
446
510
|
availableTools: transformToolsForMCP(availableTools),
|
|
447
511
|
// Include analytics and evaluation from BaseProvider
|
|
448
512
|
analytics: result.analytics,
|
|
@@ -24,13 +24,56 @@ export function transformToolExecutions(toolExecutions) {
|
|
|
24
24
|
if (!toolExecutions || !Array.isArray(toolExecutions)) {
|
|
25
25
|
return [];
|
|
26
26
|
}
|
|
27
|
-
return toolExecutions.map((te) => {
|
|
27
|
+
return toolExecutions.map((te, index) => {
|
|
28
28
|
const teRecord = te;
|
|
29
|
+
// Enhanced tool name extraction with multiple fallback strategies
|
|
30
|
+
let toolName = teRecord.name ||
|
|
31
|
+
teRecord.toolName ||
|
|
32
|
+
teRecord.tool ||
|
|
33
|
+
"";
|
|
34
|
+
// If still no name, try to extract from nested objects
|
|
35
|
+
if (!toolName &&
|
|
36
|
+
teRecord.toolCall &&
|
|
37
|
+
typeof teRecord.toolCall === "object") {
|
|
38
|
+
const toolCall = teRecord.toolCall;
|
|
39
|
+
toolName =
|
|
40
|
+
toolCall.name || toolCall.toolName || "";
|
|
41
|
+
}
|
|
42
|
+
// Last resort: use index-based fallback to avoid "Unknown Tool"
|
|
43
|
+
if (!toolName) {
|
|
44
|
+
toolName = `tool_execution_${index}`;
|
|
45
|
+
}
|
|
46
|
+
// Enhanced input extraction
|
|
47
|
+
let input = teRecord.input ||
|
|
48
|
+
teRecord.parameters ||
|
|
49
|
+
teRecord.args ||
|
|
50
|
+
{};
|
|
51
|
+
// Extract input from nested toolCall if available
|
|
52
|
+
if (Object.keys(input).length === 0 &&
|
|
53
|
+
teRecord.toolCall &&
|
|
54
|
+
typeof teRecord.toolCall === "object") {
|
|
55
|
+
const toolCall = teRecord.toolCall;
|
|
56
|
+
input =
|
|
57
|
+
toolCall.input ||
|
|
58
|
+
toolCall.parameters ||
|
|
59
|
+
toolCall.args ||
|
|
60
|
+
{};
|
|
61
|
+
}
|
|
62
|
+
// Enhanced output extraction with success indication
|
|
63
|
+
let output = teRecord.output ||
|
|
64
|
+
teRecord.result ||
|
|
65
|
+
teRecord.response ||
|
|
66
|
+
"success";
|
|
67
|
+
// Enhanced duration extraction
|
|
68
|
+
let duration = teRecord.duration ??
|
|
69
|
+
teRecord.executionTime ??
|
|
70
|
+
teRecord.responseTime ??
|
|
71
|
+
0;
|
|
29
72
|
return {
|
|
30
|
-
name:
|
|
31
|
-
input:
|
|
32
|
-
output:
|
|
33
|
-
duration:
|
|
73
|
+
name: toolName,
|
|
74
|
+
input: input,
|
|
75
|
+
output: output,
|
|
76
|
+
duration: duration,
|
|
34
77
|
};
|
|
35
78
|
});
|
|
36
79
|
}
|
|
@@ -42,13 +85,63 @@ export function transformToolExecutionsForMCP(toolExecutions) {
|
|
|
42
85
|
if (!toolExecutions || !Array.isArray(toolExecutions)) {
|
|
43
86
|
return [];
|
|
44
87
|
}
|
|
45
|
-
return toolExecutions.map((te) => {
|
|
88
|
+
return toolExecutions.map((te, index) => {
|
|
46
89
|
const teRecord = te;
|
|
90
|
+
// Enhanced tool name extraction matching the main function
|
|
91
|
+
let toolName = teRecord.name ||
|
|
92
|
+
teRecord.toolName ||
|
|
93
|
+
teRecord.tool ||
|
|
94
|
+
"";
|
|
95
|
+
// Try nested toolCall extraction
|
|
96
|
+
if (!toolName &&
|
|
97
|
+
teRecord.toolCall &&
|
|
98
|
+
typeof teRecord.toolCall === "object") {
|
|
99
|
+
const toolCall = teRecord.toolCall;
|
|
100
|
+
toolName =
|
|
101
|
+
toolCall.name || toolCall.toolName || "";
|
|
102
|
+
}
|
|
103
|
+
// Fallback to avoid empty names
|
|
104
|
+
if (!toolName) {
|
|
105
|
+
toolName = `mcp_tool_execution_${index}`;
|
|
106
|
+
}
|
|
107
|
+
// Enhanced execution time extraction
|
|
108
|
+
let executionTime = teRecord.duration ??
|
|
109
|
+
teRecord.executionTime ??
|
|
110
|
+
teRecord.responseTime ??
|
|
111
|
+
0;
|
|
112
|
+
// Enhanced success detection - check for actual success indicators
|
|
113
|
+
let success = true; // Default to true
|
|
114
|
+
// Check for explicit success/error indicators
|
|
115
|
+
if (teRecord.success !== undefined) {
|
|
116
|
+
success = Boolean(teRecord.success);
|
|
117
|
+
}
|
|
118
|
+
else if (teRecord.error !== undefined) {
|
|
119
|
+
success = false;
|
|
120
|
+
}
|
|
121
|
+
else if (teRecord.status !== undefined) {
|
|
122
|
+
const status = String(teRecord.status).toLowerCase().trim();
|
|
123
|
+
success = !["error", "failed", "failure", "fail"].includes(status);
|
|
124
|
+
}
|
|
125
|
+
// Enhanced server ID extraction
|
|
126
|
+
let serverId = teRecord.serverId ||
|
|
127
|
+
teRecord.server ||
|
|
128
|
+
teRecord.source ||
|
|
129
|
+
undefined;
|
|
130
|
+
// Try to extract from nested structures
|
|
131
|
+
if (!serverId &&
|
|
132
|
+
teRecord.toolCall &&
|
|
133
|
+
typeof teRecord.toolCall === "object") {
|
|
134
|
+
const toolCall = teRecord.toolCall;
|
|
135
|
+
serverId =
|
|
136
|
+
toolCall.serverId ||
|
|
137
|
+
toolCall.server ||
|
|
138
|
+
undefined;
|
|
139
|
+
}
|
|
47
140
|
return {
|
|
48
|
-
toolName:
|
|
49
|
-
executionTime:
|
|
50
|
-
success:
|
|
51
|
-
serverId:
|
|
141
|
+
toolName: toolName,
|
|
142
|
+
executionTime: executionTime,
|
|
143
|
+
success: success,
|
|
144
|
+
serverId: serverId,
|
|
52
145
|
};
|
|
53
146
|
});
|
|
54
147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.15.0",
|
|
4
4
|
"description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Juspay Technologies",
|
|
@@ -148,6 +148,7 @@
|
|
|
148
148
|
"@aws-sdk/client-sagemaker": "^3.862.0",
|
|
149
149
|
"@aws-sdk/client-sagemaker-runtime": "^3.862.0",
|
|
150
150
|
"@aws-sdk/types": "^3.862.0",
|
|
151
|
+
"@google-cloud/vertexai": "^1.10.0",
|
|
151
152
|
"@google/generative-ai": "^0.24.1",
|
|
152
153
|
"@huggingface/inference": "^2.8.0",
|
|
153
154
|
"@modelcontextprotocol/sdk": "^1.13.0",
|