@juspay/neurolink 7.12.0 → 7.14.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 (65) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +90 -25
  3. package/dist/config/conversationMemoryConfig.js +2 -1
  4. package/dist/context/ContextManager.d.ts +28 -0
  5. package/dist/context/ContextManager.js +113 -0
  6. package/dist/context/config.d.ts +5 -0
  7. package/dist/context/config.js +42 -0
  8. package/dist/context/types.d.ts +20 -0
  9. package/dist/context/types.js +1 -0
  10. package/dist/context/utils.d.ts +7 -0
  11. package/dist/context/utils.js +8 -0
  12. package/dist/core/baseProvider.d.ts +16 -1
  13. package/dist/core/baseProvider.js +208 -9
  14. package/dist/core/conversationMemoryManager.js +3 -2
  15. package/dist/core/factory.js +13 -2
  16. package/dist/factories/providerFactory.js +5 -11
  17. package/dist/factories/providerRegistry.js +2 -2
  18. package/dist/lib/config/conversationMemoryConfig.js +2 -1
  19. package/dist/lib/context/ContextManager.d.ts +28 -0
  20. package/dist/lib/context/ContextManager.js +113 -0
  21. package/dist/lib/context/config.d.ts +5 -0
  22. package/dist/lib/context/config.js +42 -0
  23. package/dist/lib/context/types.d.ts +20 -0
  24. package/dist/lib/context/types.js +1 -0
  25. package/dist/lib/context/utils.d.ts +7 -0
  26. package/dist/lib/context/utils.js +8 -0
  27. package/dist/lib/core/baseProvider.d.ts +16 -1
  28. package/dist/lib/core/baseProvider.js +208 -9
  29. package/dist/lib/core/conversationMemoryManager.js +3 -2
  30. package/dist/lib/core/factory.js +13 -2
  31. package/dist/lib/factories/providerFactory.js +5 -11
  32. package/dist/lib/factories/providerRegistry.js +2 -2
  33. package/dist/lib/mcp/externalServerManager.d.ts +115 -0
  34. package/dist/lib/mcp/externalServerManager.js +677 -0
  35. package/dist/lib/mcp/mcpCircuitBreaker.d.ts +184 -0
  36. package/dist/lib/mcp/mcpCircuitBreaker.js +338 -0
  37. package/dist/lib/mcp/mcpClientFactory.d.ts +104 -0
  38. package/dist/lib/mcp/mcpClientFactory.js +416 -0
  39. package/dist/lib/mcp/toolDiscoveryService.d.ts +192 -0
  40. package/dist/lib/mcp/toolDiscoveryService.js +578 -0
  41. package/dist/lib/neurolink.d.ts +128 -16
  42. package/dist/lib/neurolink.js +555 -49
  43. package/dist/lib/providers/googleVertex.d.ts +1 -1
  44. package/dist/lib/providers/googleVertex.js +23 -7
  45. package/dist/lib/types/externalMcp.d.ts +282 -0
  46. package/dist/lib/types/externalMcp.js +6 -0
  47. package/dist/lib/types/generateTypes.d.ts +0 -1
  48. package/dist/lib/types/index.d.ts +1 -0
  49. package/dist/mcp/externalServerManager.d.ts +115 -0
  50. package/dist/mcp/externalServerManager.js +677 -0
  51. package/dist/mcp/mcpCircuitBreaker.d.ts +184 -0
  52. package/dist/mcp/mcpCircuitBreaker.js +338 -0
  53. package/dist/mcp/mcpClientFactory.d.ts +104 -0
  54. package/dist/mcp/mcpClientFactory.js +416 -0
  55. package/dist/mcp/toolDiscoveryService.d.ts +192 -0
  56. package/dist/mcp/toolDiscoveryService.js +578 -0
  57. package/dist/neurolink.d.ts +128 -16
  58. package/dist/neurolink.js +555 -49
  59. package/dist/providers/googleVertex.d.ts +1 -1
  60. package/dist/providers/googleVertex.js +23 -7
  61. package/dist/types/externalMcp.d.ts +282 -0
  62. package/dist/types/externalMcp.js +6 -0
  63. package/dist/types/generateTypes.d.ts +0 -1
  64. package/dist/types/index.d.ts +1 -0
  65. package/package.json +1 -1
