@juspay/neurolink 5.0.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 (214) hide show
  1. package/CHANGELOG.md +20 -7
  2. package/README.md +160 -172
  3. package/dist/agent/direct-tools.d.ts +6 -6
  4. package/dist/chat/sse-handler.js +5 -4
  5. package/dist/chat/websocket-chat-handler.js +9 -9
  6. package/dist/cli/commands/config.d.ts +3 -3
  7. package/dist/cli/commands/mcp.js +9 -8
  8. package/dist/cli/commands/ollama.js +3 -3
  9. package/dist/cli/factories/command-factory.d.ts +18 -0
  10. package/dist/cli/factories/command-factory.js +183 -0
  11. package/dist/cli/index.js +105 -157
  12. package/dist/cli/utils/interactive-setup.js +2 -2
  13. package/dist/core/base-provider.d.ts +423 -0
  14. package/dist/core/base-provider.js +365 -0
  15. package/dist/core/constants.d.ts +1 -1
  16. package/dist/core/constants.js +1 -1
  17. package/dist/core/dynamic-models.d.ts +6 -6
  18. package/dist/core/evaluation.d.ts +19 -80
  19. package/dist/core/evaluation.js +185 -484
  20. package/dist/core/factory.d.ts +3 -3
  21. package/dist/core/factory.js +31 -91
  22. package/dist/core/service-registry.d.ts +47 -0
  23. package/dist/core/service-registry.js +112 -0
  24. package/dist/core/types.d.ts +49 -49
  25. package/dist/core/types.js +1 -0
  26. package/dist/factories/compatibility-factory.d.ts +20 -0
  27. package/dist/factories/compatibility-factory.js +69 -0
  28. package/dist/factories/provider-factory.d.ts +72 -0
  29. package/dist/factories/provider-factory.js +144 -0
  30. package/dist/factories/provider-generate-factory.d.ts +20 -0
  31. package/dist/factories/provider-generate-factory.js +87 -0
  32. package/dist/factories/provider-registry.d.ts +38 -0
  33. package/dist/factories/provider-registry.js +107 -0
  34. package/dist/index.d.ts +8 -5
  35. package/dist/index.js +5 -5
  36. package/dist/lib/agent/direct-tools.d.ts +6 -6
  37. package/dist/lib/chat/sse-handler.js +5 -4
  38. package/dist/lib/chat/websocket-chat-handler.js +9 -9
  39. package/dist/lib/core/base-provider.d.ts +423 -0
  40. package/dist/lib/core/base-provider.js +365 -0
  41. package/dist/lib/core/constants.d.ts +1 -1
  42. package/dist/lib/core/constants.js +1 -1
  43. package/dist/lib/core/dynamic-models.d.ts +6 -6
  44. package/dist/lib/core/evaluation.d.ts +19 -80
  45. package/dist/lib/core/evaluation.js +185 -484
  46. package/dist/lib/core/factory.d.ts +3 -3
  47. package/dist/lib/core/factory.js +30 -91
  48. package/dist/lib/core/service-registry.d.ts +47 -0
  49. package/dist/lib/core/service-registry.js +112 -0
  50. package/dist/lib/core/types.d.ts +49 -49
  51. package/dist/lib/core/types.js +1 -0
  52. package/dist/lib/factories/compatibility-factory.d.ts +20 -0
  53. package/dist/lib/factories/compatibility-factory.js +69 -0
  54. package/dist/lib/factories/provider-factory.d.ts +72 -0
  55. package/dist/lib/factories/provider-factory.js +144 -0
  56. package/dist/lib/factories/provider-generate-factory.d.ts +20 -0
  57. package/dist/lib/factories/provider-generate-factory.js +87 -0
  58. package/dist/lib/factories/provider-registry.d.ts +38 -0
  59. package/dist/lib/factories/provider-registry.js +107 -0
  60. package/dist/lib/index.d.ts +8 -5
  61. package/dist/lib/index.js +5 -5
  62. package/dist/lib/mcp/client.js +5 -5
  63. package/dist/lib/mcp/config.js +28 -3
  64. package/dist/lib/mcp/dynamic-orchestrator.js +8 -8
  65. package/dist/lib/mcp/external-client.js +2 -2
  66. package/dist/lib/mcp/factory.d.ts +1 -1
  67. package/dist/lib/mcp/factory.js +1 -1
  68. package/dist/lib/mcp/function-calling.js +1 -1
  69. package/dist/lib/mcp/initialize-tools.d.ts +1 -1
  70. package/dist/lib/mcp/initialize-tools.js +45 -1
  71. package/dist/lib/mcp/initialize.js +16 -6
  72. package/dist/lib/mcp/neurolink-mcp-client.js +10 -10
  73. package/dist/lib/mcp/orchestrator.js +4 -4
  74. package/dist/lib/mcp/servers/agent/direct-tools-server.d.ts +8 -0
  75. package/dist/lib/mcp/servers/agent/direct-tools-server.js +109 -0
  76. package/dist/lib/mcp/servers/ai-providers/ai-analysis-tools.js +10 -10
  77. package/dist/lib/mcp/servers/ai-providers/ai-core-server.js +8 -6
  78. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
  79. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.js +16 -16
  80. package/dist/lib/mcp/unified-registry.d.ts +4 -0
  81. package/dist/lib/mcp/unified-registry.js +42 -9
  82. package/dist/lib/neurolink.d.ts +161 -174
  83. package/dist/lib/neurolink.js +723 -397
  84. package/dist/lib/providers/amazon-bedrock.d.ts +32 -0
  85. package/dist/lib/providers/amazon-bedrock.js +143 -0
  86. package/dist/lib/providers/analytics-helper.js +7 -4
  87. package/dist/lib/providers/anthropic-baseprovider.d.ts +23 -0
  88. package/dist/lib/providers/anthropic-baseprovider.js +114 -0
  89. package/dist/lib/providers/anthropic.d.ts +19 -39
  90. package/dist/lib/providers/anthropic.js +84 -378
  91. package/dist/lib/providers/azure-openai.d.ts +20 -0
  92. package/dist/lib/providers/azure-openai.js +89 -0
  93. package/dist/lib/providers/function-calling-provider.d.ts +14 -12
  94. package/dist/lib/providers/function-calling-provider.js +114 -64
  95. package/dist/lib/providers/google-ai-studio.d.ts +23 -0
  96. package/dist/lib/providers/google-ai-studio.js +107 -0
  97. package/dist/lib/providers/google-vertex.d.ts +47 -0
  98. package/dist/lib/providers/google-vertex.js +205 -0
  99. package/dist/lib/providers/huggingFace.d.ts +33 -27
  100. package/dist/lib/providers/huggingFace.js +103 -400
  101. package/dist/lib/providers/index.d.ts +9 -9
  102. package/dist/lib/providers/index.js +9 -9
  103. package/dist/lib/providers/mcp-provider.d.ts +13 -8
  104. package/dist/lib/providers/mcp-provider.js +63 -18
  105. package/dist/lib/providers/mistral.d.ts +42 -0
  106. package/dist/lib/providers/mistral.js +160 -0
  107. package/dist/lib/providers/ollama.d.ts +52 -35
  108. package/dist/lib/providers/ollama.js +297 -477
  109. package/dist/lib/providers/openAI.d.ts +21 -21
  110. package/dist/lib/providers/openAI.js +81 -245
  111. package/dist/lib/sdk/tool-extension.d.ts +181 -0
  112. package/dist/lib/sdk/tool-extension.js +283 -0
  113. package/dist/lib/sdk/tool-registration.d.ts +95 -0
  114. package/dist/lib/sdk/tool-registration.js +167 -0
  115. package/dist/lib/types/generate-types.d.ts +80 -0
  116. package/dist/lib/types/generate-types.js +1 -0
  117. package/dist/lib/types/mcp-types.d.ts +116 -0
  118. package/dist/lib/types/mcp-types.js +5 -0
  119. package/dist/lib/types/stream-types.d.ts +95 -0
  120. package/dist/lib/types/stream-types.js +1 -0
  121. package/dist/lib/types/universal-provider-options.d.ts +87 -0
  122. package/dist/lib/types/universal-provider-options.js +53 -0
  123. package/dist/lib/utils/providerUtils-fixed.js +1 -1
  124. package/dist/lib/utils/streaming-utils.d.ts +14 -2
  125. package/dist/lib/utils/streaming-utils.js +0 -3
  126. package/dist/mcp/client.js +5 -5
  127. package/dist/mcp/config.js +28 -3
  128. package/dist/mcp/dynamic-orchestrator.js +8 -8
  129. package/dist/mcp/external-client.js +2 -2
  130. package/dist/mcp/factory.d.ts +1 -1
  131. package/dist/mcp/factory.js +1 -1
  132. package/dist/mcp/function-calling.js +1 -1
  133. package/dist/mcp/initialize-tools.d.ts +1 -1
  134. package/dist/mcp/initialize-tools.js +45 -1
  135. package/dist/mcp/initialize.js +16 -6
  136. package/dist/mcp/neurolink-mcp-client.js +10 -10
  137. package/dist/mcp/orchestrator.js +4 -4
  138. package/dist/mcp/servers/agent/direct-tools-server.d.ts +8 -0
  139. package/dist/mcp/servers/agent/direct-tools-server.js +109 -0
  140. package/dist/mcp/servers/ai-providers/ai-analysis-tools.js +10 -10
  141. package/dist/mcp/servers/ai-providers/ai-core-server.js +8 -6
  142. package/dist/mcp/servers/ai-providers/ai-workflow-tools.d.ts +2 -2
  143. package/dist/mcp/servers/ai-providers/ai-workflow-tools.js +16 -16
  144. package/dist/mcp/unified-registry.d.ts +4 -0
  145. package/dist/mcp/unified-registry.js +42 -9
  146. package/dist/neurolink.d.ts +161 -174
  147. package/dist/neurolink.js +723 -397
  148. package/dist/providers/amazon-bedrock.d.ts +32 -0
  149. package/dist/providers/amazon-bedrock.js +143 -0
  150. package/dist/providers/analytics-helper.js +7 -4
  151. package/dist/providers/anthropic-baseprovider.d.ts +23 -0
  152. package/dist/providers/anthropic-baseprovider.js +114 -0
  153. package/dist/providers/anthropic.d.ts +19 -39
  154. package/dist/providers/anthropic.js +83 -377
  155. package/dist/providers/azure-openai.d.ts +20 -0
  156. package/dist/providers/azure-openai.js +89 -0
  157. package/dist/providers/function-calling-provider.d.ts +14 -12
  158. package/dist/providers/function-calling-provider.js +114 -64
  159. package/dist/providers/google-ai-studio.d.ts +23 -0
  160. package/dist/providers/google-ai-studio.js +108 -0
  161. package/dist/providers/google-vertex.d.ts +47 -0
  162. package/dist/providers/google-vertex.js +205 -0
  163. package/dist/providers/huggingFace.d.ts +33 -27
  164. package/dist/providers/huggingFace.js +102 -399
  165. package/dist/providers/index.d.ts +9 -9
  166. package/dist/providers/index.js +9 -9
  167. package/dist/providers/mcp-provider.d.ts +13 -8
  168. package/dist/providers/mcp-provider.js +63 -18
  169. package/dist/providers/mistral.d.ts +42 -0
  170. package/dist/providers/mistral.js +160 -0
  171. package/dist/providers/ollama.d.ts +52 -35
  172. package/dist/providers/ollama.js +297 -476
  173. package/dist/providers/openAI.d.ts +21 -21
  174. package/dist/providers/openAI.js +81 -246
  175. package/dist/sdk/tool-extension.d.ts +181 -0
  176. package/dist/sdk/tool-extension.js +283 -0
  177. package/dist/sdk/tool-registration.d.ts +95 -0
  178. package/dist/sdk/tool-registration.js +168 -0
  179. package/dist/types/generate-types.d.ts +80 -0
  180. package/dist/types/generate-types.js +1 -0
  181. package/dist/types/mcp-types.d.ts +116 -0
  182. package/dist/types/mcp-types.js +5 -0
  183. package/dist/types/stream-types.d.ts +95 -0
  184. package/dist/types/stream-types.js +1 -0
  185. package/dist/types/universal-provider-options.d.ts +87 -0
  186. package/dist/types/universal-provider-options.js +53 -0
  187. package/dist/utils/providerUtils-fixed.js +1 -1
  188. package/dist/utils/streaming-utils.d.ts +14 -2
  189. package/dist/utils/streaming-utils.js +0 -3
  190. package/package.json +15 -10
  191. package/dist/lib/providers/agent-enhanced-provider.d.ts +0 -89
  192. package/dist/lib/providers/agent-enhanced-provider.js +0 -614
  193. package/dist/lib/providers/amazonBedrock.d.ts +0 -19
  194. package/dist/lib/providers/amazonBedrock.js +0 -334
  195. package/dist/lib/providers/azureOpenAI.d.ts +0 -39
  196. package/dist/lib/providers/azureOpenAI.js +0 -436
  197. package/dist/lib/providers/googleAIStudio.d.ts +0 -49
  198. package/dist/lib/providers/googleAIStudio.js +0 -333
  199. package/dist/lib/providers/googleVertexAI.d.ts +0 -38
  200. package/dist/lib/providers/googleVertexAI.js +0 -519
  201. package/dist/lib/providers/mistralAI.d.ts +0 -34
  202. package/dist/lib/providers/mistralAI.js +0 -294
  203. package/dist/providers/agent-enhanced-provider.d.ts +0 -89
  204. package/dist/providers/agent-enhanced-provider.js +0 -614
  205. package/dist/providers/amazonBedrock.d.ts +0 -19
  206. package/dist/providers/amazonBedrock.js +0 -334
  207. package/dist/providers/azureOpenAI.d.ts +0 -39
  208. package/dist/providers/azureOpenAI.js +0 -437
  209. package/dist/providers/googleAIStudio.d.ts +0 -49
  210. package/dist/providers/googleAIStudio.js +0 -333
  211. package/dist/providers/googleVertexAI.d.ts +0 -38
  212. package/dist/providers/googleVertexAI.js +0 -519
  213. package/dist/providers/mistralAI.d.ts +0 -34
  214. package/dist/providers/mistralAI.js +0 -294
