@juspay/neurolink 5.1.0 → 5.2.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 (166) hide show
  1. package/CHANGELOG.md +15 -9
  2. package/README.md +123 -126
  3. package/dist/agent/direct-tools.d.ts +6 -6
  4. package/dist/cli/commands/config.d.ts +3 -3
  5. package/dist/cli/commands/mcp.js +8 -7
  6. package/dist/cli/factories/command-factory.d.ts +4 -0
  7. package/dist/cli/factories/command-factory.js +57 -3
  8. package/dist/cli/index.js +87 -140
  9. package/dist/core/base-provider.d.ts +423 -0
  10. package/dist/core/base-provider.js +365 -0
  11. package/dist/core/constants.d.ts +1 -1
  12. package/dist/core/constants.js +1 -1
  13. package/dist/core/dynamic-models.d.ts +6 -6
  14. package/dist/core/evaluation.d.ts +19 -80
  15. package/dist/core/evaluation.js +185 -484
  16. package/dist/core/factory.d.ts +3 -3
  17. package/dist/core/factory.js +31 -91
  18. package/dist/core/service-registry.d.ts +47 -0
  19. package/dist/core/service-registry.js +112 -0
  20. package/dist/core/types.d.ts +8 -1
  21. package/dist/factories/compatibility-factory.js +1 -1
  22. package/dist/factories/provider-factory.d.ts +72 -0
  23. package/dist/factories/provider-factory.js +144 -0
  24. package/dist/factories/provider-registry.d.ts +38 -0
  25. package/dist/factories/provider-registry.js +107 -0
  26. package/dist/index.d.ts +4 -3
  27. package/dist/index.js +2 -4
  28. package/dist/lib/agent/direct-tools.d.ts +6 -6
  29. package/dist/lib/core/base-provider.d.ts +423 -0
  30. package/dist/lib/core/base-provider.js +365 -0
  31. package/dist/lib/core/constants.d.ts +1 -1
  32. package/dist/lib/core/constants.js +1 -1
  33. package/dist/lib/core/dynamic-models.d.ts +6 -6
  34. package/dist/lib/core/evaluation.d.ts +19 -80
  35. package/dist/lib/core/evaluation.js +185 -484
  36. package/dist/lib/core/factory.d.ts +3 -3
  37. package/dist/lib/core/factory.js +30 -91
  38. package/dist/lib/core/service-registry.d.ts +47 -0
  39. package/dist/lib/core/service-registry.js +112 -0
  40. package/dist/lib/core/types.d.ts +8 -1
  41. package/dist/lib/factories/compatibility-factory.js +1 -1
  42. package/dist/lib/factories/provider-factory.d.ts +72 -0
  43. package/dist/lib/factories/provider-factory.js +144 -0
  44. package/dist/lib/factories/provider-registry.d.ts +38 -0
  45. package/dist/lib/factories/provider-registry.js +107 -0
  46. package/dist/lib/index.d.ts +4 -3
  47. package/dist/lib/index.js +2 -4
  48. package/dist/lib/mcp/config.js +28 -3
  49. package/dist/lib/mcp/function-calling.js +1 -1
  50. package/dist/lib/mcp/initialize-tools.d.ts +1 -1
  51. package/dist/lib/mcp/initialize-tools.js +45 -1
  52. package/dist/lib/mcp/initialize.js +16 -6
  53. package/dist/lib/mcp/servers/agent/direct-tools-server.d.ts +8 -0
  54. package/dist/lib/mcp/servers/agent/direct-tools-server.js +109 -0
  55. package/dist/lib/mcp/servers/ai-providers/ai-core-server.js +3 -1
  56. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
  57. package/dist/lib/mcp/unified-registry.d.ts +4 -0
  58. package/dist/lib/mcp/unified-registry.js +42 -9
  59. package/dist/lib/neurolink.d.ts +156 -117
  60. package/dist/lib/neurolink.js +619 -404
  61. package/dist/lib/providers/amazon-bedrock.d.ts +32 -0
  62. package/dist/lib/providers/amazon-bedrock.js +143 -0
  63. package/dist/lib/providers/analytics-helper.js +7 -4
  64. package/dist/lib/providers/anthropic-baseprovider.d.ts +23 -0
  65. package/dist/lib/providers/anthropic-baseprovider.js +114 -0
  66. package/dist/lib/providers/anthropic.d.ts +19 -43
  67. package/dist/lib/providers/anthropic.js +82 -306
  68. package/dist/lib/providers/azure-openai.d.ts +20 -0
  69. package/dist/lib/providers/azure-openai.js +89 -0
  70. package/dist/lib/providers/google-ai-studio.d.ts +23 -0
  71. package/dist/lib/providers/google-ai-studio.js +107 -0
  72. package/dist/lib/providers/google-vertex.d.ts +47 -0
  73. package/dist/lib/providers/google-vertex.js +205 -0
  74. package/dist/lib/providers/huggingFace.d.ts +32 -25
  75. package/dist/lib/providers/huggingFace.js +97 -431
  76. package/dist/lib/providers/index.d.ts +9 -9
  77. package/dist/lib/providers/index.js +9 -9
  78. package/dist/lib/providers/mcp-provider.js +4 -0
  79. package/dist/lib/providers/mistral.d.ts +42 -0
  80. package/dist/lib/providers/mistral.js +160 -0
  81. package/dist/lib/providers/ollama.d.ts +52 -36
  82. package/dist/lib/providers/ollama.js +297 -520
  83. package/dist/lib/providers/openAI.d.ts +19 -18
  84. package/dist/lib/providers/openAI.js +76 -275
  85. package/dist/lib/sdk/tool-extension.d.ts +181 -0
  86. package/dist/lib/sdk/tool-extension.js +283 -0
  87. package/dist/lib/sdk/tool-registration.d.ts +95 -0
  88. package/dist/lib/sdk/tool-registration.js +167 -0
  89. package/dist/lib/types/generate-types.d.ts +1 -0
  90. package/dist/lib/types/mcp-types.d.ts +116 -0
  91. package/dist/lib/types/mcp-types.js +5 -0
  92. package/dist/lib/types/stream-types.d.ts +30 -18
  93. package/dist/lib/types/universal-provider-options.d.ts +87 -0
  94. package/dist/lib/types/universal-provider-options.js +53 -0
  95. package/dist/mcp/config.js +28 -3
  96. package/dist/mcp/function-calling.js +1 -1
  97. package/dist/mcp/initialize-tools.d.ts +1 -1
  98. package/dist/mcp/initialize-tools.js +45 -1
  99. package/dist/mcp/initialize.js +16 -6
  100. package/dist/mcp/servers/agent/direct-tools-server.d.ts +8 -0
  101. package/dist/mcp/servers/agent/direct-tools-server.js +109 -0
  102. package/dist/mcp/servers/ai-providers/ai-core-server.js +3 -1
  103. package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
  104. package/dist/mcp/unified-registry.d.ts +4 -0
  105. package/dist/mcp/unified-registry.js +42 -9
  106. package/dist/neurolink.d.ts +156 -117
  107. package/dist/neurolink.js +619 -404
  108. package/dist/providers/amazon-bedrock.d.ts +32 -0
  109. package/dist/providers/amazon-bedrock.js +143 -0
  110. package/dist/providers/analytics-helper.js +7 -4
  111. package/dist/providers/anthropic-baseprovider.d.ts +23 -0
  112. package/dist/providers/anthropic-baseprovider.js +114 -0
  113. package/dist/providers/anthropic.d.ts +19 -43
  114. package/dist/providers/anthropic.js +81 -305
  115. package/dist/providers/azure-openai.d.ts +20 -0
  116. package/dist/providers/azure-openai.js +89 -0
  117. package/dist/providers/google-ai-studio.d.ts +23 -0
  118. package/dist/providers/google-ai-studio.js +108 -0
  119. package/dist/providers/google-vertex.d.ts +47 -0
  120. package/dist/providers/google-vertex.js +205 -0
  121. package/dist/providers/huggingFace.d.ts +32 -25
  122. package/dist/providers/huggingFace.js +96 -430
  123. package/dist/providers/index.d.ts +9 -9
  124. package/dist/providers/index.js +9 -9
  125. package/dist/providers/mcp-provider.js +4 -0
  126. package/dist/providers/mistral.d.ts +42 -0
  127. package/dist/providers/mistral.js +160 -0
  128. package/dist/providers/ollama.d.ts +52 -36
  129. package/dist/providers/ollama.js +297 -519
  130. package/dist/providers/openAI.d.ts +19 -18
  131. package/dist/providers/openAI.js +76 -276
  132. package/dist/sdk/tool-extension.d.ts +181 -0
  133. package/dist/sdk/tool-extension.js +283 -0
  134. package/dist/sdk/tool-registration.d.ts +95 -0
  135. package/dist/sdk/tool-registration.js +168 -0
  136. package/dist/types/generate-types.d.ts +1 -0
  137. package/dist/types/mcp-types.d.ts +116 -0
  138. package/dist/types/mcp-types.js +5 -0
  139. package/dist/types/stream-types.d.ts +30 -18
  140. package/dist/types/universal-provider-options.d.ts +87 -0
  141. package/dist/types/universal-provider-options.js +53 -0
  142. package/package.json +15 -10
  143. package/dist/lib/providers/agent-enhanced-provider.d.ts +0 -93
  144. package/dist/lib/providers/agent-enhanced-provider.js +0 -605
  145. package/dist/lib/providers/amazonBedrock.d.ts +0 -28
  146. package/dist/lib/providers/amazonBedrock.js +0 -364
  147. package/dist/lib/providers/azureOpenAI.d.ts +0 -42
  148. package/dist/lib/providers/azureOpenAI.js +0 -347
  149. package/dist/lib/providers/googleAIStudio.d.ts +0 -42
  150. package/dist/lib/providers/googleAIStudio.js +0 -364
  151. package/dist/lib/providers/googleVertexAI.d.ts +0 -34
  152. package/dist/lib/providers/googleVertexAI.js +0 -547
  153. package/dist/lib/providers/mistralAI.d.ts +0 -37
  154. package/dist/lib/providers/mistralAI.js +0 -325
  155. package/dist/providers/agent-enhanced-provider.d.ts +0 -93
  156. package/dist/providers/agent-enhanced-provider.js +0 -606
  157. package/dist/providers/amazonBedrock.d.ts +0 -28
  158. package/dist/providers/amazonBedrock.js +0 -364
  159. package/dist/providers/azureOpenAI.d.ts +0 -42
  160. package/dist/providers/azureOpenAI.js +0 -348
  161. package/dist/providers/googleAIStudio.d.ts +0 -42
  162. package/dist/providers/googleAIStudio.js +0 -364
  163. package/dist/providers/googleVertexAI.d.ts +0 -34
  164. package/dist/providers/googleVertexAI.js +0 -547
  165. package/dist/providers/mistralAI.d.ts +0 -37
  166. package/dist/providers/mistralAI.js +0 -325