@@ -157,10 +157,20 @@ export class BaseProvider {
157
157
  try {
158
158
  // Import generateText dynamically to avoid circular dependencies
159
159
  const { generateText } = await import("ai");
160
- // Get ALL available tools (direct + MCP when available)
160
+ // Get ALL available tools (direct + MCP + external from options)
161
161
  const shouldUseTools = !options.disableTools && this.supportsTools();
162
- const tools = shouldUseTools ? await this.getAllTools() : {};
163
- logger.debug(`[BaseProvider.generate] Tools for ${this.providerName}: ${Object.keys(tools).join(", ")}`);
162
+ const baseTools = shouldUseTools ? await this.getAllTools() : {};
163
+ const tools = shouldUseTools
164
+ ? {
165
+ ...baseTools,
166
+ ...(options.tools || {}), // Include external tools passed from NeuroLink
167
+ }
168
+ : {};
169
+ logger.debug(`[BaseProvider.generate] Tools for ${this.providerName}:`, {
170
+ directTools: Object.keys(baseTools),
171
+ externalTools: Object.keys(options.tools || {}),
172
+ totalTools: Object.keys(tools),
173
+ });
164
174
  // EVERY provider uses Vercel AI SDK - no exceptions
165
175
  const model = await this.getAISDKModel(); // This method is now REQUIRED
166
176
  // Build proper message array with conversation history
@@ -201,22 +211,66 @@ export class BaseProvider {
201
211
  const uniqueToolsUsed = [...new Set(toolsUsed)];
202
212
  // ✅ Extract tool executions from AI SDK result
203
213
  const toolExecutions = [];
214
+ // Create a map of tool calls to their arguments for matching with results
215
+ const toolCallArgsMap = new Map();
204
216
  // Extract tool executions from AI SDK result steps
205
- // Extract tool executions from steps (where tool results are stored)
206
217
  if (result.steps &&
207
218
  Array.isArray(result.steps)) {
208
219
  for (const step of result.steps ||
209
220
  []) {
210
- // Focus only on tool results (which have complete execution data)
211
- // Tool calls are just the requests, tool results contain the actual execution data
221
+ // First, collect tool calls and their arguments
222
+ if (step?.toolCalls && Array.isArray(step.toolCalls)) {
223
+ for (const toolCall of step.toolCalls) {
224
+ const tcRecord = toolCall;
225
+ const toolName = tcRecord.toolName ||
226
+ tcRecord.name ||
227
+ "unknown";
228
+ const toolId = tcRecord.toolCallId ||
229
+ tcRecord.id ||
230
+ toolName;
231
+ // Extract arguments from tool call
232
+ let callArgs = {};
233
+ if (tcRecord.args) {
234
+ callArgs = tcRecord.args;
235
+ }
236
+ else if (tcRecord.arguments) {
237
+ callArgs = tcRecord.arguments;
238
+ }
239
+ else if (tcRecord.parameters) {
240
+ callArgs = tcRecord.parameters;
241
+ }
242
+ toolCallArgsMap.set(toolId, callArgs);
243
+ toolCallArgsMap.set(toolName, callArgs); // Also map by name as fallback
244
+ }
245
+ }
246
+ // Then, process tool results and match with call arguments
212
247
  if (step?.toolResults && Array.isArray(step.toolResults)) {
213
248
  for (const toolResult of step.toolResults) {
214
249
  const trRecord = toolResult;
250
+ const toolName = trRecord.toolName || "unknown";
251
+ const toolId = trRecord.toolCallId || trRecord.id;
252
+ // Try to get arguments from the tool result first
253
+ let toolArgs = {};
254
+ if (trRecord.args) {
255
+ toolArgs = trRecord.args;
256
+ }
257
+ else if (trRecord.arguments) {
258
+ toolArgs = trRecord.arguments;
259
+ }
260
+ else if (trRecord.parameters) {
261
+ toolArgs = trRecord.parameters;
262
+ }
263
+ else if (trRecord.input) {
264
+ toolArgs = trRecord.input;
265
+ }
266
+ else {
267
+ // Fallback: get arguments from the corresponding tool call
268
+ toolArgs = toolCallArgsMap.get(toolId || toolName) || {};
269
+ }
215
270
  toolExecutions.push({
216
- name: trRecord.toolName || "unknown",
217
- input: trRecord.args || {},
271
+ name: toolName,
272
+ input: toolArgs,
218
273
  output: trRecord.result || "success",
219
- duration: 0, // AI SDK doesn't track duration
220
274
  });
221
275
  }
222
276
  }
@@ -248,6 +302,12 @@ export class BaseProvider {
248
302
  toolResults: result.toolResults,
249
303
  toolsUsed: uniqueToolsUsed,
250
304
  toolExecutions, // ✅ Add extracted tool executions
305
+ availableTools: Object.keys(tools).map((name) => ({
306
+ name,
307
+ description: tools[name].description || "No description available",
308
+ parameters: tools[name].parameters || {},
309
+ server: tools[name].serverId || "direct",
310
+ })),
251
311
  };
252
312
  // Enhanced result with analytics and evaluation
253
313
  return await this.enhanceResult(enhancedResult, options, startTime);
@@ -339,6 +399,71 @@ export class BaseProvider {
339
399
  // Not an error - custom tools are optional
340
400
  }
341
401
  }
402
+ // ✅ CRITICAL FIX: Add external MCP tools if SDK has external server manager
403
+ if (this.sdk &&
404
+ this.sdk.externalServerManager &&
405
+ typeof this.sdk.externalServerManager.getAllTools === "function") {
406
+ try {
407
+ logger.debug(`[BaseProvider] Loading external MCP tools from SDK via externalServerManager`);
408
+ const externalTools = this.sdk.externalServerManager.getAllTools();
409
+ logger.debug(`[BaseProvider] Found ${externalTools.length} external MCP tools`, {
410
+ tools: externalTools.map((t) => ({
411
+ name: t.name,
412
+ available: t.isAvailable,
413
+ server: t.serverId,
414
+ })),
415
+ });
416
+ for (const externalTool of externalTools) {
417
+ if (externalTool.isAvailable) {
418
+ logger.debug(`[BaseProvider] Converting external MCP tool: ${externalTool.name} from ${externalTool.serverId}`);
419
+ // Convert to AI SDK tool format
420
+ const { tool: createAISDKTool } = await import("ai");
421
+ const { z } = await import("zod");
422
+ tools[externalTool.name] = createAISDKTool({
423
+ description: externalTool.description ||
424
+ `External MCP tool ${externalTool.name}`,
425
+ parameters: await this.convertMCPSchemaToZod(externalTool.inputSchema),
426
+ execute: async (args) => {
427
+ logger.debug(`[BaseProvider] Executing external MCP tool: ${externalTool.name}`, { args });
428
+ // Execute via SDK's external server manager
429
+ if (this.sdk &&
430
+ this.sdk.externalServerManager &&
431
+ typeof this.sdk.externalServerManager.executeTool ===
432
+ "function") {
433
+ return await this.sdk.externalServerManager.executeTool(externalTool.serverId, externalTool.name, args);
434
+ }
435
+ else {
436
+ throw new Error(`Cannot execute external MCP tool: SDK externalServerManager.executeTool not available`);
437
+ }
438
+ },
439
+ });
440
+ logger.debug(`[BaseProvider] Successfully added external MCP tool: ${externalTool.name}`);
441
+ }
442
+ else {
443
+ logger.debug(`[BaseProvider] Skipping unavailable external MCP tool: ${externalTool.name}`);
444
+ }
445
+ }
446
+ logger.debug(`[BaseProvider] External MCP tools loading complete`, {
447
+ totalExternalTools: externalTools.length,
448
+ availableExternalTools: externalTools.filter((t) => t.isAvailable)
449
+ .length,
450
+ addedToTools: externalTools.filter((t) => t.isAvailable).length,
451
+ });
452
+ }
453
+ catch (error) {
454
+ logger.error(`[BaseProvider] Failed to load external MCP tools for ${this.providerName}:`, error);
455
+ // Not an error - external tools are optional
456
+ }
457
+ }
458
+ else {
459
+ logger.debug(`[BaseProvider] No external MCP tools interface available`, {
460
+ hasSDK: !!this.sdk,
461
+ hasExternalServerManager: this.sdk && !!this.sdk.externalServerManager,
462
+ hasGetAllTools: this.sdk &&
463
+ this.sdk.externalServerManager &&
464
+ typeof this.sdk.externalServerManager.getAllTools === "function",
465
+ });
466
+ }
342
467
  // MCP tools loading simplified - removed functionCalling dependency
343
468
  if (!this.mcpTools) {
344
469
  // Set empty tools object - MCP tools are handled at a higher level
@@ -351,6 +476,80 @@ export class BaseProvider {
351
476
  logger.debug(`[BaseProvider] getAllTools returning tools: ${Object.keys(tools).join(", ")}`);
352
477
  return tools;
353
478
  }
479
+ /**
480
+ * Convert MCP JSON Schema to Zod schema for AI SDK tools
481
+ * Handles common MCP schema patterns safely
482
+ */
483
+ async convertMCPSchemaToZod(inputSchema) {
484
+ const { z } = await import("zod");
485
+ if (!inputSchema || typeof inputSchema !== "object") {
486
+ return z.object({});
487
+ }
488
+ try {
489
+ const schema = inputSchema;
490
+ const zodFields = {};
491
+ // Handle JSON Schema properties
492
+ if (schema.properties && typeof schema.properties === "object") {
493
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
494
+ for (const [propName, propDef] of Object.entries(schema.properties)) {
495
+ const prop = propDef;
496
+ let zodType;
497
+ // Convert based on JSON Schema type
498
+ switch (prop.type) {
499
+ case "string":
500
+ zodType = z.string();
501
+ if (prop.description) {
502
+ zodType = zodType.describe(prop.description);
503
+ }
504
+ break;
505
+ case "number":
506
+ case "integer":
507
+ zodType = z.number();
508
+ if (prop.description) {
509
+ zodType = zodType.describe(prop.description);
510
+ }
511
+ break;
512
+ case "boolean":
513
+ zodType = z.boolean();
514
+ if (prop.description) {
515
+ zodType = zodType.describe(prop.description);
516
+ }
517
+ break;
518
+ case "array":
519
+ zodType = z.array(z.unknown());
520
+ if (prop.description) {
521
+ zodType = zodType.describe(prop.description);
522
+ }
523
+ break;
524
+ case "object":
525
+ zodType = z.object({});
526
+ if (prop.description) {
527
+ zodType = zodType.describe(prop.description);
528
+ }
529
+ break;
530
+ default:
531
+ // Unknown type, use string as fallback
532
+ zodType = z.string();
533
+ if (prop.description) {
534
+ zodType = zodType.describe(prop.description);
535
+ }
536
+ }
537
+ // Make optional if not required
538
+ if (!required.has(propName)) {
539
+ zodType = zodType.optional();
540
+ }
541
+ zodFields[propName] = zodType;
542
+ }
543
+ }
544
+ return Object.keys(zodFields).length > 0
545
+ ? z.object(zodFields)
546
+ : z.object({});
547
+ }
548
+ catch (error) {
549
+ logger.warn(`Failed to convert MCP schema to Zod, using empty schema:`, error);
550
+ return z.object({});
551
+ }
552
+ }
354
553
  /**
355
554
  * Set session context for MCP tools
356
555
  */
@@ -3,7 +3,7 @@
3
3
  * Handles in-memory conversation storage, session management, and context injection
4
4
  */
5
5
  import { ConversationMemoryError } from "../types/conversationTypes.js";
6
- import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN } from "../config/conversationMemoryConfig.js";
6
+ import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN, } from "../config/conversationMemoryConfig.js";
7
7
  import { logger } from "../utils/logger.js";
8
8
  export class ConversationMemoryManager {
9
9
  sessions = new Map();
@@ -49,7 +49,8 @@ export class ConversationMemoryManager {
49
49
  session.messages.push({ role: "user", content: userMessage }, { role: "assistant", content: aiResponse });
50
50
  session.lastActivity = Date.now();
51
51
  // Enforce per-session turn limit (each turn = MESSAGES_PER_TURN messages: user + assistant)
52
- const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) * MESSAGES_PER_TURN;
52
+ const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) *
53
+ MESSAGES_PER_TURN;
53
54
  if (session.messages.length > maxMessages) {
54
55
  session.messages = session.messages.slice(-maxMessages);
55
56
  }
@@ -109,13 +109,24 @@ export class AIProviderFactory {
109
109
  const finalModelName = resolvedModelName === "default" || resolvedModelName === null
110
110
  ? undefined
111
111
  : resolvedModelName;
112
- const provider = await ProviderFactory.createProvider(normalizedName, finalModelName, sdk);
112
+ // CRITICAL FIX: Pass external MCP tools interface to BaseProvider
113
+ let finalSdk = sdk;
114
+ if (sdk && typeof sdk.getExternalMCPTools === "function") {
115
+ finalSdk = {
116
+ ...sdk,
117
+ externalServerManager: {
118
+ getAllTools: () => sdk.getExternalMCPTools(),
119
+ executeTool: (serverId, toolName, params) => sdk.executeExternalMCPTool(serverId, toolName, params),
120
+ },
121
+ };
122
+ }
123
+ // Create provider with enhanced SDK
124
+ const provider = await ProviderFactory.createProvider(normalizedName, finalModelName, finalSdk);
113
125
  logger.debug(componentIdentifier, "Pure factory pattern provider created", {
114
126
  providerName: normalizedName,
115
127
  modelName: finalModelName,
116
128
  factoryUsed: true,
117
129
  });
118
- // PURE FACTORY PATTERN: All providers handled by ProviderFactory - no switch statements needed
119
130
  // Wrap with MCP if enabled
120
131
  if (enableMCP) {
121
132
  try {
@@ -46,22 +46,16 @@ export class ProviderFactory {
46
46
  model = model || registration.defaultModel;
47
47
  }
48
48
  try {
49
- // Try calling as factory function first, then fallback to constructor
50
49
  let result;
51
50
  try {
52
- // Try as factory function
53
- result = registration.constructor(model, providerName, sdk);
51
+ // Try as async factory function first (most providers are async functions)
52
+ result = await registration.constructor(model, providerName, sdk);
54
53
  }
55
- catch (factoryError) {
56
- // Fallback to constructor
54
+ catch (functionError) {
55
+ // Fallback to constructor - ensure parameters are maintained
57
56
  result = new registration.constructor(model, providerName, sdk);
58
57
  }
59
- // Only await if result is actually a Promise
60
- if (result &&
61
- typeof result === "object" &&
62
- typeof result.then === "function") {
63
- result = await result;
64
- }
58
+ // Return result (no need to await again if already awaited in try block)
65
59
  return result;
66
60
  }
67
61
  catch (error) {
@@ -52,9 +52,9 @@ export class ProviderRegistry {
52
52
  process.env.AZURE_OPENAI_DEPLOYMENT_ID ||
53
53
  "gpt-4o-mini", ["azure", "azureOpenai"]);
54
54
  // Register Google Vertex AI provider
55
- ProviderFactory.registerProvider(AIProviderName.VERTEX, async (modelName) => {
55
+ ProviderFactory.registerProvider(AIProviderName.VERTEX, async (modelName, providerName, sdk) => {
56
56
  const { GoogleVertexProvider } = await import("../providers/googleVertex.js");
57
- return new GoogleVertexProvider(modelName);
57
+ return new GoogleVertexProvider(modelName, providerName, sdk);
58
58
  }, "claude-sonnet-4@20250514", ["vertex", "googleVertex"]);
59
59
  // Register Hugging Face provider (Unified Router implementation)
60
60
  ProviderFactory.registerProvider(AIProviderName.HUGGINGFACE, async (modelName) => {
@@ -0,0 +1,115 @@
1
+ /**
2
+ * External MCP Server Manager
3
+ * Handles lifecycle management of external MCP servers including:
4
+ * - Process spawning and management
5
+ * - Health monitoring and automatic restart
6
+ * - Connection management and cleanup
7
+ * - Tool discovery and registration
8
+ */
9
+ import { EventEmitter } from "events";
10
+ import { ToolDiscoveryService } from "./toolDiscoveryService.js";
11
+ import type { ExternalMCPServerConfig, ExternalMCPServerInstance, ExternalMCPServerHealth, ExternalMCPConfigValidation, ExternalMCPOperationResult, ExternalMCPManagerConfig, ExternalMCPToolInfo } from "../types/externalMcp.js";
12
+ /**
13
+ * ExternalServerManager
14
+ * Core class for managing external MCP servers
15
+ */
16
+ export declare class ExternalServerManager extends EventEmitter {
17
+ private servers;
18
+ private config;
19
+ private isShuttingDown;
20
+ private toolDiscovery;
21
+ constructor(config?: ExternalMCPManagerConfig);
22
+ /**
23
+ * Validate external MCP server configuration
24
+ */
25
+ validateConfig(config: ExternalMCPServerConfig): ExternalMCPConfigValidation;
26
+ /**
27
+ * Add a new external MCP server
28
+ */
29
+ addServer(serverId: string, config: ExternalMCPServerConfig): Promise<ExternalMCPOperationResult<ExternalMCPServerInstance>>;
30
+ /**
31
+ * Remove an external MCP server
32
+ */
33
+ removeServer(serverId: string): Promise<ExternalMCPOperationResult<void>>;
34
+ /**
35
+ * Start an external MCP server
36
+ */
37
+ private startServer;
38
+ /**
39
+ * Stop an external MCP server
40
+ */
41
+ private stopServer;
42
+ /**
43
+ * Update server status and emit events
44
+ */
45
+ private updateServerStatus;
46
+ /**
47
+ * Handle server errors
48
+ */
49
+ private handleServerError;
50
+ /**
51
+ * Handle server disconnection
52
+ */
53
+ private handleServerDisconnection;
54
+ /**
55
+ * Schedule server restart with exponential backoff
56
+ */
57
+ private scheduleRestart;
58
+ /**
59
+ * Start health monitoring for a server
60
+ */
61
+ private startHealthMonitoring;
62
+ /**
63
+ * Perform health check on a server
64
+ */
65
+ private performHealthCheck;
66
+ /**
67
+ * Get server instance
68
+ */
69
+ getServer(serverId: string): ExternalMCPServerInstance | undefined;
70
+ /**
71
+ * Get all servers
72
+ */
73
+ getAllServers(): Map<string, ExternalMCPServerInstance>;
74
+ /**
75
+ * Get server statuses
76
+ */
77
+ getServerStatuses(): ExternalMCPServerHealth[];
78
+ /**
79
+ * Shutdown all servers
80
+ */
81
+ shutdown(): Promise<void>;
82
+ /**
83
+ * Get manager statistics
84
+ */
85
+ getStatistics(): {
86
+ totalServers: number;
87
+ connectedServers: number;
88
+ failedServers: number;
89
+ totalTools: number;
90
+ totalConnections: number;
91
+ totalErrors: number;
92
+ };
93
+ /**
94
+ * Discover tools from a server
95
+ */
96
+ private discoverServerTools;
97
+ /**
98
+ * Execute a tool on a specific server
99
+ */
100
+ executeTool(serverId: string, toolName: string, parameters: Record<string, any>, options?: {
101
+ timeout?: number;
102
+ }): Promise<any>;
103
+ /**
104
+ * Get all tools from all servers
105
+ */
106
+ getAllTools(): ExternalMCPToolInfo[];
107
+ /**
108
+ * Get tools for a specific server
109
+ */
110
+ getServerTools(serverId: string): ExternalMCPToolInfo[];
111
+ /**
112
+ * Get tool discovery service
113
+ */
114
+ getToolDiscovery(): ToolDiscoveryService;
115
+ }