@@ -1,21 +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";
23
+ import { ProviderRegistry } from "./factories/provider-registry.js";
24
+ import { validateTool, createMCPServerFromTools, } from "./sdk/tool-registration.js";
25
+ // Core types imported from core/types.js
15
26
  export class NeuroLink {
16
27
  mcpInitialized = false;
17
28
  contextManager;
29
+ // Tool registration support
30
+ customTools = new Map();
31
+ inMemoryServers = new Map();
18
32
  constructor() {
33
+ // SDK always disables manual MCP config for security
34
+ ProviderRegistry.setOptions({
35
+ enableManualMCP: false,
36
+ });
19
37
  this.contextManager = new ContextManager();
20
38
  }
21
39
  /**
@@ -28,92 +46,171 @@ export class NeuroLink {
28
46
  }
29
47
  try {
30
48
  mcpLogger.debug("[NeuroLink] Starting isolated MCP initialization...");
31
- // Use Promise.race with aggressive timeout and isolated context
32
- const initTimeout = 3000; // 3 seconds max (reduced from 5)
33
- const mcpInitPromise = Promise.race([
34
- 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
35
53
  new Promise((_, reject) => {
36
- setTimeout(() => {
37
- reject(new Error("MCP initialization timeout after 3s"));
38
- }, initTimeout);
54
+ setTimeout(() => reject(new Error("MCP initialization timeout")), initTimeout);
39
55
  }),
40
56
  ]);
41
- await mcpInitPromise;
57
+ // Register all providers with lazy loading support
58
+ await ProviderRegistry.registerAllProviders();
42
59
  this.mcpInitialized = true;
43
- mcpLogger.debug("[NeuroLink] MCP tool integration initialized successfully");
60
+ mcpLogger.debug("[NeuroLink] MCP initialization completed successfully");
44
61
  }
45
62
  catch (error) {
46
- mcpLogger.warn("[NeuroLink] MCP initialization failed, continuing without tools:", error);
47
- // Mark as initialized to prevent infinite retries
48
- this.mcpInitialized = true;
63
+ mcpLogger.warn("[NeuroLink] MCP initialization failed", {
64
+ error: error instanceof Error ? error.message : String(error),
65
+ });
66
+ // Continue without MCP - graceful degradation
49
67
  }
50
68
  }
51
69
  /**
52
- * Isolated MCP initialization to prevent context-dependent hanging
70
+ * MAIN ENTRY POINT: Enhanced generate method with new function signature
71
+ * Replaces both generateText and legacy methods
53
72
  */
54
- async doIsolatedMCPInitialization() {
55
- try {
56
- // Initialize only the essential built-in tools without complex registry
57
- mcpLogger.debug("[NeuroLink] Initializing essential MCP tools...");
58
- // Use dynamic import in isolated context to avoid circular dependencies
59
- const { initializeNeuroLinkMCP, isNeuroLinkMCPInitialized } = await import("./mcp/initialize.js");
60
- // Only initialize if not already done
61
- if (!isNeuroLinkMCPInitialized()) {
62
- await initializeNeuroLinkMCP();
63
- }
64
- mcpLogger.debug("[NeuroLink] Essential MCP tools initialized successfully");
65
- }
66
- catch (error) {
67
- mcpLogger.warn("[NeuroLink] Isolated MCP initialization failed:", error);
68
- throw error;
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");
69
82
  }
83
+ // Convert to internal TextGenerationOptions for compatibility
84
+ const textOptions = {
85
+ prompt: options.input.text,
86
+ provider: options.provider,
87
+ model: options.model,
88
+ temperature: options.temperature,
89
+ maxTokens: options.maxTokens,
90
+ systemPrompt: options.systemPrompt,
91
+ disableTools: options.disableTools, // FIX: Pass disableTools flag
92
+ // 🔧 FIX: Include analytics and evaluation options!
93
+ enableAnalytics: options.enableAnalytics,
94
+ enableEvaluation: options.enableEvaluation,
95
+ context: options.context,
96
+ evaluationDomain: options.evaluationDomain,
97
+ toolUsageContext: options.toolUsageContext,
98
+ };
99
+ // Use redesigned generation logic
100
+ const textResult = await this.generateTextInternal(textOptions);
101
+ // Convert back to GenerateResult
102
+ const generateResult = {
103
+ content: textResult.content,
104
+ provider: textResult.provider,
105
+ model: textResult.model,
106
+ usage: textResult.usage
107
+ ? {
108
+ inputTokens: textResult.usage.promptTokens || 0,
109
+ outputTokens: textResult.usage.completionTokens || 0,
110
+ totalTokens: textResult.usage.totalTokens || 0,
111
+ }
112
+ : undefined,
113
+ responseTime: textResult.responseTime,
114
+ toolsUsed: textResult.toolsUsed,
115
+ toolExecutions: textResult.toolExecutions?.map((te) => ({
116
+ name: te.toolName || te.name || "",
117
+ input: te.input || {},
118
+ output: te.output || te.result,
119
+ duration: te.executionTime || te.duration || 0,
120
+ })),
121
+ enhancedWithTools: textResult.enhancedWithTools,
122
+ availableTools: textResult.availableTools?.map((tool) => ({
123
+ name: tool.name || "",
124
+ description: tool.description || "",
125
+ parameters: tool.parameters || {},
126
+ })),
127
+ analytics: textResult.analytics,
128
+ evaluation: textResult.evaluation,
129
+ };
130
+ return generateResult;
70
131
  }
71
132
  /**
72
- * Generate text using the best available AI provider with automatic fallback
73
- * Tools are ENABLED BY DEFAULT for natural AI behavior
133
+ * BACKWARD COMPATIBILITY: Legacy generateText method
134
+ * Internally calls generate() and converts result format
74
135
  */
75
136
  async generateText(options) {
76
- // 🔧 FIX: Add input validation
77
- if (!options ||
137
+ // Validate required parameters for backward compatibility
138
+ if (!options.prompt ||
78
139
  typeof options.prompt !== "string" ||
79
140
  options.prompt.trim() === "") {
80
- throw new Error("options.prompt is required and must be a non-empty string");
81
- }
82
- // Tools are DEFAULT behavior unless explicitly disabled
83
- if (options.disableTools === true) {
84
- return this.generateTextRegular(options);
141
+ throw new Error("GenerateText options must include prompt as a non-empty string");
85
142
  }
86
- // Default: Generate with tools (natural AI behavior)
87
- return this.generateTextWithTools(options);
143
+ // Use internal generation method directly
144
+ return await this.generateTextInternal(options);
88
145
  }
89
146
  /**
90
- * Generate text with real MCP tool integration using automatic detection
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
91
153
  */
92
- async generateTextWithTools(options) {
154
+ async generateTextInternal(options) {
93
155
  const startTime = Date.now();
94
- const functionTag = "NeuroLink.generateTextWithTools";
95
- // Initialize MCP if needed
96
- await this.initializeMCP();
97
- // Create execution context for tool operations
98
- const context = this.contextManager.createContext({
99
- sessionId: `neurolink-${Date.now()}`,
100
- userId: "neurolink-user",
101
- aiProvider: options.provider || "auto",
156
+ const functionTag = "NeuroLink.generateTextInternal";
157
+ logger.debug(`[${functionTag}] Starting generation`, {
158
+ provider: options.provider || "auto",
159
+ promptLength: options.prompt?.length || 0,
102
160
  });
103
- // Determine provider to use
104
- const providerName = options.provider === "auto" || !options.provider
105
- ? await getBestProvider()
106
- : options.provider;
107
161
  try {
108
- mcpLogger.debug(`[${functionTag}] Starting MCP-enabled generation`, {
109
- provider: providerName,
110
- prompt: (options.prompt?.substring(0, 100) || "No prompt") + "...",
111
- contextId: context.sessionId,
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;
169
+ }
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;
181
+ }
182
+ catch (error) {
183
+ logger.error(`[${functionTag}] All generation methods failed`, {
184
+ error: error instanceof Error ? error.message : String(error),
112
185
  });
113
- // Get available tools from tool registry (simplified approach)
186
+ throw error;
187
+ }
188
+ }
189
+ /**
190
+ * Try MCP-enhanced generation (no fallback recursion)
191
+ */
192
+ async tryMCPGeneration(options) {
193
+ const functionTag = "NeuroLink.tryMCPGeneration";
194
+ const startTime = Date.now(); // 🔧 FIX: Add proper timing
195
+ try {
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",
206
+ });
207
+ // Determine provider
208
+ const providerName = options.provider === "auto" || !options.provider
209
+ ? await getBestProvider()
210
+ : options.provider;
211
+ // Get available tools
114
212
  let availableTools = [];
115
213
  try {
116
- // Use toolRegistry directly instead of unified registry to avoid hanging
117
214
  const allTools = await toolRegistry.listTools();
118
215
  availableTools = allTools.map((tool) => ({
119
216
  name: tool.name,
@@ -121,85 +218,52 @@ export class NeuroLink {
121
218
  server: tool.server,
122
219
  category: tool.category,
123
220
  }));
124
- mcpLogger.debug(`[${functionTag}] Found ${availableTools.length} available tools from default registry`, {
125
- tools: availableTools.map((t) => t.name),
126
- });
127
221
  }
128
222
  catch (error) {
129
- mcpLogger.warn(`[${functionTag}] Failed to get available tools`, {
130
- error,
131
- });
223
+ mcpLogger.warn(`[${functionTag}] Failed to get tools`, { error });
132
224
  }
133
225
  // Create tool-aware system prompt
134
226
  const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
135
- // Create provider with MCP enabled using best provider function
136
- const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true);
137
- // Generate text with automatic tool detection
138
- const result = await provider.generateText({
139
- prompt: options.prompt,
140
- temperature: options.temperature,
141
- maxTokens: options.maxTokens,
227
+ // Create provider and generate
228
+ const provider = await AIProviderFactory.createProvider(providerName, options.model, !options.disableTools, // Pass disableTools as inverse of enableMCP
229
+ this);
230
+ const result = await provider.generate({
231
+ ...options,
142
232
  systemPrompt: enhancedSystemPrompt,
143
- timeout: options.timeout,
144
- // NEW: Pass enhancement options
145
- enableAnalytics: options.enableAnalytics,
146
- enableEvaluation: options.enableEvaluation,
147
- context: options.context,
148
- // NEW: Lighthouse-compatible domain-aware evaluation
149
- evaluationDomain: options.evaluationDomain,
150
- toolUsageContext: options.toolUsageContext,
151
- conversationHistory: options.conversationHistory,
152
- }, options.schema);
153
- if (!result) {
154
- throw new Error("No response received from AI provider");
155
- }
156
- const responseTime = Date.now() - startTime;
157
- // Extract MCP metadata if available
158
- const metadata = result.metadata || {};
159
- mcpLogger.debug(`[${functionTag}] MCP-enabled generation completed`, {
160
- responseTime,
161
- toolsUsed: metadata.toolsUsed || [],
162
- enhancedWithTools: metadata.enhancedWithTools || false,
163
- availableToolsCount: availableTools.length,
164
233
  });
165
- // Check if we actually got content
166
- if (!result.text || result.text.trim() === "") {
167
- mcpLogger.warn(`[${functionTag}] Empty response from provider, attempting fallback`, {
168
- provider: providerName,
169
- hasText: !!result.text,
170
- textLength: result.text?.length || 0,
171
- });
172
- // Fall back to regular generation if MCP generation returns empty
173
- return this.generateTextRegular(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
174
238
  }
239
+ // Return enhanced result
175
240
  return {
176
- content: result.text,
241
+ content: result.content,
177
242
  provider: providerName,
178
243
  usage: result.usage,
179
244
  responseTime,
180
- toolsUsed: metadata.toolsUsed || [],
181
- enhancedWithTools: metadata.enhancedWithTools || false,
245
+ toolsUsed: [],
246
+ enhancedWithTools: true,
182
247
  availableTools: availableTools.length > 0 ? availableTools : undefined,
183
- // NEW: Preserve enhancement data from provider
184
- ...(result.analytics && { analytics: result.analytics }),
185
- ...(result.evaluation && { evaluation: result.evaluation }),
248
+ // 🔧 FIX: Include analytics and evaluation from BaseProvider
249
+ analytics: result.analytics,
250
+ evaluation: result.evaluation,
186
251
  };
187
252
  }
188
253
  catch (error) {
189
- // Fall back to regular generation if MCP fails
190
- mcpLogger.warn(`[${functionTag}] MCP generation failed, falling back to regular`, {
254
+ mcpLogger.warn(`[${functionTag}] MCP generation failed`, {
191
255
  error: error instanceof Error ? error.message : String(error),
192
256
  });
193
- return this.generateTextRegular(options);
257
+ return null; // Let caller fall back
194
258
  }
195
259
  }
196
260
  /**
197
- * Regular text generation (existing logic)
261
+ * Direct provider generation (no MCP, no recursion)
198
262
  */
199
- async generateTextRegular(options) {
263
+ async directProviderGeneration(options) {
200
264
  const startTime = Date.now();
201
- const functionTag = "NeuroLink.generateTextRegular";
202
- // Define fallback provider priority order
265
+ const functionTag = "NeuroLink.directProviderGeneration";
266
+ // Define provider priority for fallback
203
267
  const providerPriority = [
204
268
  "openai",
205
269
  "vertex",
@@ -213,91 +277,55 @@ export class NeuroLink {
213
277
  const requestedProvider = options.provider === "auto" ? undefined : options.provider;
214
278
  // If specific provider requested, only use that provider (no fallback)
215
279
  const tryProviders = requestedProvider
216
- ? [requestedProvider] // Only use the requested provider, no fallback
280
+ ? [requestedProvider]
217
281
  : providerPriority;
218
- logger.debug(`[${functionTag}] Starting text generation`, {
282
+ logger.debug(`[${functionTag}] Starting direct generation`, {
219
283
  requestedProvider: requestedProvider || "auto",
220
284
  tryProviders,
221
285
  allowFallback: !requestedProvider,
222
- promptLength: options.prompt?.length || 0,
223
286
  });
224
287
  let lastError = null;
288
+ // Try each provider in order
225
289
  for (const providerName of tryProviders) {
226
290
  try {
227
- logger.debug(`[${functionTag}] Attempting provider`, {
228
- provider: providerName,
229
- });
230
- const provider = await AIProviderFactory.createProvider(providerName, options.model, false);
231
- const result = await provider.generateText({
232
- prompt: options.prompt,
233
- model: options.model,
234
- temperature: options.temperature,
235
- maxTokens: options.maxTokens,
236
- systemPrompt: options.systemPrompt,
237
- timeout: options.timeout,
238
- // NEW: Pass enhancement options
239
- enableAnalytics: options.enableAnalytics,
240
- enableEvaluation: options.enableEvaluation,
241
- context: options.context,
242
- // NEW: Lighthouse-compatible domain-aware evaluation
243
- evaluationDomain: options.evaluationDomain,
244
- toolUsageContext: options.toolUsageContext,
245
- conversationHistory: options.conversationHistory,
246
- }, 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;
247
296
  if (!result) {
248
- throw new Error("No response received from AI provider");
297
+ throw new Error(`Provider ${providerName} returned null result`);
249
298
  }
250
- // Check if we actually got content
251
- if (!result.text || result.text.trim() === "") {
252
- logger.warn(`[${functionTag}] Empty response from provider`, {
253
- provider: providerName,
254
- hasText: !!result.text,
255
- textLength: result.text?.length || 0,
256
- });
257
- // Continue to next provider if available
258
- throw new Error(`Empty response from ${providerName}`);
259
- }
260
- const responseTime = Date.now() - startTime;
261
- logger.debug(`[${functionTag}] Provider succeeded`, {
262
- provider: providerName,
299
+ logger.debug(`[${functionTag}] Provider ${providerName} succeeded`, {
263
300
  responseTime,
264
- usage: result.usage,
301
+ contentLength: result.content?.length || 0,
265
302
  });
266
303
  return {
267
- content: result.text,
304
+ content: result.content || "",
268
305
  provider: providerName,
306
+ model: result.model,
269
307
  usage: result.usage,
270
308
  responseTime,
271
- // NEW: Preserve enhancement data from provider
272
- ...(result.analytics && { analytics: result.analytics }),
273
- ...(result.evaluation && { evaluation: result.evaluation }),
309
+ toolsUsed: [],
310
+ enhancedWithTools: false,
311
+ analytics: result.analytics,
312
+ evaluation: result.evaluation,
274
313
  };
275
314
  }
276
315
  catch (error) {
277
- const errorMessage = error instanceof Error ? error.message : String(error);
278
- lastError = error instanceof Error ? error : new Error(errorMessage);
279
- // Special handling for timeout errors
280
- if (error instanceof TimeoutError) {
281
- logger.warn(`[${functionTag}] Provider timed out`, {
282
- provider: providerName,
283
- timeout: error.timeout,
284
- operation: error.operation,
285
- });
286
- }
287
- logger.debug(`[${functionTag}] Provider failed, trying next`, {
288
- provider: providerName,
289
- error: errorMessage,
290
- isTimeout: error instanceof TimeoutError,
291
- 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,
292
319
  });
293
320
  // Continue to next provider
294
- continue;
295
321
  }
296
322
  }
297
323
  // All providers failed
298
- logger.debug(`[${functionTag}] All providers failed`, {
324
+ const responseTime = Date.now() - startTime;
325
+ logger.error(`[${functionTag}] All providers failed`, {
299
326
  triedProviders: tryProviders,
300
327
  lastError: lastError?.message,
328
+ responseTime,
301
329
  });
302
330
  throw new Error(`Failed to generate text with all providers. Last error: ${lastError?.message || "Unknown error"}`);
303
331
  }
@@ -305,283 +333,581 @@ export class NeuroLink {
305
333
  * Create tool-aware system prompt that informs AI about available tools
306
334
  */
307
335
  createToolAwareSystemPrompt(originalSystemPrompt, availableTools) {
308
- const basePrompt = originalSystemPrompt || "You are a helpful AI assistant.";
309
336
  if (availableTools.length === 0) {
310
- return basePrompt;
337
+ return originalSystemPrompt || "";
311
338
  }
312
339
  const toolDescriptions = availableTools
313
- .map((tool) => `- ${tool.name}: ${tool.description}`)
340
+ .map((tool) => `- ${tool.name}: ${tool.description} (from ${tool.server})`)
314
341
  .join("\n");
315
- return `${basePrompt}
316
-
317
- Available tools that can be used when relevant:
318
-
319
- ${toolDescriptions}
320
-
321
- You can mention these capabilities when they're relevant to user questions. For example:
322
- - For time questions: "I can get the current time"
323
- - For provider questions: "I can check AI provider status"
324
- - For tool questions: "I can list available tools"
325
-
326
- 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;
327
344
  }
328
345
  /**
329
- * Generate streaming text using the best available AI provider with automatic fallback
346
+ * BACKWARD COMPATIBILITY: Legacy streamText method
347
+ * Internally calls stream() and converts result format
330
348
  */
331
- async generateTextStream(options) {
332
- const functionTag = "NeuroLink.generateTextStream";
333
- // Define fallback provider priority order
334
- const providerPriority = [
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();
364
+ }
365
+ /**
366
+ * PRIMARY METHOD: Stream content using AI (recommended for new code)
367
+ * Future-ready for multi-modal capabilities with current text focus
368
+ */
369
+ async stream(options) {
370
+ const startTime = Date.now();
371
+ const functionTag = "NeuroLink.stream";
372
+ // Validate input
373
+ if (!options?.input?.text ||
374
+ typeof options.input.text !== "string" ||
375
+ options.input.text.trim() === "") {
376
+ throw new Error("Stream options must include input.text as a non-empty string");
377
+ }
378
+ // Initialize MCP if needed
379
+ await this.initializeMCP();
380
+ // Create execution context for tool operations
381
+ const context = this.contextManager.createContext({
382
+ sessionId: `neurolink-stream-${Date.now()}`,
383
+ userId: "neurolink-user",
384
+ aiProvider: options.provider || "auto",
385
+ });
386
+ // Determine provider to use
387
+ const providerName = options.provider === "auto" || !options.provider
388
+ ? await getBestProvider()
389
+ : options.provider;
390
+ try {
391
+ mcpLogger.debug(`[${functionTag}] Starting MCP-enabled streaming`, {
392
+ provider: providerName,
393
+ prompt: (options.input.text?.substring(0, 100) || "No text") + "...",
394
+ contextId: context.sessionId,
395
+ });
396
+ // Create provider using the same factory pattern as generate
397
+ const provider = await AIProviderFactory.createBestProvider(providerName, options.model, true, this);
398
+ // Call the provider's stream method directly
399
+ const streamResult = await provider.stream(options);
400
+ // Extract the stream from the result
401
+ const stream = streamResult.stream;
402
+ const responseTime = Date.now() - startTime;
403
+ mcpLogger.debug(`[${functionTag}] MCP-enabled streaming completed`, {
404
+ responseTime,
405
+ provider: providerName,
406
+ });
407
+ // Convert to StreamResult format
408
+ return {
409
+ stream,
410
+ provider: providerName,
411
+ model: options.model,
412
+ metadata: {
413
+ streamId: `neurolink-${Date.now()}`,
414
+ startTime,
415
+ },
416
+ };
417
+ }
418
+ catch (error) {
419
+ // Fall back to regular streaming if MCP fails
420
+ mcpLogger.warn(`[${functionTag}] MCP streaming failed, falling back to regular`, {
421
+ error: error instanceof Error ? error.message : String(error),
422
+ });
423
+ // Use factory to create provider without MCP
424
+ const provider = await AIProviderFactory.createBestProvider(providerName, options.model, false, // Disable MCP for fallback
425
+ this);
426
+ const streamResult = await provider.stream(options);
427
+ const responseTime = Date.now() - startTime;
428
+ return {
429
+ stream: streamResult.stream,
430
+ provider: providerName,
431
+ model: options.model,
432
+ metadata: {
433
+ streamId: `neurolink-${Date.now()}`,
434
+ startTime,
435
+ responseTime,
436
+ fallback: true,
437
+ },
438
+ };
439
+ }
440
+ }
441
+ // ========================================
442
+ // Tool Registration API
443
+ // ========================================
444
+ /**
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
448
+ */
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}`);
464
+ }
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) {
507
+ try {
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
+ });
520
+ }
521
+ catch (error) {
522
+ mcpLogger.error(`[NeuroLink] Failed to register in-memory server ${serverId}:`, error);
523
+ throw error;
524
+ }
525
+ }
526
+ /**
527
+ * Get all registered in-memory servers
528
+ * @returns Map of server IDs to configurations
529
+ */
530
+ getInMemoryServers() {
531
+ return new Map(this.inMemoryServers);
532
+ }
533
+ /**
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
540
+ */
541
+ async executeTool(toolName, params = {}, options) {
542
+ const functionTag = "NeuroLink.executeTool";
543
+ try {
544
+ mcpLogger.debug(`[${functionTag}] Executing tool: ${toolName}`, {
545
+ toolName,
546
+ params,
547
+ options,
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 = [
335
661
  "openai",
336
- "vertex",
337
662
  "bedrock",
663
+ "vertex",
664
+ "google-vertex",
338
665
  "anthropic",
339
666
  "azure",
340
667
  "google-ai",
341
668
  "huggingface",
342
669
  "ollama",
670
+ "mistral",
343
671
  ];
344
- const requestedProvider = options.provider === "auto" ? undefined : options.provider;
345
- // If specific provider requested, only use that provider (no fallback)
346
- const tryProviders = requestedProvider
347
- ? [requestedProvider] // Only use the requested provider, no fallback
348
- : providerPriority;
349
- logger.debug(`[${functionTag}] Starting stream generation`, {
350
- requestedProvider: requestedProvider || "auto",
351
- tryProviders,
352
- allowFallback: !requestedProvider,
353
- promptLength: options.prompt.length,
354
- });
355
- let lastError = null;
356
- for (const providerName of tryProviders) {
357
- try {
358
- logger.debug(`[${functionTag}] Attempting provider`, {
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({
359
679
  provider: providerName,
680
+ status: "not-configured",
681
+ configured: false,
682
+ authenticated: false,
683
+ error: "Missing required environment variables",
684
+ responseTime: 0,
360
685
  });
361
- const provider = await AIProviderFactory.createProvider(providerName, options.model, false);
362
- const result = await provider.streamText({
363
- prompt: options.prompt,
364
- model: options.model,
365
- temperature: options.temperature,
366
- maxTokens: options.maxTokens,
367
- systemPrompt: options.systemPrompt,
368
- timeout: options.timeout,
369
- });
370
- if (!result) {
371
- throw new Error("No stream response received from AI provider");
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
+ }
372
721
  }
373
- logger.debug(`[${functionTag}] Provider succeeded`, {
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({
374
745
  provider: providerName,
746
+ status: "working",
747
+ configured: true,
748
+ authenticated: true,
749
+ responseTime: Date.now() - startTime,
375
750
  });
376
- // Convert the AI SDK stream to our expected format
377
- async function* convertStream() {
378
- if (result && result.textStream) {
379
- for await (const chunk of result.textStream) {
380
- yield { content: chunk };
381
- }
382
- }
383
- }
384
- return convertStream();
385
751
  }
386
752
  catch (error) {
387
753
  const errorMessage = error instanceof Error ? error.message : String(error);
388
- lastError = error instanceof Error ? error : new Error(errorMessage);
389
- // Special handling for timeout errors
390
- if (error instanceof TimeoutError) {
391
- logger.warn(`[${functionTag}] Provider timed out`, {
392
- provider: providerName,
393
- timeout: error.timeout,
394
- operation: error.operation,
395
- });
396
- }
397
- logger.debug(`[${functionTag}] Provider failed, trying next`, {
754
+ results.push({
398
755
  provider: providerName,
756
+ status: "failed",
757
+ configured: true,
758
+ authenticated: false,
399
759
  error: errorMessage,
400
- isTimeout: error instanceof TimeoutError,
401
- remainingProviders: tryProviders.slice(tryProviders.indexOf(providerName) + 1),
760
+ responseTime: Date.now() - startTime,
402
761
  });
403
- // Continue to next provider
404
- continue;
405
762
  }
406
763
  }
407
- // All providers failed
408
- logger.debug(`[${functionTag}] All providers failed`, {
409
- triedProviders: tryProviders,
410
- lastError: lastError?.message,
411
- });
412
- throw new Error(`Failed to stream text with all providers. Last error: ${lastError?.message || "Unknown error"}`);
764
+ return results;
413
765
  }
414
766
  /**
415
- * Get the best available AI provider
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
416
770
  */
417
- async getBestProvider() {
418
- return await getBestProvider();
419
- }
420
- /**
421
- * Test a specific provider
422
- */
423
- async testProvider(providerName, testPrompt = "test") {
771
+ async testProvider(providerName) {
424
772
  try {
425
- const provider = await AIProviderFactory.createProvider(providerName, null, false); // Disable MCP for simple testing
426
- await provider.generateText({
427
- prompt: testPrompt,
428
- enableAnalytics: false,
429
- enableEvaluation: false,
430
- });
773
+ await this.testProviderConnection(providerName);
431
774
  return true;
432
775
  }
433
- catch (error) {
776
+ catch {
434
777
  return false;
435
778
  }
436
779
  }
437
780
  /**
438
- * Get access to the unified MCP registry for tool inspection and management
781
+ * Internal method to test provider connection with minimal generation call
439
782
  */
440
- getUnifiedRegistry() {
441
- 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
+ });
442
791
  }
443
792
  /**
444
- * 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
445
796
  */
446
- async getMCPStatus() {
447
- await this.initializeMCP();
448
- const totalServers = unifiedRegistry.getTotalServerCount();
449
- const availableServers = unifiedRegistry.getAvailableServerCount();
450
- const autoDiscoveredServers = unifiedRegistry.getAutoDiscoveredServers();
451
- const allTools = await unifiedRegistry.listAllTools();
452
- return {
453
- mcpInitialized: this.mcpInitialized,
454
- totalServers,
455
- availableServers,
456
- autoDiscoveredCount: autoDiscoveredServers.length,
457
- totalTools: allTools.length,
458
- autoDiscoveredServers: autoDiscoveredServers.map((server) => ({
459
- id: server.metadata.name,
460
- name: server.metadata.name,
461
- source: server.source,
462
- status: "discovered",
463
- hasServer: true,
464
- })),
465
- };
797
+ async hasProviderEnvVars(providerName) {
798
+ const { hasProviderEnvVars } = await import("./utils/providerUtils.js");
799
+ return hasProviderEnvVars(providerName);
466
800
  }
467
801
  /**
468
- * Add a new MCP server programmatically
469
- *
470
- * Allows dynamic registration of MCP servers at runtime for enhanced
471
- * tool ecosystem management. Perfect for integrating external services
472
- * like Bitbucket, Slack, databases, etc.
473
- *
474
- * @param serverId - Unique identifier for the server (e.g., 'bitbucket', 'slack-api')
475
- * @param config - Server configuration with command and execution parameters
476
- * @returns Promise that resolves when server is successfully added and connected
477
- *
478
- * @example
479
- * ```typescript
480
- * // Add Bitbucket MCP server
481
- * await neurolink.addMCPServer('bitbucket', {
482
- * command: 'npx',
483
- * args: ['-y', '@nexus2520/bitbucket-mcp-server'],
484
- * env: {
485
- * BITBUCKET_USERNAME: 'your-username',
486
- * BITBUCKET_APP_PASSWORD: 'your-app-password'
487
- * }
488
- * });
489
- *
490
- * // Add custom database connector
491
- * await neurolink.addMCPServer('database', {
492
- * command: 'node',
493
- * args: ['./custom-db-mcp-server.js'],
494
- * env: { DB_CONNECTION_STRING: 'postgresql://...' }
495
- * });
496
- * ```
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
497
805
  */
498
- async addMCPServer(serverId, config) {
499
- const functionTag = "NeuroLink.addMCPServer";
500
- mcpLogger.info(`[${functionTag}] Adding MCP server: ${serverId}`, {
501
- command: config.command,
502
- argsCount: config.args?.length || 0,
503
- hasEnv: Object.keys(config.env || {}).length > 0,
504
- });
505
- try {
506
- // Ensure MCP is initialized
507
- await this.initializeMCP();
508
- // Add server to unified registry with configurable transport type
509
- const transportType = config.type || "stdio";
510
- // Validate URL requirement for non-stdio transports
511
- if ((transportType === "sse" || transportType === "http") &&
512
- !config.url) {
513
- throw new Error(`URL is required for ${transportType} transport. Please provide config.url for server '${serverId}'.`);
514
- }
515
- const transportConfig = {
516
- type: transportType,
517
- ...(transportType === "stdio" && {
518
- command: config.command,
519
- args: config.args || [],
520
- env: config.env || {},
521
- cwd: config.cwd,
522
- }),
523
- ...(transportType === "sse" && {
524
- url: config.url,
525
- headers: config.headers,
526
- timeout: config.timeout,
527
- }),
528
- ...(transportType === "http" && {
529
- url: config.url,
530
- headers: config.headers,
531
- timeout: config.timeout,
532
- }),
533
- };
534
- await unifiedRegistry.addExternalServer(serverId, transportConfig);
535
- // Check if server is actually connected vs just registered
536
- const isConnected = unifiedRegistry.isConnected(serverId);
537
- if (isConnected) {
538
- mcpLogger.info(`[${functionTag}] Successfully connected to MCP server: ${serverId}`);
539
- }
540
- else {
541
- mcpLogger.info(`[${functionTag}] MCP server registered: ${serverId} (connection failed, but server available for retry)`);
542
- }
543
- }
544
- catch (error) {
545
- mcpLogger.error(`[${functionTag}] Failed to add MCP server: ${serverId}`, {
546
- error: error instanceof Error ? error.message : String(error),
547
- });
548
- const newError = new Error(`Failed to add MCP server '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
549
- if (error instanceof Error && error.stack) {
550
- newError.stack = `${newError.stack}\nCaused by: ${error.stack}`;
551
- }
552
- throw newError;
553
- }
806
+ async getBestProvider(requestedProvider) {
807
+ const { getBestProvider } = await import("./utils/providerUtils.js");
808
+ return getBestProvider(requestedProvider);
554
809
  }
555
810
  /**
556
- * Alias for generateText() - CLI-SDK consistency
557
- * @param options - Text generation options
558
- * @returns Promise resolving to text generation result
811
+ * Get list of all available AI provider names
812
+ * @returns Array of supported provider names
559
813
  */
560
- async generate(options) {
561
- return this.generateText(options);
814
+ async getAvailableProviders() {
815
+ const { getAvailableProviders } = await import("./utils/providerUtils.js");
816
+ return getAvailableProviders();
562
817
  }
563
818
  /**
564
- * Short alias for generateText() - CLI-SDK consistency
565
- * @param options - Text generation options
566
- * @returns Promise resolving to text generation result
819
+ * Validate if a provider name is supported
820
+ * @param providerName - Provider name to validate
821
+ * @returns True if provider name is valid
567
822
  */
568
- async gen(options) {
569
- return this.generateText(options);
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");
836
+ try {
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,
856
+ };
857
+ }
858
+ catch (error) {
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,
868
+ error: error instanceof Error ? error.message : String(error),
869
+ };
870
+ }
570
871
  }
571
872
  /**
572
- * Get the connection client for a specific MCP server
573
- * @param serverId - The ID of the server to get connection for
574
- * @returns Client connection object or undefined if not connected
873
+ * List all configured MCP servers with their status
874
+ * @returns Promise resolving to array of MCP server information
575
875
  */
576
- getConnection(serverId) {
577
- 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
+ }
578
895
  }
579
896
  /**
580
- * Check if a specific MCP server is currently connected
581
- * @param serverId - The ID of the server to check
582
- * @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
583
900
  */
584
- isConnected(serverId) {
585
- 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
+ }
586
909
  }
587
910
  }
911
+ // Create default instance
912
+ export const neurolink = new NeuroLink();
913
+ export default neurolink;