@@ -1,23 +1,39 @@
1
1
  /**
2
2
  * NeuroLink - Unified AI Interface with Real MCP Tool Integration
3
3
  *
4
+ * REDESIGNED FALLBACK CHAIN - NO CIRCULAR DEPENDENCIES
4
5
  * Enhanced AI provider system with natural MCP tool access.
5
6
  * Uses real MCP infrastructure for tool discovery and execution.
6
7
  */
7
- import { AIProviderFactory } from "./index.js";
8
+ // Load environment variables from .env file (critical for SDK usage)
9
+ import { config as dotenvConfig } from "dotenv";
10
+ try {
11
+ dotenvConfig(); // Load .env from current working directory
12
+ }
13
+ catch (error) {
14
+ // Environment variables should be set externally in production
15
+ }
16
+ import { AIProviderFactory } from "./core/factory.js";
8
17
  import { ContextManager } from "./mcp/context-manager.js";
9
18
  import { mcpLogger } from "./mcp/logging.js";
10
19
  import { toolRegistry } from "./mcp/tool-registry.js";
11
20
  import { unifiedRegistry } from "./mcp/unified-registry.js";
12
21
  import { logger } from "./utils/logger.js";
13
22
  import { getBestProvider } from "./utils/providerUtils-fixed.js";
14
- import { TimeoutError } from "./utils/timeout.js";
15
- import { CompatibilityConversionFactory } from "./factories/compatibility-factory.js";
23
+ import { ProviderRegistry } from "./factories/provider-registry.js";
24
+ import { validateTool, createMCPServerFromTools, } from "./sdk/tool-registration.js";
16
25
  // Core types imported from core/types.js
17
26
  export class NeuroLink {
18
27
  mcpInitialized = false;
19
28
  contextManager;
29
+ // Tool registration support
30
+ customTools = new Map();
31
+ inMemoryServers = new Map();
20
32
  constructor() {
33
+ // SDK always disables manual MCP config for security
34
+ ProviderRegistry.setOptions({
35
+ enableManualMCP: false,
36
+ });
21
37
  this.contextManager = new ContextManager();
22
38
  }
23
39
  /**
@@ -30,75 +46,59 @@ export class NeuroLink {
30
46
  }
31
47
  try {
32
48
  mcpLogger.debug("[NeuroLink] Starting isolated MCP initialization...");
33
- // Use Promise.race with aggressive timeout and isolated context
34
- const initTimeout = 3000; // 3 seconds max (reduced from 5)
35
- const mcpInitPromise = Promise.race([
36
- this.doIsolatedMCPInitialization(),
49
+ // Initialize tool registry with timeout protection
50
+ const initTimeout = 3000; // 3 second timeout
51
+ await Promise.race([
52
+ Promise.resolve(), // toolRegistry doesn't need explicit initialization
37
53
  new Promise((_, reject) => {
38
- setTimeout(() => {
39
- reject(new Error("MCP initialization timeout after 3s"));
40
- }, initTimeout);
54
+ setTimeout(() => reject(new Error("MCP initialization timeout")), initTimeout);
41
55
  }),
42
56
  ]);
43
- await mcpInitPromise;
44
- this.mcpInitialized = true;
45
- mcpLogger.debug("[NeuroLink] MCP tool integration initialized successfully");
46
- }
47
- catch (error) {
48
- mcpLogger.warn("[NeuroLink] MCP initialization failed, continuing without tools:", error);
49
- // Mark as initialized to prevent infinite retries
57
+ // Register all providers with lazy loading support
58
+ await ProviderRegistry.registerAllProviders();
50
59
  this.mcpInitialized = true;
51
- }
52
- }
53
- /**
54
- * Isolated MCP initialization to prevent context-dependent hanging
55
- */
56
- async doIsolatedMCPInitialization() {
57
- try {
58
- // Initialize only the essential built-in tools without complex registry
59
- mcpLogger.debug("[NeuroLink] Initializing essential MCP tools...");
60
- // Use dynamic import in isolated context to avoid circular dependencies
61
- const { initializeNeuroLinkMCP, isNeuroLinkMCPInitialized } = await import("./mcp/initialize.js");
62
- // Only initialize if not already done
63
- if (!isNeuroLinkMCPInitialized()) {
64
- await initializeNeuroLinkMCP();
65
- }
66
- mcpLogger.debug("[NeuroLink] Essential MCP tools initialized successfully");
60
+ mcpLogger.debug("[NeuroLink] MCP initialization completed successfully");
67
61
  }
68
62
  catch (error) {
69
- mcpLogger.warn("[NeuroLink] Isolated MCP initialization failed:", error);
70
- throw error;
63
+ mcpLogger.warn("[NeuroLink] MCP initialization failed", {
64
+ error: error instanceof Error ? error.message : String(error),
65
+ });
66
+ // Continue without MCP - graceful degradation
71
67
  }
72
68
  }
73
69
  /**
74
- * PRIMARY METHOD: Generate content using AI (recommended for new code)
75
- * Future-ready for multi-modal capabilities with current text focus
70
+ * MAIN ENTRY POINT: Enhanced generate method with new function signature
71
+ * Replaces both generateText and legacy methods
76
72
  */
77
- async generate(options) {
78
- // Validate input
79
- if (!options?.input?.text ||
80
- typeof options.input.text !== "string" ||
81
- options.input.text.trim() === "") {
82
- throw new Error("Generate options must include input.text as a non-empty string");
73
+ async generate(optionsOrPrompt) {
74
+ const startTime = Date.now();
75
+ // Convert string prompt to full options
76
+ const options = typeof optionsOrPrompt === "string"
77
+ ? { input: { text: optionsOrPrompt } }
78
+ : optionsOrPrompt;
79
+ // Validate prompt
80
+ if (!options.input?.text || typeof options.input.text !== "string") {
81
+ throw new Error("Input text is required and must be a non-empty string");
83
82
  }
84
- // Convert input format and extract text
85
- const prompt = options.input.text;
86
- const convertedOptions = {
87
- prompt,
83
+ // Convert to internal TextGenerationOptions for compatibility
84
+ const textOptions = {
85
+ prompt: options.input.text,
88
86
  provider: options.provider,
89
87
  model: options.model,
90
88
  temperature: options.temperature,
91
89
  maxTokens: options.maxTokens,
92
90
  systemPrompt: options.systemPrompt,
93
- timeout: options.timeout,
91
+ disableTools: options.disableTools, // FIX: Pass disableTools flag
92
+ // 🔧 FIX: Include analytics and evaluation options!
94
93
  enableAnalytics: options.enableAnalytics,
95
94
  enableEvaluation: options.enableEvaluation,
96
95
  context: options.context,
96
+ evaluationDomain: options.evaluationDomain,
97
+ toolUsageContext: options.toolUsageContext,
97
98
  };
98
- // Use existing generation infrastructure directly
99
- // For now, always use generateWithTools for full functionality
100
- const textResult = await this.generateWithTools(convertedOptions);
101
- // Convert to GenerateResult format
99
+ // Use redesigned generation logic
100
+ const textResult = await this.generateTextInternal(textOptions);
101
+ // Convert back to GenerateResult
102
102
  const generateResult = {
103
103
  content: textResult.content,
104
104
  provider: textResult.provider,
@@ -140,73 +140,77 @@ export class NeuroLink {
140
140
  options.prompt.trim() === "") {
141
141
  throw new Error("GenerateText options must include prompt as a non-empty string");
142
142
  }
143
- // Convert TextGenerationOptions to GenerateOptions
144
- const generateOptions = CompatibilityConversionFactory.convertTextToGenerate(options);
143
+ // Use internal generation method directly
144
+ return await this.generateTextInternal(options);
145
+ }
146
+ /**
147
+ * REDESIGNED INTERNAL GENERATION - NO CIRCULAR DEPENDENCIES
148
+ *
149
+ * This method implements a clean fallback chain:
150
+ * 1. Try MCP-enhanced generation if available
151
+ * 2. Fall back to direct provider generation
152
+ * 3. No recursive calls - each method has a specific purpose
153
+ */
154
+ async generateTextInternal(options) {
155
+ const startTime = Date.now();
156
+ const functionTag = "NeuroLink.generateTextInternal";
157
+ logger.debug(`[${functionTag}] Starting generation`, {
158
+ provider: options.provider || "auto",
159
+ promptLength: options.prompt?.length || 0,
160
+ });
145
161
  try {
146
- // Use internal generate method for identical performance and behavior
147
- const generateResult = await this.generate(generateOptions);
148
- // Convert GenerateResult back to TextGenerationResult format for backward compatibility
149
- const textResult = {
150
- content: generateResult.content,
151
- provider: generateResult.provider,
152
- model: generateResult.model,
153
- usage: generateResult.usage
154
- ? {
155
- promptTokens: generateResult.usage?.inputTokens || 0,
156
- completionTokens: generateResult.usage?.outputTokens || 0,
157
- totalTokens: generateResult.usage?.totalTokens || 0,
162
+ // Try MCP-enhanced generation first (if not explicitly disabled)
163
+ if (!options.disableTools) {
164
+ try {
165
+ const mcpResult = await this.tryMCPGeneration(options);
166
+ if (mcpResult && mcpResult.content) {
167
+ logger.debug(`[${functionTag}] MCP generation successful`);
168
+ return mcpResult;
158
169
  }
159
- : undefined,
160
- responseTime: generateResult.responseTime,
161
- toolsUsed: generateResult.toolsUsed,
162
- toolExecutions: generateResult.toolExecutions?.map((te) => ({
163
- toolName: te.name || te.toolName || "",
164
- executionTime: te.duration || te.executionTime || 0,
165
- success: true, // Assume success if execution completed
166
- serverId: te.serverId,
167
- })),
168
- enhancedWithTools: generateResult.enhancedWithTools,
169
- availableTools: generateResult.availableTools?.map((tool) => ({
170
- name: tool.name || "",
171
- description: tool.description || "",
172
- server: tool.server || "unknown",
173
- category: tool.category,
174
- })),
175
- };
176
- return textResult;
170
+ }
171
+ catch (error) {
172
+ logger.debug(`[${functionTag}] MCP generation failed, falling back`, {
173
+ error: error instanceof Error ? error.message : String(error),
174
+ });
175
+ }
176
+ }
177
+ // Fall back to direct provider generation
178
+ const directResult = await this.directProviderGeneration(options);
179
+ logger.debug(`[${functionTag}] Direct generation successful`);
180
+ return directResult;
177
181
  }
178
182
  catch (error) {
179
- throw new Error(`GenerateText compatibility method failed: ${error}`);
183
+ logger.error(`[${functionTag}] All generation methods failed`, {
184
+ error: error instanceof Error ? error.message : String(error),
185
+ });
186
+ throw error;
180
187
  }
181
188
  }
182
189
  /**
183
- * Generate text with real MCP tool integration using automatic detection
190
+ * Try MCP-enhanced generation (no fallback recursion)
184
191
  */
185
- async generateWithTools(options) {
186
- const startTime = Date.now();
187
- const functionTag = "NeuroLink.generateWithTools";
188
- // Initialize MCP if needed
189
- await this.initializeMCP();
190
- // Create execution context for tool operations
191
- const context = this.contextManager.createContext({
192
- sessionId: `neurolink-${Date.now()}`,
193
- userId: "neurolink-user",
194
- aiProvider: options.provider || "auto",
195
- });
196
- // Determine provider to use
197
- const providerName = options.provider === "auto" || !options.provider
198
- ? await getBestProvider()
199
- : options.provider;
192
+ async tryMCPGeneration(options) {
193
+ const functionTag = "NeuroLink.tryMCPGeneration";
194
+ const startTime = Date.now(); // 🔧 FIX: Add proper timing
200
195
  try {
201
- mcpLogger.debug(`[${functionTag}] Starting MCP-enabled generation`, {
202
- provider: providerName,
203
- prompt: (options.prompt?.substring(0, 100) || "No prompt") + "...",
204
- contextId: context.sessionId,
196
+ // Initialize MCP if needed
197
+ await this.initializeMCP();
198
+ if (!this.mcpInitialized) {
199
+ return null; // Skip MCP if not available
200
+ }
201
+ // Create execution context
202
+ const context = this.contextManager.createContext({
203
+ sessionId: `neurolink-${Date.now()}`,
204
+ userId: "neurolink-user",
205
+ aiProvider: options.provider || "auto",
205
206
  });
206
- // Get available tools from tool registry (simplified approach)
207
+ // Determine provider
208
+ const providerName = options.provider === "auto" || !options.provider
209
+ ? await getBestProvider()
210
+ : options.provider;
211
+ // Get available tools
207
212
  let availableTools = [];
208
213
  try {
209
- // Use toolRegistry directly instead of unified registry to avoid hanging
210
214
  const allTools = await toolRegistry.listTools();
211
215
  availableTools = allTools.map((tool) => ({
212
216
  name: tool.name,
@@ -214,85 +218,52 @@ export class NeuroLink {
214
218
  server: tool.server,
215
219
  category: tool.category,
216
220
  }));
217
- mcpLogger.debug(`[${functionTag}] Found ${availableTools.length} available tools from default registry`, {
218
- tools: availableTools.map((t) => t.name),
219
- });
220
221
  }
221
222
  catch (error) {
222
- mcpLogger.warn(`[${functionTag}] Failed to get available tools`, {
223
- error,
224
- });
223
+ mcpLogger.warn(`[${functionTag}] Failed to get tools`, { error });
225
224
  }
226
225
  // Create tool-aware system prompt
227
226
  const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
228
- // Create provider with MCP enabled using best provider function
229
- const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true);
230
- // Generate text with automatic tool detection
227
+ // Create provider and generate
228
+ const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP
229
+ this);
231
230
  const result = await provider.generate({
232
- prompt: options.prompt,
233
- temperature: options.temperature,
234
- maxTokens: options.maxTokens,
231
+ ...options,
235
232
  systemPrompt: enhancedSystemPrompt,
236
- timeout: options.timeout,
237
- // NEW: Pass enhancement options
238
- enableAnalytics: options.enableAnalytics,
239
- enableEvaluation: options.enableEvaluation,
240
- context: options.context,
241
- // NEW: Lighthouse-compatible domain-aware evaluation
242
- evaluationDomain: options.evaluationDomain,
243
- toolUsageContext: options.toolUsageContext,
244
- conversationHistory: options.conversationHistory,
245
- }, options.schema);
246
- if (!result) {
247
- throw new Error("No response received from AI provider");
248
- }
249
- const responseTime = Date.now() - startTime;
250
- // Extract MCP metadata if available
251
- const metadata = result.metadata || {};
252
- mcpLogger.debug(`[${functionTag}] MCP-enabled generation completed`, {
253
- responseTime,
254
- toolsUsed: metadata.toolsUsed || [],
255
- enhancedWithTools: metadata.enhancedWithTools || false,
256
- availableToolsCount: availableTools.length,
257
233
  });
258
- // Check if we actually got content
259
- if (!result.content || result.content.trim() === "") {
260
- mcpLogger.warn(`[${functionTag}] Empty response from provider, attempting fallback`, {
261
- provider: providerName,
262
- hasText: !!result.content,
263
- textLength: result.content?.length || 0,
264
- });
265
- // Fall back to regular generation if MCP generation returns empty
266
- return this.generateRegular(options);
234
+ const responseTime = Date.now() - startTime; // 🔧 FIX: Proper timing calculation
235
+ // Check if result is meaningful
236
+ if (!result || !result.content || result.content.trim().length === 0) {
237
+ return null; // Let caller fall back to direct generation
267
238
  }
239
+ // Return enhanced result
268
240
  return {
269
241
  content: result.content,
270
242
  provider: providerName,
271
243
  usage: result.usage,
272
244
  responseTime,
273
- toolsUsed: metadata.toolsUsed || [],
274
- enhancedWithTools: metadata.enhancedWithTools || false,
245
+ toolsUsed: [],
246
+ enhancedWithTools: true,
275
247
  availableTools: availableTools.length > 0 ? availableTools : undefined,
276
- // NEW: Preserve enhancement data from provider
277
- ...(result.analytics && { analytics: result.analytics }),
278
- ...(result.evaluation && { evaluation: result.evaluation }),
248
+ // 🔧 FIX: Include analytics and evaluation from BaseProvider
249
+ analytics: result.analytics,
250
+ evaluation: result.evaluation,
279
251
  };
280
252
  }
281
253
  catch (error) {
282
- // Fall back to regular generation if MCP fails
283
- mcpLogger.warn(`[${functionTag}] MCP generation failed, falling back to regular`, {
254
+ mcpLogger.warn(`[${functionTag}] MCP generation failed`, {
284
255
  error: error instanceof Error ? error.message : String(error),
285
256
  });
286
- return this.generateRegular(options);
257
+ return null; // Let caller fall back
287
258
  }
288
259
  }
289
260
  /**
290
- * Regular text generation (existing logic)
261
+ * Direct provider generation (no MCP, no recursion)
291
262
  */
292
- async generateRegular(options) {
263
+ async directProviderGeneration(options) {
293
264
  const startTime = Date.now();
294
- const functionTag = "NeuroLink.generateRegular";
295
- // Define fallback provider priority order
265
+ const functionTag = "NeuroLink.directProviderGeneration";
266
+ // Define provider priority for fallback
296
267
  const providerPriority = [
297
268
  "openai",
298
269
  "vertex",
@@ -306,97 +277,55 @@ export class NeuroLink {
306
277
  const requestedProvider = options.provider === "auto" ? undefined : options.provider;
307
278
  // If specific provider requested, only use that provider (no fallback)
308
279
  const tryProviders = requestedProvider
309
- ? [requestedProvider] // Only use the requested provider, no fallback
280
+ ? [requestedProvider]
310
281
  : providerPriority;
311
- logger.debug(`[${functionTag}] Starting text generation`, {
282
+ logger.debug(`[${functionTag}] Starting direct generation`, {
312
283
  requestedProvider: requestedProvider || "auto",
313
284
  tryProviders,
314
285
  allowFallback: !requestedProvider,
315
- promptLength: options.prompt?.length || 0,
316
286
  });
317
287
  let lastError = null;
288
+ // Try each provider in order
318
289
  for (const providerName of tryProviders) {
319
290
  try {
320
- logger.debug(`[${functionTag}] Attempting provider`, {
321
- provider: providerName,
322
- });
323
- const provider = await AIProviderFactory.createProvider(providerName, options.model, false);
324
- const result = await provider.generate({
325
- prompt: options.prompt,
326
- model: options.model,
327
- temperature: options.temperature,
328
- maxTokens: options.maxTokens,
329
- systemPrompt: options.systemPrompt,
330
- timeout: options.timeout,
331
- // NEW: Pass enhancement options
332
- enableAnalytics: options.enableAnalytics,
333
- enableEvaluation: options.enableEvaluation,
334
- context: options.context,
335
- // NEW: Lighthouse-compatible domain-aware evaluation
336
- evaluationDomain: options.evaluationDomain,
337
- toolUsageContext: options.toolUsageContext,
338
- conversationHistory: options.conversationHistory,
339
- }, options.schema);
291
+ logger.debug(`[${functionTag}] Attempting provider: ${providerName}`);
292
+ const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP
293
+ this);
294
+ const result = await provider.generate(options);
295
+ const responseTime = Date.now() - startTime;
340
296
  if (!result) {
341
- throw new Error("No response received from AI provider");
342
- }
343
- // Check if we actually got content
344
- if (!result.content || result.content.trim() === "") {
345
- logger.warn(`[${functionTag}] Empty response from provider`, {
346
- provider: providerName,
347
- hasText: !!result.content,
348
- textLength: result.content?.length || 0,
349
- });
350
- // Continue to next provider if available
351
- throw new Error(`Empty response from ${providerName}`);
297
+ throw new Error(`Provider ${providerName} returned null result`);
352
298
  }
353
- const responseTime = Date.now() - startTime;
354
- logger.debug(`[${functionTag}] Provider succeeded`, {
355
- provider: providerName,
299
+ logger.debug(`[${functionTag}] Provider ${providerName} succeeded`, {
356
300
  responseTime,
357
- usage: result.usage,
301
+ contentLength: result.content?.length || 0,
358
302
  });
359
303
  return {
360
- content: result.content,
304
+ content: result.content || "",
361
305
  provider: providerName,
362
- usage: result.usage
363
- ? {
364
- promptTokens: result.usage.inputTokens || 0,
365
- completionTokens: result.usage.outputTokens || 0,
366
- totalTokens: result.usage.totalTokens || 0,
367
- }
368
- : undefined,
306
+ model: result.model,
307
+ usage: result.usage,
369
308
  responseTime,
370
- // NEW: Preserve enhancement data from provider
371
- ...(result.analytics && { analytics: result.analytics }),
372
- ...(result.evaluation && { evaluation: result.evaluation }),
309
+ toolsUsed: [],
310
+ enhancedWithTools: false,
311
+ analytics: result.analytics,
312
+ evaluation: result.evaluation,
373
313
  };
374
314
  }
375
315
  catch (error) {
376
- const errorMessage = error instanceof Error ? error.message : String(error);
377
- lastError = error instanceof Error ? error : new Error(errorMessage);
378
- // Special handling for timeout errors
379
- if (error instanceof TimeoutError) {
380
- logger.warn(`[${functionTag}] Provider timed out`, {
381
- provider: providerName,
382
- timeout: error.timeout,
383
- operation: error.operation,
384
- });
385
- }
386
- logger.debug(`[${functionTag}] Provider failed, trying next`, {
387
- provider: providerName,
388
- error: errorMessage,
389
- isTimeout: error instanceof TimeoutError,
390
- remainingProviders: tryProviders.slice(tryProviders.indexOf(providerName) + 1),
316
+ lastError = error instanceof Error ? error : new Error(String(error));
317
+ logger.warn(`[${functionTag}] Provider ${providerName} failed`, {
318
+ error: lastError.message,
391
319
  });
392
320
  // Continue to next provider
393
- continue;
394
321
  }
395
322
  }
396
323
  // All providers failed
397
- logger.debug(`[${functionTag}] All providers failed`, {
324
+ const responseTime = Date.now() - startTime;
325
+ logger.error(`[${functionTag}] All providers failed`, {
398
326
  triedProviders: tryProviders,
399
327
  lastError: lastError?.message,
328
+ responseTime,
400
329
  });
401
330
  throw new Error(`Failed to generate text with all providers. Last error: ${lastError?.message || "Unknown error"}`);
402
331
  }
@@ -404,25 +333,34 @@ export class NeuroLink {
404
333
  * Create tool-aware system prompt that informs AI about available tools
405
334
  */
406
335
  createToolAwareSystemPrompt(originalSystemPrompt, availableTools) {
407
- const basePrompt = originalSystemPrompt || "You are a helpful AI assistant.";
408
336
  if (availableTools.length === 0) {
409
- return basePrompt;
337
+ return originalSystemPrompt || "";
410
338
  }
411
339
  const toolDescriptions = availableTools
412
- .map((tool) => `- ${tool.name}: ${tool.description}`)
340
+ .map((tool) => `- ${tool.name}: ${tool.description} (from ${tool.server})`)
413
341
  .join("\n");
414
- return `${basePrompt}
415
-
416
- Available tools that can be used when relevant:
417
-
418
- ${toolDescriptions}
419
-
420
- You can mention these capabilities when they're relevant to user questions. For example:
421
- - For time questions: "I can get the current time"
422
- - For provider questions: "I can check AI provider status"
423
- - For tool questions: "I can list available tools"
424
-
425
- Note: Tool integration is currently in development. Please provide helpful responses based on your knowledge while mentioning tool capabilities when relevant.`;
342
+ const toolPrompt = `\n\nAvailable Tools:\n${toolDescriptions}\n\nYou can use these tools when appropriate to enhance your responses.`;
343
+ return (originalSystemPrompt || "") + toolPrompt;
344
+ }
345
+ /**
346
+ * BACKWARD COMPATIBILITY: Legacy streamText method
347
+ * Internally calls stream() and converts result format
348
+ */
349
+ async streamText(prompt, options) {
350
+ // Convert legacy format to new StreamOptions
351
+ const streamOptions = {
352
+ input: { text: prompt },
353
+ ...options,
354
+ };
355
+ // Call the new stream method
356
+ const result = await this.stream(streamOptions);
357
+ // Convert StreamResult to simple string async iterable
358
+ async function* stringStream() {
359
+ for await (const chunk of result.stream) {
360
+ yield chunk.content;
361
+ }
362
+ }
363
+ return stringStream();
426
364
  }
427
365
  /**
428
366
  * PRIMARY METHOD: Stream content using AI (recommended for new code)
@@ -456,7 +394,7 @@ Note: Tool integration is currently in development. Please provide helpful respo
456
394
  contextId: context.sessionId,
457
395
  });
458
396
  // Create provider using the same factory pattern as generate
459
- const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true);
397
+ const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true, this);
460
398
  // Call the provider's stream method directly
461
399
  const streamResult = await provider.stream(options);
462
400
  // Extract the stream from the result
@@ -482,71 +420,357 @@ Note: Tool integration is currently in development. Please provide helpful respo
482
420
  mcpLogger.warn(`[${functionTag}] MCP streaming failed, falling back to regular`, {
483
421
  error: error instanceof Error ? error.message : String(error),
484
422
  });
485
- // Create provider and use regular streaming
486
- const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true);
487
- // Call the provider's stream method directly
423
+ // Use factory to create provider without MCP
424
+ const provider = await AIProviderFactory.createBestProvider(providerName, options.model, false, // Disable MCP for fallback
425
+ this);
488
426
  const streamResult = await provider.stream(options);
489
- // Extract the stream from the result
490
- const stream = streamResult.stream;
427
+ const responseTime = Date.now() - startTime;
491
428
  return {
492
- stream,
429
+ stream: streamResult.stream,
493
430
  provider: providerName,
494
431
  model: options.model,
495
432
  metadata: {
496
- streamId: `neurolink-fallback-${Date.now()}`,
433
+ streamId: `neurolink-${Date.now()}`,
497
434
  startTime,
435
+ responseTime,
436
+ fallback: true,
498
437
  },
499
438
  };
500
439
  }
501
440
  }
441
+ // ========================================
442
+ // Tool Registration API
443
+ // ========================================
502
444
  /**
503
- * BACKWARD COMPATIBILITY: Legacy streamText method
504
- * Internally calls stream() and converts result format
445
+ * Register a custom tool that will be available to all AI providers
446
+ * @param name - Unique name for the tool
447
+ * @param tool - Tool configuration
505
448
  */
506
- async streamText(options) {
507
- // Validate required parameters for backward compatibility
508
- if (!options.prompt ||
509
- typeof options.prompt !== "string" ||
510
- options.prompt.trim() === "") {
511
- throw new Error("StreamText options must include prompt as a non-empty string");
449
+ registerTool(name, tool) {
450
+ try {
451
+ // Validate tool configuration
452
+ validateTool(name, tool);
453
+ // Store the tool
454
+ this.customTools.set(name, tool);
455
+ // Convert to MCP server format for integration
456
+ const serverId = `custom-tool-${name}`;
457
+ const mcpServer = createMCPServerFromTools(serverId, { [name]: tool }, {
458
+ title: `Custom Tool: ${name}`,
459
+ category: "custom",
460
+ });
461
+ // Store as in-memory server
462
+ this.inMemoryServers.set(serverId, mcpServer);
463
+ logger.info(`Registered custom tool: ${name}`);
512
464
  }
513
- // Convert legacy options to StreamOptions
514
- const streamOptions = {
515
- input: { text: options.prompt },
516
- provider: options.provider,
517
- model: options.model,
518
- temperature: options.temperature,
519
- maxTokens: options.maxTokens,
520
- systemPrompt: options.systemPrompt,
521
- timeout: options.timeout,
522
- };
465
+ catch (error) {
466
+ logger.error(`Failed to register tool ${name}:`, error);
467
+ throw error;
468
+ }
469
+ }
470
+ /**
471
+ * Register multiple tools at once
472
+ * @param tools - Object mapping tool names to configurations
473
+ */
474
+ registerTools(tools) {
475
+ for (const [name, tool] of Object.entries(tools)) {
476
+ this.registerTool(name, tool);
477
+ }
478
+ }
479
+ /**
480
+ * Unregister a custom tool
481
+ * @param name - Name of the tool to remove
482
+ * @returns true if the tool was removed, false if it didn't exist
483
+ */
484
+ unregisterTool(name) {
485
+ const removed = this.customTools.delete(name);
486
+ if (removed) {
487
+ const serverId = `custom-tool-${name}`;
488
+ this.inMemoryServers.delete(serverId);
489
+ logger.info(`Unregistered custom tool: ${name}`);
490
+ }
491
+ return removed;
492
+ }
493
+ /**
494
+ * Get all registered custom tools
495
+ * @returns Map of tool names to configurations
496
+ */
497
+ getCustomTools() {
498
+ return new Map(this.customTools);
499
+ }
500
+ /**
501
+ * Add an in-memory MCP server (from git diff)
502
+ * Allows registration of pre-instantiated server objects
503
+ * @param serverId - Unique identifier for the server
504
+ * @param config - Server configuration
505
+ */
506
+ async addInMemoryMCPServer(serverId, config) {
523
507
  try {
524
- // Use new stream method for identical performance and behavior
525
- const streamResult = await this.stream(streamOptions);
526
- // Return just the stream for backward compatibility
527
- return streamResult.stream;
508
+ mcpLogger.debug(`[NeuroLink] Registering in-memory MCP server: ${serverId}`);
509
+ // Validate server configuration
510
+ if (!config.server) {
511
+ throw new Error(`Server object is required for ${serverId}`);
512
+ }
513
+ // Store in registry
514
+ this.inMemoryServers.set(serverId, config);
515
+ mcpLogger.info(`[NeuroLink] Successfully registered in-memory server: ${serverId}`, {
516
+ category: config.category,
517
+ provider: config.metadata?.provider,
518
+ version: config.metadata?.version,
519
+ });
528
520
  }
529
521
  catch (error) {
530
- throw new Error(`StreamText compatibility method failed: ${error}`);
522
+ mcpLogger.error(`[NeuroLink] Failed to register in-memory server ${serverId}:`, error);
523
+ throw error;
531
524
  }
532
525
  }
533
526
  /**
534
- * Get the best available AI provider
527
+ * Get all registered in-memory servers
528
+ * @returns Map of server IDs to configurations
535
529
  */
536
- async getBestProvider() {
537
- return await getBestProvider();
530
+ getInMemoryServers() {
531
+ return new Map(this.inMemoryServers);
538
532
  }
539
533
  /**
540
- * Test a specific provider
534
+ * Execute a specific tool by name (from git diff)
535
+ * Supports both custom tools and MCP server tools
536
+ * @param toolName - Name of the tool to execute
537
+ * @param params - Parameters to pass to the tool
538
+ * @param options - Execution options
539
+ * @returns Tool execution result
541
540
  */
542
- async testProvider(providerName, testPrompt = "test") {
541
+ async executeTool(toolName, params = {}, options) {
542
+ const functionTag = "NeuroLink.executeTool";
543
543
  try {
544
- const provider = await AIProviderFactory.createProvider(providerName, null, false); // Disable MCP for simple testing
545
- await provider.generate({
546
- prompt: testPrompt,
547
- enableAnalytics: false,
548
- enableEvaluation: false,
544
+ mcpLogger.debug(`[${functionTag}] Executing tool: ${toolName}`, {
545
+ toolName,
546
+ params,
547
+ options,
549
548
  });
549
+ // First check custom tools
550
+ if (this.customTools.has(toolName)) {
551
+ const tool = this.customTools.get(toolName);
552
+ const context = {
553
+ sessionId: `direct-execution-${Date.now()}`,
554
+ logger,
555
+ };
556
+ const startTime = Date.now();
557
+ const result = await tool.execute(params, context);
558
+ const executionTime = Date.now() - startTime;
559
+ mcpLogger.debug(`[${functionTag}] Custom tool executed successfully`, {
560
+ toolName,
561
+ executionTime,
562
+ });
563
+ return result;
564
+ }
565
+ // Then check in-memory servers
566
+ for (const [serverId, serverConfig] of this.inMemoryServers.entries()) {
567
+ const server = serverConfig.server;
568
+ // Check if this server has the requested tool
569
+ if (server && server.tools) {
570
+ const tool = server.tools instanceof Map
571
+ ? server.tools.get(toolName)
572
+ : server.tools[toolName];
573
+ if (tool && typeof tool.execute === "function") {
574
+ mcpLogger.debug(`[${functionTag}] Found tool in in-memory server: ${serverId}`);
575
+ const startTime = Date.now();
576
+ try {
577
+ const result = await tool.execute(params);
578
+ const executionTime = Date.now() - startTime;
579
+ mcpLogger.debug(`[${functionTag}] Tool executed successfully`, {
580
+ toolName,
581
+ serverId,
582
+ executionTime,
583
+ });
584
+ // Handle MCP-style results
585
+ if (result && typeof result === "object" && "success" in result) {
586
+ if (result.success) {
587
+ return result.data;
588
+ }
589
+ else {
590
+ throw new Error(result.error || "Tool execution failed");
591
+ }
592
+ }
593
+ return result;
594
+ }
595
+ catch (toolError) {
596
+ mcpLogger.error(`[${functionTag}] Tool execution failed`, {
597
+ toolName,
598
+ serverId,
599
+ error: toolError instanceof Error
600
+ ? toolError.message
601
+ : String(toolError),
602
+ });
603
+ throw toolError;
604
+ }
605
+ }
606
+ }
607
+ }
608
+ // If not found in custom tools or in-memory servers, try unified registry
609
+ try {
610
+ // Ensure built-in tools are initialized
611
+ const { initializeNeuroLinkMCP, isNeuroLinkMCPInitialized } = await import("./mcp/initialize.js");
612
+ if (!isNeuroLinkMCPInitialized()) {
613
+ mcpLogger.debug(`[${functionTag}] Initializing built-in MCP servers`);
614
+ await initializeNeuroLinkMCP();
615
+ }
616
+ // Create minimal execution context for external tools
617
+ const context = this.contextManager.createContext({
618
+ sessionId: `neurolink-tool-${Date.now()}`,
619
+ userId: "neurolink-user",
620
+ aiProvider: "auto",
621
+ });
622
+ const result = (await unifiedRegistry.executeTool(toolName, params, context));
623
+ return result;
624
+ }
625
+ catch (error) {
626
+ mcpLogger.warn(`[${functionTag}] External tool execution failed`, {
627
+ toolName,
628
+ error: error instanceof Error ? error.message : String(error),
629
+ });
630
+ throw error;
631
+ }
632
+ }
633
+ catch (error) {
634
+ const errorMessage = error instanceof Error ? error.message : String(error);
635
+ mcpLogger.error(`[${functionTag}] Tool execution failed: ${errorMessage}`, {
636
+ toolName,
637
+ error: errorMessage,
638
+ });
639
+ throw new Error(`Failed to execute tool '${toolName}': ${errorMessage}`);
640
+ }
641
+ }
642
+ /**
643
+ * Get all available tools including custom and in-memory ones
644
+ * @returns Array of available tools with metadata
645
+ */
646
+ async getAllAvailableTools() {
647
+ const { getAllAvailableTools } = await import("./mcp/initialize-tools.js");
648
+ return getAllAvailableTools(this.inMemoryServers);
649
+ }
650
+ // ============================================================================
651
+ // PROVIDER DIAGNOSTICS - SDK-First Architecture
652
+ // ============================================================================
653
+ /**
654
+ * Get comprehensive status of all AI providers
655
+ * Primary method for provider health checking and diagnostics
656
+ */
657
+ async getProviderStatus() {
658
+ const { AIProviderFactory } = await import("./core/factory.js");
659
+ const { hasProviderEnvVars } = await import("./utils/providerUtils.js");
660
+ const providers = [
661
+ "openai",
662
+ "bedrock",
663
+ "vertex",
664
+ "google-vertex",
665
+ "anthropic",
666
+ "azure",
667
+ "google-ai",
668
+ "huggingface",
669
+ "ollama",
670
+ "mistral",
671
+ ];
672
+ const results = [];
673
+ for (const providerName of providers) {
674
+ const startTime = Date.now();
675
+ // Check if provider has required environment variables
676
+ const hasEnvVars = await this.hasProviderEnvVars(providerName);
677
+ if (!hasEnvVars && providerName !== "ollama") {
678
+ results.push({
679
+ provider: providerName,
680
+ status: "not-configured",
681
+ configured: false,
682
+ authenticated: false,
683
+ error: "Missing required environment variables",
684
+ responseTime: 0,
685
+ });
686
+ continue;
687
+ }
688
+ // Special handling for Ollama
689
+ if (providerName === "ollama") {
690
+ try {
691
+ const response = await fetch("http://localhost:11434/api/tags", {
692
+ method: "GET",
693
+ signal: AbortSignal.timeout(2000),
694
+ });
695
+ if (!response.ok) {
696
+ throw new Error("Ollama service not responding");
697
+ }
698
+ const { models } = await response.json();
699
+ const defaultOllamaModel = "llama3.2:latest";
700
+ const modelIsAvailable = models.some((m) => m.name === defaultOllamaModel);
701
+ if (modelIsAvailable) {
702
+ results.push({
703
+ provider: providerName,
704
+ status: "working",
705
+ configured: true,
706
+ authenticated: true,
707
+ responseTime: Date.now() - startTime,
708
+ model: defaultOllamaModel,
709
+ });
710
+ }
711
+ else {
712
+ results.push({
713
+ provider: providerName,
714
+ status: "failed",
715
+ configured: true,
716
+ authenticated: false,
717
+ error: `Ollama service running but model '${defaultOllamaModel}' not found`,
718
+ responseTime: Date.now() - startTime,
719
+ });
720
+ }
721
+ }
722
+ catch (error) {
723
+ results.push({
724
+ provider: providerName,
725
+ status: "failed",
726
+ configured: false,
727
+ authenticated: false,
728
+ error: error instanceof Error
729
+ ? error.message
730
+ : "Ollama service not running",
731
+ responseTime: Date.now() - startTime,
732
+ });
733
+ }
734
+ continue;
735
+ }
736
+ // Test other providers with actual generation call
737
+ try {
738
+ const testTimeout = 5000;
739
+ const testPromise = this.testProviderConnection(providerName);
740
+ const timeoutPromise = new Promise((_, reject) => {
741
+ setTimeout(() => reject(new Error("Provider test timeout (5s)")), testTimeout);
742
+ });
743
+ await Promise.race([testPromise, timeoutPromise]);
744
+ results.push({
745
+ provider: providerName,
746
+ status: "working",
747
+ configured: true,
748
+ authenticated: true,
749
+ responseTime: Date.now() - startTime,
750
+ });
751
+ }
752
+ catch (error) {
753
+ const errorMessage = error instanceof Error ? error.message : String(error);
754
+ results.push({
755
+ provider: providerName,
756
+ status: "failed",
757
+ configured: true,
758
+ authenticated: false,
759
+ error: errorMessage,
760
+ responseTime: Date.now() - startTime,
761
+ });
762
+ }
763
+ }
764
+ return results;
765
+ }
766
+ /**
767
+ * Test a specific AI provider's connectivity and authentication
768
+ * @param providerName - Name of the provider to test
769
+ * @returns Promise resolving to true if provider is working
770
+ */
771
+ async testProvider(providerName) {
772
+ try {
773
+ await this.testProviderConnection(providerName);
550
774
  return true;
551
775
  }
552
776
  catch {
@@ -554,145 +778,136 @@ Note: Tool integration is currently in development. Please provide helpful respo
554
778
  }
555
779
  }
556
780
  /**
557
- * Get access to the unified MCP registry for tool inspection and management
781
+ * Internal method to test provider connection with minimal generation call
558
782
  */
559
- getUnifiedRegistry() {
560
- return unifiedRegistry;
783
+ async testProviderConnection(providerName) {
784
+ const { AIProviderFactory } = await import("./core/factory.js");
785
+ const provider = await AIProviderFactory.createProvider(providerName, null, false);
786
+ await provider.generate({
787
+ prompt: "test",
788
+ maxTokens: 1,
789
+ disableTools: true,
790
+ });
561
791
  }
562
792
  /**
563
- * Initialize MCP and return discovery statistics
793
+ * Check if a provider has required environment variables configured
794
+ * @param providerName - Name of the provider to check
795
+ * @returns True if provider has required environment variables
564
796
  */
565
- async getMCPStatus() {
566
- await this.initializeMCP();
567
- const totalServers = unifiedRegistry.getTotalServerCount();
568
- const availableServers = unifiedRegistry.getAvailableServerCount();
569
- const autoDiscoveredServers = unifiedRegistry.getAutoDiscoveredServers();
570
- const allTools = await unifiedRegistry.listAllTools();
571
- return {
572
- mcpInitialized: this.mcpInitialized,
573
- totalServers,
574
- availableServers,
575
- autoDiscoveredCount: autoDiscoveredServers.length,
576
- totalTools: allTools.length,
577
- autoDiscoveredServers: autoDiscoveredServers.map((server) => ({
578
- id: server.metadata.name,
579
- name: server.metadata.name,
580
- source: server.source,
581
- status: "discovered",
582
- hasServer: true,
583
- })),
584
- };
797
+ async hasProviderEnvVars(providerName) {
798
+ const { hasProviderEnvVars } = await import("./utils/providerUtils.js");
799
+ return hasProviderEnvVars(providerName);
585
800
  }
586
801
  /**
587
- * Add a new MCP server programmatically
588
- *
589
- * Allows dynamic registration of MCP servers at runtime for enhanced
590
- * tool ecosystem management. Perfect for integrating external services
591
- * like Bitbucket, Slack, databases, etc.
592
- *
593
- * @param serverId - Unique identifier for the server (e.g., 'bitbucket', 'slack-api')
594
- * @param config - Server configuration with command and execution parameters
595
- * @returns Promise that resolves when server is successfully added and connected
596
- *
597
- * @example
598
- * ```typescript
599
- * // Add Bitbucket MCP server
600
- * await neurolink.addMCPServer('bitbucket', {
601
- * command: 'npx',
602
- * args: ['-y', '@nexus2520/bitbucket-mcp-server'],
603
- * env: {
604
- * BITBUCKET_USERNAME: 'your-username',
605
- * BITBUCKET_APP_PASSWORD: 'your-app-password'
606
- * }
607
- * });
608
- *
609
- * // Add custom database connector
610
- * await neurolink.addMCPServer('database', {
611
- * command: 'node',
612
- * args: ['./custom-db-mcp-server.js'],
613
- * env: { DB_CONNECTION_STRING: 'postgresql://...' }
614
- * });
615
- * ```
802
+ * Get the best available AI provider based on configuration and availability
803
+ * @param requestedProvider - Optional preferred provider name
804
+ * @returns Promise resolving to the best provider name
616
805
  */
617
- async addMCPServer(serverId, config) {
618
- const functionTag = "NeuroLink.addMCPServer";
619
- mcpLogger.info(`[${functionTag}] Adding MCP server: ${serverId}`, {
620
- command: config.command,
621
- argsCount: config.args?.length || 0,
622
- hasEnv: Object.keys(config.env || {}).length > 0,
623
- });
806
+ async getBestProvider(requestedProvider) {
807
+ const { getBestProvider } = await import("./utils/providerUtils.js");
808
+ return getBestProvider(requestedProvider);
809
+ }
810
+ /**
811
+ * Get list of all available AI provider names
812
+ * @returns Array of supported provider names
813
+ */
814
+ async getAvailableProviders() {
815
+ const { getAvailableProviders } = await import("./utils/providerUtils.js");
816
+ return getAvailableProviders();
817
+ }
818
+ /**
819
+ * Validate if a provider name is supported
820
+ * @param providerName - Provider name to validate
821
+ * @returns True if provider name is valid
822
+ */
823
+ async isValidProvider(providerName) {
824
+ const { isValidProvider } = await import("./utils/providerUtils.js");
825
+ return isValidProvider(providerName);
826
+ }
827
+ // ============================================================================
828
+ // MCP DIAGNOSTICS - SDK-First Architecture
829
+ // ============================================================================
830
+ /**
831
+ * Get comprehensive MCP (Model Context Protocol) status information
832
+ * @returns Promise resolving to MCP status details
833
+ */
834
+ async getMCPStatus() {
835
+ const { unifiedRegistry } = await import("./mcp/unified-registry.js");
624
836
  try {
625
- // Ensure MCP is initialized
626
- await this.initializeMCP();
627
- // Add server to unified registry with configurable transport type
628
- const transportType = config.type || "stdio";
629
- // Validate URL requirement for non-stdio transports
630
- if ((transportType === "sse" || transportType === "http") &&
631
- !config.url) {
632
- throw new Error(`URL is required for ${transportType} transport. Please provide config.url for server '${serverId}'.`);
633
- }
634
- const transportConfig = {
635
- type: transportType,
636
- ...(transportType === "stdio" && {
637
- command: config.command,
638
- args: config.args || [],
639
- env: config.env || {},
640
- cwd: config.cwd,
641
- }),
642
- ...(transportType === "sse" && {
643
- url: config.url,
644
- headers: config.headers,
645
- timeout: config.timeout,
646
- }),
647
- ...(transportType === "http" && {
648
- url: config.url,
649
- headers: config.headers,
650
- timeout: config.timeout,
651
- }),
837
+ const totalServers = unifiedRegistry.getTotalServerCount();
838
+ const availableServers = unifiedRegistry.getAvailableServerCount();
839
+ const autoDiscoveredServers = unifiedRegistry.getAutoDiscoveredServers();
840
+ const allTools = await unifiedRegistry.listAllTools();
841
+ return {
842
+ mcpInitialized: this.mcpInitialized,
843
+ totalServers,
844
+ availableServers,
845
+ autoDiscoveredCount: autoDiscoveredServers.length,
846
+ totalTools: allTools.length,
847
+ autoDiscoveredServers: autoDiscoveredServers.map((server) => ({
848
+ id: server.metadata.name,
849
+ name: server.metadata.name,
850
+ source: server.source,
851
+ status: "discovered",
852
+ hasServer: true,
853
+ })),
854
+ customToolsCount: this.customTools.size,
855
+ inMemoryServersCount: this.inMemoryServers.size,
652
856
  };
653
- await unifiedRegistry.addExternalServer(serverId, transportConfig);
654
- // Check if server is actually connected vs just registered
655
- const isConnected = unifiedRegistry.isConnected(serverId);
656
- if (isConnected) {
657
- mcpLogger.info(`[${functionTag}] Successfully connected to MCP server: ${serverId}`);
658
- }
659
- else {
660
- mcpLogger.info(`[${functionTag}] MCP server registered: ${serverId} (connection failed, but server available for retry)`);
661
- }
662
857
  }
663
858
  catch (error) {
664
- mcpLogger.error(`[${functionTag}] Failed to add MCP server: ${serverId}`, {
859
+ return {
860
+ mcpInitialized: false,
861
+ totalServers: 0,
862
+ availableServers: 0,
863
+ autoDiscoveredCount: 0,
864
+ totalTools: 0,
865
+ autoDiscoveredServers: [],
866
+ customToolsCount: this.customTools.size,
867
+ inMemoryServersCount: this.inMemoryServers.size,
665
868
  error: error instanceof Error ? error.message : String(error),
666
- });
667
- const newError = new Error(`Failed to add MCP server '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
668
- if (error instanceof Error && error.stack) {
669
- newError.stack = `${newError.stack}\nCaused by: ${error.stack}`;
670
- }
671
- throw newError;
869
+ };
672
870
  }
673
871
  }
674
872
  /**
675
- * Short alias for generate() - CLI-SDK consistency
676
- * @param options - Text generation options
677
- * @returns Promise resolving to text generation result
873
+ * List all configured MCP servers with their status
874
+ * @returns Promise resolving to array of MCP server information
678
875
  */
679
- async gen(options) {
680
- return this.generateText(options);
681
- }
682
- /**
683
- * Get the connection client for a specific MCP server
684
- * @param serverId - The ID of the server to get connection for
685
- * @returns Client connection object or undefined if not connected
686
- */
687
- getConnection(serverId) {
688
- return unifiedRegistry.getConnection(serverId);
876
+ async listMCPServers() {
877
+ const { unifiedRegistry } = await import("./mcp/unified-registry.js");
878
+ try {
879
+ const servers = unifiedRegistry.getAutoDiscoveredServers();
880
+ return servers.map((server) => ({
881
+ id: server.metadata.name,
882
+ name: server.metadata.name,
883
+ source: server.source,
884
+ status: unifiedRegistry.isConnected(server.metadata.name)
885
+ ? "connected"
886
+ : "discovered",
887
+ hasServer: true,
888
+ metadata: server.metadata,
889
+ }));
890
+ }
891
+ catch (error) {
892
+ logger.warn("Failed to list MCP servers", { error });
893
+ return [];
894
+ }
689
895
  }
690
896
  /**
691
- * Check if a specific MCP server is currently connected
692
- * @param serverId - The ID of the server to check
693
- * @returns True if server is connected, false otherwise
897
+ * Test connectivity to a specific MCP server
898
+ * @param serverId - ID of the MCP server to test
899
+ * @returns Promise resolving to true if server is reachable
694
900
  */
695
- isConnected(serverId) {
696
- return unifiedRegistry.isConnected(serverId);
901
+ async testMCPServer(serverId) {
902
+ const { unifiedRegistry } = await import("./mcp/unified-registry.js");
903
+ try {
904
+ return unifiedRegistry.isConnected(serverId);
905
+ }
906
+ catch {
907
+ return false;
908
+ }
697
909
  }
698
910
  }
911
+ // Create default instance
912
+ export const neurolink = new NeuroLink();
913
+ export default neurolink;