@juspay/neurolink 7.36.0 → 7.37.1

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 (87) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/commands/config.d.ts +18 -18
  3. package/dist/cli/factories/commandFactory.d.ts +24 -0
  4. package/dist/cli/factories/commandFactory.js +297 -245
  5. package/dist/config/taskClassificationConfig.d.ts +51 -0
  6. package/dist/config/taskClassificationConfig.js +148 -0
  7. package/dist/core/baseProvider.d.ts +40 -3
  8. package/dist/core/baseProvider.js +689 -352
  9. package/dist/core/constants.d.ts +2 -30
  10. package/dist/core/constants.js +15 -43
  11. package/dist/factories/providerFactory.js +23 -6
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.js +4 -3
  14. package/dist/lib/config/taskClassificationConfig.d.ts +51 -0
  15. package/dist/lib/config/taskClassificationConfig.js +148 -0
  16. package/dist/lib/core/baseProvider.d.ts +40 -3
  17. package/dist/lib/core/baseProvider.js +689 -352
  18. package/dist/lib/core/constants.d.ts +2 -30
  19. package/dist/lib/core/constants.js +15 -43
  20. package/dist/lib/factories/providerFactory.js +23 -6
  21. package/dist/lib/index.d.ts +3 -2
  22. package/dist/lib/index.js +4 -3
  23. package/dist/lib/mcp/externalServerManager.js +2 -2
  24. package/dist/lib/mcp/registry.js +2 -2
  25. package/dist/lib/mcp/servers/agent/directToolsServer.js +19 -10
  26. package/dist/lib/mcp/toolRegistry.js +4 -8
  27. package/dist/lib/neurolink.d.ts +82 -27
  28. package/dist/lib/neurolink.js +672 -713
  29. package/dist/lib/providers/amazonBedrock.js +2 -2
  30. package/dist/lib/providers/googleVertex.d.ts +3 -23
  31. package/dist/lib/providers/googleVertex.js +14 -342
  32. package/dist/lib/providers/openAI.d.ts +23 -0
  33. package/dist/lib/providers/openAI.js +313 -6
  34. package/dist/lib/providers/sagemaker/language-model.d.ts +2 -2
  35. package/dist/lib/sdk/toolRegistration.js +18 -1
  36. package/dist/lib/types/common.d.ts +98 -0
  37. package/dist/lib/types/index.d.ts +2 -0
  38. package/dist/lib/types/index.js +2 -0
  39. package/dist/lib/types/streamTypes.d.ts +13 -6
  40. package/dist/lib/types/taskClassificationTypes.d.ts +52 -0
  41. package/dist/lib/types/taskClassificationTypes.js +5 -0
  42. package/dist/lib/types/typeAliases.d.ts +3 -2
  43. package/dist/lib/utils/modelRouter.d.ts +107 -0
  44. package/dist/lib/utils/modelRouter.js +292 -0
  45. package/dist/lib/utils/parameterValidation.js +6 -25
  46. package/dist/lib/utils/promptRedaction.d.ts +29 -0
  47. package/dist/lib/utils/promptRedaction.js +62 -0
  48. package/dist/lib/utils/schemaConversion.d.ts +14 -0
  49. package/dist/lib/utils/schemaConversion.js +140 -0
  50. package/dist/lib/utils/taskClassificationUtils.d.ts +55 -0
  51. package/dist/lib/utils/taskClassificationUtils.js +149 -0
  52. package/dist/lib/utils/taskClassifier.d.ts +23 -0
  53. package/dist/lib/utils/taskClassifier.js +94 -0
  54. package/dist/lib/utils/transformationUtils.js +143 -5
  55. package/dist/mcp/externalServerManager.js +2 -2
  56. package/dist/mcp/registry.js +2 -2
  57. package/dist/mcp/servers/agent/directToolsServer.js +19 -10
  58. package/dist/mcp/toolRegistry.js +4 -8
  59. package/dist/neurolink.d.ts +82 -27
  60. package/dist/neurolink.js +672 -713
  61. package/dist/providers/amazonBedrock.js +2 -2
  62. package/dist/providers/googleVertex.d.ts +3 -23
  63. package/dist/providers/googleVertex.js +14 -342
  64. package/dist/providers/openAI.d.ts +23 -0
  65. package/dist/providers/openAI.js +313 -6
  66. package/dist/providers/sagemaker/language-model.d.ts +2 -2
  67. package/dist/sdk/toolRegistration.js +18 -1
  68. package/dist/types/common.d.ts +98 -0
  69. package/dist/types/index.d.ts +2 -0
  70. package/dist/types/index.js +2 -0
  71. package/dist/types/streamTypes.d.ts +13 -6
  72. package/dist/types/taskClassificationTypes.d.ts +52 -0
  73. package/dist/types/taskClassificationTypes.js +5 -0
  74. package/dist/types/typeAliases.d.ts +3 -2
  75. package/dist/utils/modelRouter.d.ts +107 -0
  76. package/dist/utils/modelRouter.js +292 -0
  77. package/dist/utils/parameterValidation.js +6 -25
  78. package/dist/utils/promptRedaction.d.ts +29 -0
  79. package/dist/utils/promptRedaction.js +62 -0
  80. package/dist/utils/schemaConversion.d.ts +14 -0
  81. package/dist/utils/schemaConversion.js +140 -0
  82. package/dist/utils/taskClassificationUtils.d.ts +55 -0
  83. package/dist/utils/taskClassificationUtils.js +149 -0
  84. package/dist/utils/taskClassifier.d.ts +23 -0
  85. package/dist/utils/taskClassifier.js +94 -0
  86. package/dist/utils/transformationUtils.js +143 -5
  87. package/package.json +3 -2
@@ -16,7 +16,7 @@ catch {
16
16
  import { AIProviderFactory } from "./core/factory.js";
17
17
  import { mcpLogger } from "./utils/logger.js";
18
18
  import { SYSTEM_LIMITS } from "./core/constants.js";
19
- import { NANOSECOND_TO_MS_DIVISOR, MCP_TIMEOUTS, SERVER_CONFIG, TOOL_TIMEOUTS, RETRY_ATTEMPTS, RETRY_DELAYS, CIRCUIT_BREAKER, CIRCUIT_BREAKER_RESET_MS, MEMORY_THRESHOLDS, PROVIDER_TIMEOUTS, PERFORMANCE_THRESHOLDS, } from "./constants/index.js";
19
+ import { NANOSECOND_TO_MS_DIVISOR, TOOL_TIMEOUTS, RETRY_ATTEMPTS, RETRY_DELAYS, CIRCUIT_BREAKER, CIRCUIT_BREAKER_RESET_MS, MEMORY_THRESHOLDS, PROVIDER_TIMEOUTS, PERFORMANCE_THRESHOLDS, } from "./constants/index.js";
20
20
  import pLimit from "p-limit";
21
21
  import { toolRegistry } from "./mcp/toolRegistry.js";
22
22
  import { logger } from "./utils/logger.js";
@@ -35,7 +35,11 @@ import { getConversationMessages, storeConversationTurn, } from "./utils/convers
35
35
  import { ExternalServerManager } from "./mcp/externalServerManager.js";
36
36
  // Import direct tools server for automatic registration
37
37
  import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
38
+ // Import orchestration components
39
+ import { ModelRouter } from "./utils/modelRouter.js";
40
+ import { BinaryTaskClassifier } from "./utils/taskClassifier.js";
38
41
  import { isNonNullObject } from "./utils/typeUtils.js";
42
+ import { isZodSchema } from "./utils/schemaConversion.js";
39
43
  // Core types imported from "./types/index.js"
40
44
  export class NeuroLink {
41
45
  mcpInitialized = false;
@@ -49,6 +53,9 @@ export class NeuroLink {
49
53
  // Enhanced error handling support
50
54
  toolCircuitBreakers = new Map();
51
55
  toolExecutionMetrics = new Map();
56
+ currentStreamToolExecutions = [];
57
+ toolExecutionHistory = [];
58
+ activeToolExecutions = new Map();
52
59
  /**
53
60
  * Helper method to emit tool end event in a consistent way
54
61
  * Used by executeTool in both success and error paths
@@ -75,6 +82,13 @@ export class NeuroLink {
75
82
  conversationMemory;
76
83
  conversationMemoryNeedsInit = false;
77
84
  conversationMemoryConfig;
85
+ // Add orchestration property
86
+ enableOrchestration;
87
+ /**
88
+ * Context storage for tool execution
89
+ * This context will be merged with any runtime context passed by the AI model
90
+ */
91
+ toolExecutionContext;
78
92
  /**
79
93
  * Creates a new NeuroLink instance for AI text generation with MCP tool integration.
80
94
  *
@@ -83,6 +97,7 @@ export class NeuroLink {
83
97
  * @param config.conversationMemory.enabled - Whether to enable conversation memory (default: false)
84
98
  * @param config.conversationMemory.maxSessions - Maximum number of concurrent sessions (default: 100)
85
99
  * @param config.conversationMemory.maxTurnsPerSession - Maximum conversation turns per session (default: 50)
100
+ * @param config.enableOrchestration - Whether to enable smart model orchestration (default: false)
86
101
  *
87
102
  * @example
88
103
  * ```typescript
@@ -97,6 +112,11 @@ export class NeuroLink {
97
112
  * maxTurnsPerSession: 20
98
113
  * }
99
114
  * });
115
+ *
116
+ * // With orchestration enabled
117
+ * const neurolink = new NeuroLink({
118
+ * enableOrchestration: true
119
+ * });
100
120
  * ```
101
121
  *
102
122
  * @throws {Error} When provider registry setup fails
@@ -104,6 +124,8 @@ export class NeuroLink {
104
124
  * @throws {Error} When external server manager initialization fails
105
125
  */
106
126
  constructor(config) {
127
+ // Initialize orchestration setting
128
+ this.enableOrchestration = config?.enableOrchestration ?? false;
107
129
  // Read tool cache duration from environment variables, with a default
108
130
  const cacheDurationEnv = process.env.NEUROLINK_TOOL_CACHE_DURATION;
109
131
  this.toolCacheDuration = cacheDurationEnv
@@ -112,43 +134,11 @@ export class NeuroLink {
112
134
  const constructorStartTime = Date.now();
113
135
  const constructorHrTimeStart = process.hrtime.bigint();
114
136
  const constructorId = `neurolink-constructor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
115
- this.logConstructorStart(constructorId, constructorStartTime, constructorHrTimeStart, config);
116
137
  this.initializeProviderRegistry(constructorId, constructorStartTime, constructorHrTimeStart);
117
138
  this.initializeConversationMemory(config, constructorId, constructorStartTime, constructorHrTimeStart);
118
139
  this.initializeExternalServerManager(constructorId, constructorStartTime, constructorHrTimeStart);
119
140
  this.logConstructorComplete(constructorId, constructorStartTime, constructorHrTimeStart);
120
141
  }
121
- /**
122
- * Log constructor start with comprehensive environment analysis
123
- */
124
- logConstructorStart(constructorId, constructorStartTime, constructorHrTimeStart, config) {
125
- logger.debug(`[NeuroLink] 🏗️ LOG_POINT_C001_CONSTRUCTOR_START`, {
126
- logPoint: "C001_CONSTRUCTOR_START",
127
- constructorId,
128
- timestamp: new Date().toISOString(),
129
- constructorStartTime,
130
- constructorHrTimeStart: constructorHrTimeStart.toString(),
131
- hasConfig: !!config,
132
- configType: typeof config,
133
- configKeys: config ? Object.keys(config) : [],
134
- configSize: config ? JSON.stringify(config).length : 0,
135
- hasConversationMemoryConfig: !!config?.conversationMemory,
136
- conversationMemoryEnabled: config?.conversationMemory?.enabled || false,
137
- conversationMemoryKeys: config?.conversationMemory
138
- ? Object.keys(config.conversationMemory)
139
- : [],
140
- nodeVersion: process.version,
141
- platform: process.platform,
142
- arch: process.arch,
143
- nodeEnv: process.env.NODE_ENV || "UNKNOWN",
144
- memoryUsage: process.memoryUsage(),
145
- cpuUsage: process.cpuUsage(),
146
- uptime: process.uptime(),
147
- pid: process.pid,
148
- ppid: process.ppid,
149
- message: "NeuroLink constructor initialization starting with comprehensive environment analysis",
150
- });
151
- }
152
142
  /**
153
143
  * Initialize provider registry with security settings
154
144
  */
@@ -163,40 +153,7 @@ export class NeuroLink {
163
153
  registrySetupStartTimeNs: registrySetupStartTime.toString(),
164
154
  message: "Starting ProviderRegistry configuration for security",
165
155
  });
166
- try {
167
- ProviderRegistry.setOptions({ enableManualMCP: false });
168
- const registrySetupEndTime = process.hrtime.bigint();
169
- const registrySetupDurationNs = registrySetupEndTime - registrySetupStartTime;
170
- logger.debug(`[NeuroLink] ✅ LOG_POINT_C003_PROVIDER_REGISTRY_SETUP_SUCCESS`, {
171
- logPoint: "C003_PROVIDER_REGISTRY_SETUP_SUCCESS",
172
- constructorId,
173
- timestamp: new Date().toISOString(),
174
- elapsedMs: Date.now() - constructorStartTime,
175
- elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
176
- registrySetupDurationNs: registrySetupDurationNs.toString(),
177
- registrySetupDurationMs: Number(registrySetupDurationNs) / NANOSECOND_TO_MS_DIVISOR,
178
- enableManualMCP: false,
179
- message: "ProviderRegistry configured successfully with security settings",
180
- });
181
- }
182
- catch (error) {
183
- const registrySetupErrorTime = process.hrtime.bigint();
184
- const registrySetupDurationNs = registrySetupErrorTime - registrySetupStartTime;
185
- logger.error(`[NeuroLink] ❌ LOG_POINT_C004_PROVIDER_REGISTRY_SETUP_ERROR`, {
186
- logPoint: "C004_PROVIDER_REGISTRY_SETUP_ERROR",
187
- constructorId,
188
- timestamp: new Date().toISOString(),
189
- elapsedMs: Date.now() - constructorStartTime,
190
- elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
191
- registrySetupDurationNs: registrySetupDurationNs.toString(),
192
- registrySetupDurationMs: Number(registrySetupDurationNs) / NANOSECOND_TO_MS_DIVISOR,
193
- error: error instanceof Error ? error.message : String(error),
194
- errorName: error instanceof Error ? error.name : "UnknownError",
195
- errorStack: error instanceof Error ? error.stack : undefined,
196
- message: "ProviderRegistry setup failed - critical initialization error",
197
- });
198
- throw error;
199
- }
156
+ ProviderRegistry.setOptions({ enableManualMCP: false });
200
157
  }
201
158
  /**
202
159
  * Initialize conversation memory if enabled
@@ -204,21 +161,6 @@ export class NeuroLink {
204
161
  initializeConversationMemory(config, constructorId, constructorStartTime, constructorHrTimeStart) {
205
162
  if (config?.conversationMemory?.enabled) {
206
163
  const memoryInitStartTime = process.hrtime.bigint();
207
- logger.debug(`[NeuroLink] 🧠 LOG_POINT_C005_MEMORY_INIT_START`, {
208
- logPoint: "C005_MEMORY_INIT_START",
209
- constructorId,
210
- timestamp: new Date().toISOString(),
211
- elapsedMs: Date.now() - constructorStartTime,
212
- elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
213
- memoryInitStartTimeNs: memoryInitStartTime.toString(),
214
- memoryConfig: {
215
- enabled: config.conversationMemory.enabled,
216
- maxSessions: config.conversationMemory.maxSessions,
217
- maxTurnsPerSession: config.conversationMemory.maxTurnsPerSession,
218
- keys: Object.keys(config.conversationMemory),
219
- },
220
- message: "Conversation memory initialization flag set for lazy loading",
221
- });
222
164
  // Store config for later use and set flag for lazy initialization
223
165
  this.conversationMemoryConfig = config;
224
166
  this.conversationMemoryNeedsInit = true;
@@ -261,24 +203,6 @@ export class NeuroLink {
261
203
  */
262
204
  initializeExternalServerManager(constructorId, constructorStartTime, constructorHrTimeStart) {
263
205
  const externalServerInitStartTime = process.hrtime.bigint();
264
- logger.debug(`[NeuroLink] 🌐 LOG_POINT_C009_EXTERNAL_SERVER_INIT_START`, {
265
- logPoint: "C009_EXTERNAL_SERVER_INIT_START",
266
- constructorId,
267
- timestamp: new Date().toISOString(),
268
- elapsedMs: Date.now() - constructorStartTime,
269
- elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
270
- externalServerInitStartTimeNs: externalServerInitStartTime.toString(),
271
- serverManagerConfig: {
272
- maxServers: SERVER_CONFIG.MAX_MCP_SERVERS,
273
- defaultTimeout: MCP_TIMEOUTS.EXTERNAL_SERVER_STARTUP_MS,
274
- enableAutoRestart: true,
275
- enablePerformanceMonitoring: true,
276
- },
277
- registryIntegrationConfig: {
278
- enableMainRegistryIntegration: true,
279
- },
280
- message: "Starting external server manager initialization",
281
- });
282
206
  try {
283
207
  this.externalServerManager = new ExternalServerManager({
284
208
  maxServers: 20,
@@ -301,7 +225,7 @@ export class NeuroLink {
301
225
  hasExternalServerManager: !!this.externalServerManager,
302
226
  message: "External server manager initialized successfully",
303
227
  });
304
- this.setupExternalServerEventHandlers(constructorId, constructorStartTime, constructorHrTimeStart);
228
+ this.setupExternalServerEventHandlers(constructorId);
305
229
  }
306
230
  catch (error) {
307
231
  const externalServerInitErrorTime = process.hrtime.bigint();
@@ -325,17 +249,7 @@ export class NeuroLink {
325
249
  /**
326
250
  * Setup event handlers for external server manager
327
251
  */
328
- setupExternalServerEventHandlers(constructorId, constructorStartTime, constructorHrTimeStart) {
329
- const eventHandlerSetupStartTime = process.hrtime.bigint();
330
- logger.debug(`[NeuroLink] 🔗 LOG_POINT_C011_EVENT_HANDLER_SETUP_START`, {
331
- logPoint: "C011_EVENT_HANDLER_SETUP_START",
332
- constructorId,
333
- timestamp: new Date().toISOString(),
334
- elapsedMs: Date.now() - constructorStartTime,
335
- elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
336
- eventHandlerSetupStartTimeNs: eventHandlerSetupStartTime.toString(),
337
- message: "Setting up external server event handlers",
338
- });
252
+ setupExternalServerEventHandlers(constructorId) {
339
253
  this.externalServerManager.on("connected", (event) => {
340
254
  logger.debug(`[NeuroLink] 🔗 EXTERNAL_SERVER_EVENT_CONNECTED`, {
341
255
  constructorId,
@@ -389,26 +303,6 @@ export class NeuroLink {
389
303
  this.emitter.emit("externalMCP:toolRemoved", event);
390
304
  this.unregisterExternalMCPToolFromRegistry(event.toolName);
391
305
  });
392
- const eventHandlerSetupEndTime = process.hrtime.bigint();
393
- const eventHandlerSetupDurationNs = eventHandlerSetupEndTime - eventHandlerSetupStartTime;
394
- logger.debug(`[NeuroLink] ✅ LOG_POINT_C012_EVENT_HANDLER_SETUP_SUCCESS`, {
395
- logPoint: "C012_EVENT_HANDLER_SETUP_SUCCESS",
396
- constructorId,
397
- timestamp: new Date().toISOString(),
398
- elapsedMs: Date.now() - constructorStartTime,
399
- elapsedNs: (process.hrtime.bigint() - constructorHrTimeStart).toString(),
400
- eventHandlerSetupDurationNs: eventHandlerSetupDurationNs.toString(),
401
- eventHandlerSetupDurationMs: Number(eventHandlerSetupDurationNs) / NANOSECOND_TO_MS_DIVISOR,
402
- eventHandlersCount: 5,
403
- eventHandlerTypes: [
404
- "connected",
405
- "disconnected",
406
- "failed",
407
- "toolDiscovered",
408
- "toolRemoved",
409
- ],
410
- message: "Event handlers set up successfully",
411
- });
412
306
  }
413
307
  /**
414
308
  * Log constructor completion with final state summary
@@ -416,7 +310,7 @@ export class NeuroLink {
416
310
  logConstructorComplete(constructorId, constructorStartTime, constructorHrTimeStart) {
417
311
  const constructorEndTime = process.hrtime.bigint();
418
312
  const constructorDurationNs = constructorEndTime - constructorHrTimeStart;
419
- logger.info(`[NeuroLink] 🏁 LOG_POINT_C014_CONSTRUCTOR_COMPLETE`, {
313
+ logger.debug(`🏁 LOG_POINT_C014_CONSTRUCTOR_COMPLETE`, {
420
314
  logPoint: "C014_CONSTRUCTOR_COMPLETE",
421
315
  constructorId,
422
316
  timestamp: new Date().toISOString(),
@@ -444,11 +338,6 @@ export class NeuroLink {
444
338
  const mcpInitId = `mcp-init-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
445
339
  const mcpInitStartTime = Date.now();
446
340
  const mcpInitHrTimeStart = process.hrtime.bigint();
447
- this.logMCPInitStart(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart);
448
- if (this.mcpInitialized) {
449
- this.logMCPAlreadyInitialized(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart);
450
- return;
451
- }
452
341
  const MemoryManager = await this.importPerformanceManager(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart);
453
342
  const startMemory = MemoryManager
454
343
  ? MemoryManager.getMemoryUsageMB()
@@ -459,73 +348,31 @@ export class NeuroLink {
459
348
  this.logMCPInitComplete(startMemory, MemoryManager, mcpInitStartTime);
460
349
  }
461
350
  catch (error) {
351
+ const initializationTime = Date.now() - mcpInitStartTime;
352
+ const initializationTimeNs = process.hrtime.bigint() - mcpInitHrTimeStart;
462
353
  mcpLogger.warn("[NeuroLink] MCP initialization failed", {
354
+ mcpInitId,
463
355
  error: error instanceof Error ? error.message : String(error),
356
+ errorName: error instanceof Error ? error.name : "UnknownError",
357
+ errorStack: error instanceof Error ? error.stack : undefined,
358
+ initializationTime,
359
+ initializationTimeNs: initializationTimeNs.toString(),
360
+ initializationPhase: "performMCPInitialization",
361
+ memoryUsage: process.memoryUsage(),
362
+ timestamp: new Date().toISOString(),
363
+ gracefulDegradation: true,
464
364
  });
465
365
  // Continue without MCP - graceful degradation
466
366
  }
467
367
  }
468
- /**
469
- * Log MCP initialization start
470
- */
471
- logMCPInitStart(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart) {
472
- logger.debug(`[NeuroLink] 🔧 LOG_POINT_M001_MCP_INIT_ENTRY`, {
473
- logPoint: "M001_MCP_INIT_ENTRY",
474
- mcpInitId,
475
- timestamp: new Date().toISOString(),
476
- mcpInitStartTime,
477
- mcpInitHrTimeStart: mcpInitHrTimeStart.toString(),
478
- mcpInitialized: this.mcpInitialized,
479
- hasExternalServerManager: !!this.externalServerManager,
480
- memoryUsage: process.memoryUsage(),
481
- cpuUsage: process.cpuUsage(),
482
- message: "MCP initialization entry point - checking if already initialized",
483
- });
484
- }
485
- /**
486
- * Log MCP already initialized
487
- */
488
- logMCPAlreadyInitialized(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart) {
489
- logger.debug(`[NeuroLink] ✅ LOG_POINT_M002_MCP_ALREADY_INITIALIZED`, {
490
- logPoint: "M002_MCP_ALREADY_INITIALIZED",
491
- mcpInitId,
492
- timestamp: new Date().toISOString(),
493
- elapsedMs: Date.now() - mcpInitStartTime,
494
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
495
- mcpInitialized: this.mcpInitialized,
496
- message: "MCP already initialized - skipping initialization",
497
- });
498
- }
499
368
  /**
500
369
  * Import performance manager with error handling
501
370
  */
502
371
  async importPerformanceManager(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart) {
503
372
  const performanceImportStartTime = process.hrtime.bigint();
504
- logger.debug(`[NeuroLink] 📊 LOG_POINT_M003_PERFORMANCE_IMPORT_START`, {
505
- logPoint: "M003_PERFORMANCE_IMPORT_START",
506
- mcpInitId,
507
- timestamp: new Date().toISOString(),
508
- elapsedMs: Date.now() - mcpInitStartTime,
509
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
510
- performanceImportStartTimeNs: performanceImportStartTime.toString(),
511
- message: "Starting MemoryManager import for performance tracking",
512
- });
513
373
  try {
514
374
  const moduleImport = await import("./utils/performance.js");
515
375
  const MemoryManager = moduleImport.MemoryManager;
516
- const performanceImportEndTime = process.hrtime.bigint();
517
- const performanceImportDurationNs = performanceImportEndTime - performanceImportStartTime;
518
- logger.debug(`[NeuroLink] ✅ LOG_POINT_M004_PERFORMANCE_IMPORT_SUCCESS`, {
519
- logPoint: "M004_PERFORMANCE_IMPORT_SUCCESS",
520
- mcpInitId,
521
- timestamp: new Date().toISOString(),
522
- elapsedMs: Date.now() - mcpInitStartTime,
523
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
524
- performanceImportDurationNs: performanceImportDurationNs.toString(),
525
- performanceImportDurationMs: Number(performanceImportDurationNs) / NANOSECOND_TO_MS_DIVISOR,
526
- hasMemoryManager: !!MemoryManager,
527
- message: "MemoryManager imported successfully",
528
- });
529
376
  return MemoryManager;
530
377
  }
531
378
  catch (error) {
@@ -560,107 +407,44 @@ export class NeuroLink {
560
407
  message: "Starting isolated MCP initialization process",
561
408
  });
562
409
  mcpLogger.debug("[NeuroLink] Starting isolated MCP initialization...");
563
- await this.initializeToolRegistryInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart);
564
- await this.initializeProviderRegistryInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart);
410
+ await this.initializeToolRegistryInternal();
411
+ await this.initializeProviderRegistryInternal();
565
412
  await this.registerDirectToolsServerInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart);
566
- await this.loadMCPConfigurationInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart);
413
+ await this.loadMCPConfigurationInternal();
567
414
  }
568
415
  /**
569
416
  * Initialize tool registry with timeout protection
570
417
  */
571
- async initializeToolRegistryInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart) {
572
- const toolRegistryStartTime = process.hrtime.bigint();
573
- const initTimeout = MCP_TIMEOUTS.INITIALIZATION_MS;
574
- logger.debug(`[NeuroLink] ⏱️ LOG_POINT_M007_TOOL_REGISTRY_TIMEOUT_SETUP`, {
575
- logPoint: "M007_TOOL_REGISTRY_TIMEOUT_SETUP",
576
- mcpInitId,
577
- timestamp: new Date().toISOString(),
578
- elapsedMs: Date.now() - mcpInitStartTime,
579
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
580
- toolRegistryStartTimeNs: toolRegistryStartTime.toString(),
581
- initTimeoutMs: initTimeout,
582
- message: "Setting up tool registry initialization with timeout protection",
583
- });
418
+ async initializeToolRegistryInternal() {
419
+ const initTimeout = 3000;
584
420
  await Promise.race([
585
421
  Promise.resolve(),
586
422
  new Promise((_, reject) => {
587
423
  setTimeout(() => reject(new Error("MCP initialization timeout")), initTimeout);
588
424
  }),
589
425
  ]);
590
- const toolRegistryEndTime = process.hrtime.bigint();
591
- const toolRegistryDurationNs = toolRegistryEndTime - toolRegistryStartTime;
592
- logger.debug(`[NeuroLink] ✅ LOG_POINT_M008_TOOL_REGISTRY_SUCCESS`, {
593
- logPoint: "M008_TOOL_REGISTRY_SUCCESS",
594
- mcpInitId,
595
- timestamp: new Date().toISOString(),
596
- elapsedMs: Date.now() - mcpInitStartTime,
597
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
598
- toolRegistryDurationNs: toolRegistryDurationNs.toString(),
599
- toolRegistryDurationMs: Number(toolRegistryDurationNs) / NANOSECOND_TO_MS_DIVISOR,
600
- message: "Tool registry initialization completed within timeout",
601
- });
602
426
  }
603
427
  /**
604
428
  * Initialize provider registry
605
429
  */
606
- async initializeProviderRegistryInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart) {
607
- const providerRegistryStartTime = process.hrtime.bigint();
608
- logger.debug(`[NeuroLink] 🏭 LOG_POINT_M009_PROVIDER_REGISTRY_START`, {
609
- logPoint: "M009_PROVIDER_REGISTRY_START",
610
- mcpInitId,
611
- timestamp: new Date().toISOString(),
612
- elapsedMs: Date.now() - mcpInitStartTime,
613
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
614
- providerRegistryStartTimeNs: providerRegistryStartTime.toString(),
615
- message: "Starting provider registry registration with lazy loading",
616
- });
430
+ async initializeProviderRegistryInternal() {
617
431
  await ProviderRegistry.registerAllProviders();
618
- const providerRegistryEndTime = process.hrtime.bigint();
619
- const providerRegistryDurationNs = providerRegistryEndTime - providerRegistryStartTime;
620
- logger.debug(`[NeuroLink] ✅ LOG_POINT_M010_PROVIDER_REGISTRY_SUCCESS`, {
621
- logPoint: "M010_PROVIDER_REGISTRY_SUCCESS",
622
- mcpInitId,
623
- timestamp: new Date().toISOString(),
624
- elapsedMs: Date.now() - mcpInitStartTime,
625
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
626
- providerRegistryDurationNs: providerRegistryDurationNs.toString(),
627
- providerRegistryDurationMs: Number(providerRegistryDurationNs) / NANOSECOND_TO_MS_DIVISOR,
628
- message: "Provider registry registration completed successfully",
629
- });
630
432
  }
631
433
  /**
632
434
  * Register direct tools server
633
435
  */
634
436
  async registerDirectToolsServerInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart) {
635
437
  const directToolsStartTime = process.hrtime.bigint();
636
- logger.debug(`[NeuroLink] 🛠️ LOG_POINT_M011_DIRECT_TOOLS_START`, {
637
- logPoint: "M011_DIRECT_TOOLS_START",
638
- mcpInitId,
639
- timestamp: new Date().toISOString(),
640
- elapsedMs: Date.now() - mcpInitStartTime,
641
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
642
- directToolsStartTimeNs: directToolsStartTime.toString(),
643
- serverId: "neurolink-direct",
644
- message: "Starting direct tools server registration",
645
- });
646
438
  try {
647
- await toolRegistry.registerServer("neurolink-direct", directToolsServer);
648
- const directToolsSuccessTime = process.hrtime.bigint();
649
- const directToolsDurationNs = directToolsSuccessTime - directToolsStartTime;
650
- logger.debug(`[NeuroLink] ✅ LOG_POINT_M012_DIRECT_TOOLS_SUCCESS`, {
651
- logPoint: "M012_DIRECT_TOOLS_SUCCESS",
652
- mcpInitId,
653
- timestamp: new Date().toISOString(),
654
- elapsedMs: Date.now() - mcpInitStartTime,
655
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
656
- directToolsDurationNs: directToolsDurationNs.toString(),
657
- directToolsDurationMs: Number(directToolsDurationNs) / NANOSECOND_TO_MS_DIVISOR,
658
- serverId: "neurolink-direct",
659
- message: "Direct tools server registered successfully",
660
- });
661
- mcpLogger.debug("[NeuroLink] Direct tools server registered successfully", {
662
- serverId: "neurolink-direct",
663
- });
439
+ if (process.env.NEUROLINK_DISABLE_DIRECT_TOOLS === "true") {
440
+ mcpLogger.debug("Direct tools server are disabled via environment variable.");
441
+ }
442
+ else {
443
+ await toolRegistry.registerServer("neurolink-direct", directToolsServer);
444
+ mcpLogger.debug("[NeuroLink] Direct tools server registered successfully", {
445
+ serverId: "neurolink-direct",
446
+ });
447
+ }
664
448
  }
665
449
  catch (error) {
666
450
  const directToolsErrorTime = process.hrtime.bigint();
@@ -687,42 +471,10 @@ export class NeuroLink {
687
471
  /**
688
472
  * Load MCP configuration from .mcp-config.json with parallel loading for improved performance
689
473
  */
690
- async loadMCPConfigurationInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart) {
691
- const mcpConfigStartTime = process.hrtime.bigint();
692
- logger.debug(`[NeuroLink] 📄 LOG_POINT_M014_MCP_CONFIG_START`, {
693
- logPoint: "M014_MCP_CONFIG_START",
694
- mcpInitId,
695
- timestamp: new Date().toISOString(),
696
- elapsedMs: Date.now() - mcpInitStartTime,
697
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
698
- mcpConfigStartTimeNs: mcpConfigStartTime.toString(),
699
- hasExternalServerManager: !!this.externalServerManager,
700
- message: "Starting MCP configuration loading from .mcp-config.json",
701
- });
474
+ async loadMCPConfigurationInternal() {
702
475
  try {
703
476
  const configResult = await this.externalServerManager.loadMCPConfiguration(undefined, // Use default config path
704
477
  { parallel: true });
705
- const mcpConfigSuccessTime = process.hrtime.bigint();
706
- const mcpConfigDurationNs = mcpConfigSuccessTime - mcpConfigStartTime;
707
- logger.debug(`[NeuroLink] ✅ LOG_POINT_M015_MCP_CONFIG_SUCCESS`, {
708
- logPoint: "M015_MCP_CONFIG_SUCCESS",
709
- mcpInitId,
710
- timestamp: new Date().toISOString(),
711
- elapsedMs: Date.now() - mcpInitStartTime,
712
- elapsedNs: (process.hrtime.bigint() - mcpInitHrTimeStart).toString(),
713
- mcpConfigDurationNs: mcpConfigDurationNs.toString(),
714
- mcpConfigDurationMs: Number(mcpConfigDurationNs) / NANOSECOND_TO_MS_DIVISOR,
715
- serversLoaded: configResult.serversLoaded,
716
- errorsCount: configResult.errors.length,
717
- configResult: {
718
- serversLoaded: configResult.serversLoaded,
719
- errors: configResult.errors.map((err) => ({
720
- message: err instanceof Error ? err.message : String(err),
721
- name: err instanceof Error ? err.name : "UnknownError",
722
- })),
723
- },
724
- message: "MCP configuration loaded successfully",
725
- });
726
478
  mcpLogger.debug("[NeuroLink] MCP configuration loaded successfully", {
727
479
  serversLoaded: configResult.serversLoaded,
728
480
  errors: configResult.errors.length,
@@ -758,6 +510,234 @@ export class NeuroLink {
758
510
  mcpLogger.debug("💡 Memory cleanup suggestion: MCP initialization used significant memory. Consider calling MemoryManager.forceGC() after heavy operations.");
759
511
  }
760
512
  }
513
+ /**
514
+ * Apply orchestration to determine optimal provider and model
515
+ * @param options - Original GenerateOptions
516
+ * @returns Modified options with orchestrated provider marked in context, or empty object if validation fails
517
+ */
518
+ async applyOrchestration(options) {
519
+ const startTime = Date.now();
520
+ try {
521
+ // Ensure input.text exists before proceeding
522
+ if (!options.input?.text || typeof options.input.text !== "string") {
523
+ logger.debug("Orchestration skipped - no valid input text", {
524
+ hasInput: !!options.input,
525
+ hasText: !!options.input?.text,
526
+ textType: typeof options.input?.text,
527
+ });
528
+ return {}; // Return empty object to preserve existing fallback behavior
529
+ }
530
+ // Compute classification once to avoid duplicate calls
531
+ const classification = BinaryTaskClassifier.classify(options.input.text);
532
+ // Use the model router to get the optimal route
533
+ const route = ModelRouter.route(options.input.text);
534
+ // Validate that the routed provider is available and configured
535
+ const isProviderAvailable = await this.hasProviderEnvVars(route.provider);
536
+ if (!isProviderAvailable && route.provider !== "ollama") {
537
+ logger.debug("Orchestration provider validation failed", {
538
+ taskType: classification.type,
539
+ routedProvider: route.provider,
540
+ routedModel: route.model,
541
+ reason: "Provider not configured or missing environment variables",
542
+ orchestrationTime: `${Date.now() - startTime}ms`,
543
+ });
544
+ return {}; // Return empty object to preserve existing fallback behavior
545
+ }
546
+ // For Ollama, check if service is running and model is available
547
+ if (route.provider === "ollama") {
548
+ try {
549
+ const response = await fetch("http://localhost:11434/api/tags", {
550
+ method: "GET",
551
+ signal: AbortSignal.timeout(2000),
552
+ });
553
+ if (!response.ok) {
554
+ logger.debug("Orchestration provider validation failed", {
555
+ taskType: classification.type,
556
+ routedProvider: route.provider,
557
+ routedModel: route.model,
558
+ reason: "Ollama service not responding",
559
+ orchestrationTime: `${Date.now() - startTime}ms`,
560
+ });
561
+ return {}; // Return empty object to preserve existing fallback behavior
562
+ }
563
+ const responseData = await response.json();
564
+ const models = responseData?.models;
565
+ // Runtime-safe guard: ensure models is an array with valid objects
566
+ if (!Array.isArray(models)) {
567
+ logger.warn("Ollama API returned invalid models format", {
568
+ responseData,
569
+ modelsType: typeof models,
570
+ });
571
+ return {}; // Return empty object for fallback behavior
572
+ }
573
+ // Filter and validate models before comparison
574
+ const validModels = models.filter((m) => m && typeof m === "object" && typeof m.name === "string");
575
+ const targetModel = route.model || "llama3.2:latest";
576
+ const modelIsAvailable = validModels.some((m) => m.name === targetModel);
577
+ if (!modelIsAvailable) {
578
+ logger.debug("Orchestration provider validation failed", {
579
+ taskType: classification.type,
580
+ routedProvider: route.provider,
581
+ routedModel: route.model,
582
+ reason: `Ollama model '${route.model || "llama3.2:latest"}' not found`,
583
+ orchestrationTime: `${Date.now() - startTime}ms`,
584
+ });
585
+ return {}; // Return empty object to preserve existing fallback behavior
586
+ }
587
+ }
588
+ catch (error) {
589
+ logger.debug("Orchestration provider validation failed", {
590
+ taskType: classification.type,
591
+ routedProvider: route.provider,
592
+ routedModel: route.model,
593
+ reason: error instanceof Error
594
+ ? error.message
595
+ : "Ollama service check failed",
596
+ orchestrationTime: `${Date.now() - startTime}ms`,
597
+ });
598
+ return {}; // Return empty object to preserve existing fallback behavior
599
+ }
600
+ }
601
+ logger.debug("Orchestration route determined", {
602
+ taskType: classification.type,
603
+ selectedProvider: route.provider,
604
+ selectedModel: route.model,
605
+ confidence: route.confidence,
606
+ reasoning: route.reasoning,
607
+ orchestrationTime: `${Date.now() - startTime}ms`,
608
+ });
609
+ // Mark preferred provider in context instead of directly setting provider
610
+ // This preserves global fallback behavior while indicating orchestration preference
611
+ return {
612
+ model: route.model,
613
+ context: {
614
+ ...(options.context || {}),
615
+ __orchestratedPreferredProvider: route.provider,
616
+ },
617
+ };
618
+ }
619
+ catch (error) {
620
+ logger.error("Orchestration failed", {
621
+ error: error instanceof Error ? error.message : String(error),
622
+ orchestrationTime: `${Date.now() - startTime}ms`,
623
+ });
624
+ throw error;
625
+ }
626
+ }
627
+ /**
628
+ * Apply orchestration to determine optimal provider and model for streaming
629
+ * @param options - Original StreamOptions
630
+ * @returns Modified options with orchestrated provider marked in context, or empty object if validation fails
631
+ */
632
+ async applyStreamOrchestration(options) {
633
+ const startTime = Date.now();
634
+ try {
635
+ // Ensure input.text exists before proceeding
636
+ if (!options.input?.text || typeof options.input.text !== "string") {
637
+ logger.debug("Stream orchestration skipped - no valid input text", {
638
+ hasInput: !!options.input,
639
+ hasText: !!options.input?.text,
640
+ textType: typeof options.input?.text,
641
+ });
642
+ return {}; // Return empty object to preserve existing fallback behavior
643
+ }
644
+ // Compute classification once to avoid duplicate calls
645
+ const classification = BinaryTaskClassifier.classify(options.input.text);
646
+ // Use the model router to get the optimal route
647
+ const route = ModelRouter.route(options.input.text);
648
+ // Validate that the routed provider is available and configured
649
+ const isProviderAvailable = await this.hasProviderEnvVars(route.provider);
650
+ if (!isProviderAvailable && route.provider !== "ollama") {
651
+ logger.debug("Stream orchestration provider validation failed", {
652
+ taskType: classification.type,
653
+ routedProvider: route.provider,
654
+ routedModel: route.model,
655
+ reason: "Provider not configured or missing environment variables",
656
+ orchestrationTime: `${Date.now() - startTime}ms`,
657
+ });
658
+ return {}; // Return empty object to preserve existing fallback behavior
659
+ }
660
+ // For Ollama, check if service is running and model is available
661
+ if (route.provider === "ollama") {
662
+ try {
663
+ const response = await fetch("http://localhost:11434/api/tags", {
664
+ method: "GET",
665
+ signal: AbortSignal.timeout(2000),
666
+ });
667
+ if (!response.ok) {
668
+ logger.debug("Stream orchestration provider validation failed", {
669
+ taskType: classification.type,
670
+ routedProvider: route.provider,
671
+ routedModel: route.model,
672
+ reason: "Ollama service not responding",
673
+ orchestrationTime: `${Date.now() - startTime}ms`,
674
+ });
675
+ return {}; // Return empty object to preserve existing fallback behavior
676
+ }
677
+ const responseData = await response.json();
678
+ const models = responseData?.models;
679
+ // Runtime-safe guard: ensure models is an array with valid objects
680
+ if (!Array.isArray(models)) {
681
+ logger.warn("Ollama API returned invalid models format in stream", {
682
+ responseData,
683
+ modelsType: typeof models,
684
+ });
685
+ return {}; // Return empty object for fallback behavior
686
+ }
687
+ // Filter and validate models before comparison
688
+ const validModels = models.filter((m) => m && typeof m === "object" && typeof m.name === "string");
689
+ const targetModel = route.model || "llama3.2:latest";
690
+ const modelIsAvailable = validModels.some((m) => m.name === targetModel);
691
+ if (!modelIsAvailable) {
692
+ logger.debug("Stream orchestration provider validation failed", {
693
+ taskType: classification.type,
694
+ routedProvider: route.provider,
695
+ routedModel: route.model,
696
+ reason: `Ollama model '${route.model || "llama3.2:latest"}' not found`,
697
+ orchestrationTime: `${Date.now() - startTime}ms`,
698
+ });
699
+ return {}; // Return empty object to preserve existing fallback behavior
700
+ }
701
+ }
702
+ catch (error) {
703
+ logger.debug("Stream orchestration provider validation failed", {
704
+ taskType: classification.type,
705
+ routedProvider: route.provider,
706
+ routedModel: route.model,
707
+ reason: error instanceof Error
708
+ ? error.message
709
+ : "Ollama service check failed",
710
+ orchestrationTime: `${Date.now() - startTime}ms`,
711
+ });
712
+ return {}; // Return empty object to preserve existing fallback behavior
713
+ }
714
+ }
715
+ logger.debug("Stream orchestration route determined", {
716
+ taskType: classification.type,
717
+ selectedProvider: route.provider,
718
+ selectedModel: route.model,
719
+ confidence: route.confidence,
720
+ reasoning: route.reasoning,
721
+ orchestrationTime: `${Date.now() - startTime}ms`,
722
+ });
723
+ // Mark preferred provider in context instead of directly setting provider
724
+ // This preserves global fallback behavior while indicating orchestration preference
725
+ return {
726
+ model: route.model,
727
+ context: {
728
+ ...(options.context || {}),
729
+ __orchestratedPreferredProvider: route.provider,
730
+ },
731
+ };
732
+ }
733
+ catch (error) {
734
+ logger.error("Stream orchestration failed", {
735
+ error: error instanceof Error ? error.message : String(error),
736
+ orchestrationTime: `${Date.now() - startTime}ms`,
737
+ });
738
+ throw error;
739
+ }
740
+ }
761
741
  /**
762
742
  * MAIN ENTRY POINT: Enhanced generate method with new function signature
763
743
  * Replaces both generateText and legacy methods
@@ -833,6 +813,27 @@ export class NeuroLink {
833
813
  throw new Error("Input text is required and must be a non-empty string");
834
814
  }
835
815
  const startTime = Date.now();
816
+ // Apply orchestration if enabled and no specific provider/model requested
817
+ if (this.enableOrchestration && !options.provider && !options.model) {
818
+ try {
819
+ const orchestratedOptions = await this.applyOrchestration(options);
820
+ logger.debug("Orchestration applied", {
821
+ originalProvider: options.provider || "auto",
822
+ orchestratedProvider: orchestratedOptions.provider,
823
+ orchestratedModel: orchestratedOptions.model,
824
+ prompt: options.input.text.substring(0, 100),
825
+ });
826
+ // Use orchestrated options
827
+ Object.assign(options, orchestratedOptions);
828
+ }
829
+ catch (error) {
830
+ logger.warn("Orchestration failed, continuing with original options", {
831
+ error: error instanceof Error ? error.message : String(error),
832
+ originalProvider: options.provider || "auto",
833
+ });
834
+ // Continue with original options if orchestration fails
835
+ }
836
+ }
836
837
  // Emit generation start event (NeuroLink format - keep existing)
837
838
  this.emitter.emit("generation:start", {
838
839
  provider: options.provider || "auto",
@@ -1004,54 +1005,6 @@ export class NeuroLink {
1004
1005
  * Log generateTextInternal start with comprehensive analysis
1005
1006
  */
1006
1007
  logGenerateTextInternalStart(generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, options, functionTag) {
1007
- logger.debug(`[NeuroLink] 🎯 LOG_POINT_G001_GENERATE_INTERNAL_START`, {
1008
- logPoint: "G001_GENERATE_INTERNAL_START",
1009
- generateInternalId,
1010
- timestamp: new Date().toISOString(),
1011
- generateInternalStartTime,
1012
- generateInternalHrTimeStart: generateInternalHrTimeStart.toString(),
1013
- inputAnalysis: {
1014
- provider: options.provider || "auto",
1015
- providerType: typeof options.provider,
1016
- isAutoProvider: options.provider === "auto" || !options.provider,
1017
- model: options.model || "NOT_SET",
1018
- modelType: typeof options.model,
1019
- temperature: options.temperature,
1020
- temperatureType: typeof options.temperature,
1021
- maxTokens: options.maxTokens,
1022
- maxTokensType: typeof options.maxTokens,
1023
- promptLength: options.prompt?.length || 0,
1024
- promptPreview: options.prompt?.substring(0, 200) || "NO_PROMPT",
1025
- hasSystemPrompt: !!options.systemPrompt,
1026
- systemPromptLength: options.systemPrompt?.length || 0,
1027
- disableTools: options.disableTools || false,
1028
- enableAnalytics: options.enableAnalytics || false,
1029
- enableEvaluation: options.enableEvaluation || false,
1030
- hasContext: !!options.context,
1031
- contextKeys: options.context ? Object.keys(options.context) : [],
1032
- evaluationDomain: options.evaluationDomain || "NOT_SET",
1033
- toolUsageContext: options.toolUsageContext || "NOT_SET",
1034
- },
1035
- instanceState: {
1036
- hasConversationMemory: !!this.conversationMemory,
1037
- conversationMemoryType: this.conversationMemory?.constructor?.name || "NOT_SET",
1038
- mcpInitialized: this.mcpInitialized,
1039
- hasProviderRegistry: !!AIProviderFactory,
1040
- providerRegistrySize: 0,
1041
- hasToolRegistry: !!toolRegistry,
1042
- toolRegistrySize: 0,
1043
- hasExternalServerManager: !!this.externalServerManager,
1044
- },
1045
- environmentContext: {
1046
- nodeVersion: process.version,
1047
- platform: process.platform,
1048
- arch: process.arch,
1049
- memoryUsage: process.memoryUsage(),
1050
- cpuUsage: process.cpuUsage(),
1051
- uptime: process.uptime(),
1052
- },
1053
- message: "Starting generateTextInternal with comprehensive input analysis",
1054
- });
1055
1008
  logger.debug(`[${functionTag}] Starting generation`, {
1056
1009
  provider: options.provider || "auto",
1057
1010
  promptLength: options.prompt?.length || 0,
@@ -1071,18 +1024,6 @@ export class NeuroLink {
1071
1024
  */
1072
1025
  async initializeConversationMemoryForGeneration(generateInternalId, generateInternalStartTime, generateInternalHrTimeStart) {
1073
1026
  const conversationMemoryStartTime = process.hrtime.bigint();
1074
- logger.debug(`[NeuroLink] 🧠 LOG_POINT_G002_CONVERSATION_MEMORY_CHECK`, {
1075
- logPoint: "G002_CONVERSATION_MEMORY_CHECK",
1076
- generateInternalId,
1077
- timestamp: new Date().toISOString(),
1078
- elapsedMs: Date.now() - generateInternalStartTime,
1079
- elapsedNs: (process.hrtime.bigint() - generateInternalHrTimeStart).toString(),
1080
- conversationMemoryStartTimeNs: conversationMemoryStartTime.toString(),
1081
- hasConversationMemory: !!this.conversationMemory,
1082
- needsLazyInit: this.conversationMemoryNeedsInit,
1083
- hasConfig: !!this.conversationMemoryConfig,
1084
- message: "Checking conversation memory initialization requirement",
1085
- });
1086
1027
  // Handle lazy initialization if needed
1087
1028
  if (this.conversationMemoryNeedsInit && this.conversationMemoryConfig) {
1088
1029
  await this.lazyInitializeConversationMemory(generateInternalId, generateInternalStartTime, generateInternalHrTimeStart);
@@ -1116,33 +1057,6 @@ export class NeuroLink {
1116
1057
  * Attempt MCP generation with retry logic
1117
1058
  */
1118
1059
  async attemptMCPGeneration(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag) {
1119
- const mcpDecisionStartTime = process.hrtime.bigint();
1120
- logger.debug(`[NeuroLink] 🔧 LOG_POINT_G005_MCP_DECISION_CHECK`, {
1121
- logPoint: "G005_MCP_DECISION_CHECK",
1122
- generateInternalId,
1123
- timestamp: new Date().toISOString(),
1124
- elapsedMs: Date.now() - generateInternalStartTime,
1125
- elapsedNs: (process.hrtime.bigint() - generateInternalHrTimeStart).toString(),
1126
- mcpDecisionStartTimeNs: mcpDecisionStartTime.toString(),
1127
- mcpDecisionFactors: {
1128
- disableTools: options.disableTools || false,
1129
- toolsEnabled: !options.disableTools,
1130
- mcpInitialized: this.mcpInitialized,
1131
- hasExternalServerManager: !!this.externalServerManager,
1132
- hasToolRegistry: !!toolRegistry,
1133
- toolRegistrySize: 0,
1134
- shouldTryMCP: !options.disableTools,
1135
- },
1136
- mcpReadinessAnalysis: {
1137
- mcpAvailable: !options.disableTools && this.mcpInitialized,
1138
- componentsReady: {
1139
- externalServerManager: !!this.externalServerManager,
1140
- toolRegistry: !!toolRegistry,
1141
- providerRegistry: !!AIProviderFactory,
1142
- },
1143
- },
1144
- message: "Analyzing MCP generation eligibility and readiness",
1145
- });
1146
1060
  if (!options.disableTools) {
1147
1061
  return await this.performMCPGenerationRetries(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag);
1148
1062
  }
@@ -1153,66 +1067,11 @@ export class NeuroLink {
1153
1067
  */
1154
1068
  async performMCPGenerationRetries(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag) {
1155
1069
  const maxMcpRetries = RETRY_ATTEMPTS.QUICK;
1156
- const mcpRetryLoopStartTime = process.hrtime.bigint();
1157
- logger.debug(`[NeuroLink] 🔄 LOG_POINT_G006_MCP_RETRY_LOOP_START`, {
1158
- logPoint: "G006_MCP_RETRY_LOOP_START",
1159
- generateInternalId,
1160
- timestamp: new Date().toISOString(),
1161
- elapsedMs: Date.now() - generateInternalStartTime,
1162
- elapsedNs: (process.hrtime.bigint() - generateInternalHrTimeStart).toString(),
1163
- mcpRetryLoopStartTimeNs: mcpRetryLoopStartTime.toString(),
1164
- maxMcpRetries,
1165
- totalPossibleAttempts: maxMcpRetries + 1,
1166
- message: "Starting MCP generation retry loop with failure tolerance",
1167
- });
1168
1070
  const maxAttempts = maxMcpRetries + 1;
1169
1071
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1170
1072
  try {
1171
- const mcpAttemptStartTime = process.hrtime.bigint();
1172
- logger.debug(`[NeuroLink] 🎯 LOG_POINT_G007_MCP_ATTEMPT_START`, {
1173
- logPoint: "G007_MCP_ATTEMPT_START",
1174
- generateInternalId,
1175
- timestamp: new Date().toISOString(),
1176
- elapsedMs: Date.now() - generateInternalStartTime,
1177
- elapsedNs: (process.hrtime.bigint() - generateInternalHrTimeStart).toString(),
1178
- mcpAttemptStartTimeNs: mcpAttemptStartTime.toString(),
1179
- currentAttempt: attempt,
1180
- maxAttempts,
1181
- isFirstAttempt: attempt === 1,
1182
- isLastAttempt: attempt === maxAttempts,
1183
- attemptType: attempt === 1 ? "INITIAL" : "RETRY",
1184
- message: `Attempting MCP generation (attempt ${attempt}/${maxAttempts})`,
1185
- });
1186
1073
  logger.debug(`[${functionTag}] Attempting MCP generation (attempt ${attempt}/${maxAttempts})...`);
1187
1074
  const mcpResult = await this.tryMCPGeneration(options);
1188
- const mcpAttemptEndTime = process.hrtime.bigint();
1189
- const mcpAttemptDurationNs = mcpAttemptEndTime - mcpAttemptStartTime;
1190
- logger.debug(`[NeuroLink] 📊 LOG_POINT_G008_MCP_ATTEMPT_RESULT`, {
1191
- logPoint: "G008_MCP_ATTEMPT_RESULT",
1192
- generateInternalId,
1193
- timestamp: new Date().toISOString(),
1194
- elapsedMs: Date.now() - generateInternalStartTime,
1195
- elapsedNs: (process.hrtime.bigint() - generateInternalHrTimeStart).toString(),
1196
- mcpAttemptDurationNs: mcpAttemptDurationNs.toString(),
1197
- mcpAttemptDurationMs: Number(mcpAttemptDurationNs) / NANOSECOND_TO_MS_DIVISOR,
1198
- currentAttempt: attempt,
1199
- resultAnalysis: {
1200
- hasResult: !!mcpResult,
1201
- resultType: typeof mcpResult,
1202
- hasContent: !!(mcpResult && mcpResult.content),
1203
- contentLength: mcpResult?.content?.length || 0,
1204
- contentPreview: mcpResult?.content?.substring(0, 200) || "NO_CONTENT",
1205
- hasToolExecutions: !!(mcpResult &&
1206
- mcpResult.toolExecutions &&
1207
- mcpResult.toolExecutions.length > 0),
1208
- toolExecutionsCount: mcpResult?.toolExecutions?.length || 0,
1209
- toolsUsedCount: mcpResult?.toolsUsed?.length || 0,
1210
- provider: mcpResult?.provider || "NOT_SET",
1211
- responseTime: mcpResult?.responseTime || 0,
1212
- enhancedWithTools: mcpResult?.enhancedWithTools || false,
1213
- },
1214
- message: `MCP generation attempt ${attempt} completed - analyzing result`,
1215
- });
1216
1075
  if (mcpResult &&
1217
1076
  (mcpResult.content ||
1218
1077
  (mcpResult.toolExecutions && mcpResult.toolExecutions.length > 0))) {
@@ -1255,69 +1114,9 @@ export class NeuroLink {
1255
1114
  const tryMCPStartTime = Date.now();
1256
1115
  const tryMCPHrTimeStart = process.hrtime.bigint();
1257
1116
  const functionTag = "NeuroLink.tryMCPGeneration";
1258
- logger.debug(`[NeuroLink] 🚀 LOG_POINT_T001_TRY_MCP_START`, {
1259
- logPoint: "T001_TRY_MCP_START",
1260
- tryMCPId,
1261
- timestamp: new Date().toISOString(),
1262
- tryMCPStartTime,
1263
- tryMCPHrTimeStart: tryMCPHrTimeStart.toString(),
1264
- // 📊 Input options analysis
1265
- optionsAnalysis: {
1266
- provider: options.provider || "auto",
1267
- isAutoProvider: options.provider === "auto" || !options.provider,
1268
- model: options.model || "NOT_SET",
1269
- promptLength: options.prompt?.length || 0,
1270
- promptPreview: options.prompt?.substring(0, 150) || "NO_PROMPT",
1271
- hasSystemPrompt: !!options.systemPrompt,
1272
- systemPromptLength: options.systemPrompt?.length || 0,
1273
- disableTools: options.disableTools || false,
1274
- enableAnalytics: options.enableAnalytics || false,
1275
- temperature: options.temperature,
1276
- maxTokens: options.maxTokens,
1277
- },
1278
- // 🔧 MCP state analysis
1279
- mcpStateAnalysis: {
1280
- mcpInitialized: this.mcpInitialized,
1281
- hasExternalServerManager: !!this.externalServerManager,
1282
- hasToolRegistry: !!toolRegistry,
1283
- toolRegistrySize: 0, // Not accessible as size property
1284
- hasProviderRegistry: !!AIProviderFactory,
1285
- providerRegistrySize: 0, // Not accessible as size property
1286
- },
1287
- message: "Starting MCP-enhanced generation attempt with comprehensive analysis",
1288
- });
1289
1117
  try {
1290
- // 🚀 EXHAUSTIVE LOGGING POINT T002: MCP INITIALIZATION CHECK
1291
- const mcpInitCheckStartTime = process.hrtime.bigint();
1292
- logger.debug(`[NeuroLink] 🔧 LOG_POINT_T002_MCP_INIT_CHECK`, {
1293
- logPoint: "T002_MCP_INIT_CHECK",
1294
- tryMCPId,
1295
- timestamp: new Date().toISOString(),
1296
- elapsedMs: Date.now() - tryMCPStartTime,
1297
- elapsedNs: (process.hrtime.bigint() - tryMCPHrTimeStart).toString(),
1298
- mcpInitCheckStartTimeNs: mcpInitCheckStartTime.toString(),
1299
- mcpInitializedBefore: this.mcpInitialized,
1300
- needsInitialization: !this.mcpInitialized,
1301
- message: "Checking MCP initialization status before generation",
1302
- });
1303
- // Initialize MCP only when tools are enabled
1304
- if (!options.disableTools) {
1305
- await this.initializeMCP();
1306
- }
1307
- const mcpInitCheckEndTime = process.hrtime.bigint();
1308
- const mcpInitCheckDurationNs = mcpInitCheckEndTime - mcpInitCheckStartTime;
1309
- logger.debug(`[NeuroLink] ✅ LOG_POINT_T003_MCP_INIT_CHECK_COMPLETE`, {
1310
- logPoint: "T003_MCP_INIT_CHECK_COMPLETE",
1311
- tryMCPId,
1312
- timestamp: new Date().toISOString(),
1313
- elapsedMs: Date.now() - tryMCPStartTime,
1314
- elapsedNs: (process.hrtime.bigint() - tryMCPHrTimeStart).toString(),
1315
- mcpInitCheckDurationNs: mcpInitCheckDurationNs.toString(),
1316
- mcpInitCheckDurationMs: Number(mcpInitCheckDurationNs) / NANOSECOND_TO_MS_DIVISOR,
1317
- mcpInitializedAfter: this.mcpInitialized,
1318
- initializationSuccessful: this.mcpInitialized,
1319
- message: "MCP initialization check completed",
1320
- });
1118
+ // Initialize MCP if needed
1119
+ await this.initializeMCP();
1321
1120
  if (!this.mcpInitialized) {
1322
1121
  logger.warn(`[NeuroLink] ⚠️ LOG_POINT_T004_MCP_NOT_AVAILABLE`, {
1323
1122
  logPoint: "T004_MCP_NOT_AVAILABLE",
@@ -1343,8 +1142,27 @@ export class NeuroLink {
1343
1142
  : options.provider;
1344
1143
  // Get available tools
1345
1144
  const availableTools = await this.getAllAvailableTools();
1145
+ const targetTool = availableTools.find((t) => t.name.includes("SuccessRateSRByTime") ||
1146
+ t.name.includes("juspay-analytics"));
1147
+ logger.debug("Available tools for AI prompt generation", {
1148
+ toolsCount: availableTools.length,
1149
+ toolNames: availableTools.map((t) => t.name),
1150
+ hasTargetTool: !!targetTool,
1151
+ targetToolDetails: targetTool
1152
+ ? {
1153
+ name: targetTool.name,
1154
+ description: targetTool.description,
1155
+ server: targetTool.server,
1156
+ }
1157
+ : null,
1158
+ });
1346
1159
  // Create tool-aware system prompt
1347
1160
  const enhancedSystemPrompt = this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
1161
+ logger.debug("Tool-aware system prompt created", {
1162
+ originalPromptLength: options.systemPrompt?.length || 0,
1163
+ enhancedPromptLength: enhancedSystemPrompt.length,
1164
+ enhancedPromptPreview: enhancedSystemPrompt.substring(0, 500) + "...",
1165
+ });
1348
1166
  // Get conversation messages for context
1349
1167
  const conversationMessages = await getConversationMessages(this.conversationMemory, options);
1350
1168
  // Create provider and generate
@@ -1429,14 +1247,27 @@ export class NeuroLink {
1429
1247
  "ollama",
1430
1248
  ];
1431
1249
  const requestedProvider = options.provider === "auto" ? undefined : options.provider;
1432
- // If specific provider requested, only use that provider (no fallback)
1433
- const tryProviders = requestedProvider
1434
- ? [requestedProvider]
1435
- : providerPriority;
1250
+ // Check for orchestrated preferred provider in context
1251
+ const preferredOrchestrated = options.context &&
1252
+ typeof options.context === "object" &&
1253
+ "__orchestratedPreferredProvider" in options.context
1254
+ ? options.context
1255
+ .__orchestratedPreferredProvider
1256
+ : undefined;
1257
+ // Build provider list with orchestrated preference first, then fallback to full list
1258
+ const tryProviders = preferredOrchestrated
1259
+ ? [
1260
+ preferredOrchestrated,
1261
+ ...providerPriority.filter((p) => p !== preferredOrchestrated),
1262
+ ]
1263
+ : requestedProvider
1264
+ ? [requestedProvider]
1265
+ : providerPriority;
1436
1266
  logger.debug(`[${functionTag}] Starting direct generation`, {
1437
1267
  requestedProvider: requestedProvider || "auto",
1268
+ preferredOrchestrated: preferredOrchestrated || "none",
1438
1269
  tryProviders,
1439
- allowFallback: !requestedProvider,
1270
+ allowFallback: !requestedProvider || !!preferredOrchestrated,
1440
1271
  });
1441
1272
  let lastError = null;
1442
1273
  // Try each provider in order
@@ -1500,7 +1331,15 @@ export class NeuroLink {
1500
1331
  * Create tool-aware system prompt that informs AI about available tools
1501
1332
  */
1502
1333
  createToolAwareSystemPrompt(originalSystemPrompt, availableTools) {
1334
+ // AI prompt generation with tool analysis and structured logging
1335
+ const promptGenerationData = {
1336
+ originalPromptLength: originalSystemPrompt?.length || 0,
1337
+ availableToolsCount: availableTools.length,
1338
+ hasOriginalPrompt: !!originalSystemPrompt,
1339
+ };
1340
+ logger.debug("AI prompt generation with tool schemas", promptGenerationData);
1503
1341
  if (availableTools.length === 0) {
1342
+ logger.debug("No tools available - returning original prompt");
1504
1343
  return originalSystemPrompt || "";
1505
1344
  }
1506
1345
  const toolDescriptions = transformToolsToDescriptions(availableTools.map((t) => ({
@@ -1509,8 +1348,22 @@ export class NeuroLink {
1509
1348
  server: t.serverId ?? "unknown",
1510
1349
  inputSchema: t.inputSchema,
1511
1350
  })));
1351
+ const transformationResult = {
1352
+ toolDescriptionsLength: toolDescriptions.length,
1353
+ toolDescriptionsCharCount: toolDescriptions.length,
1354
+ hasDescriptions: toolDescriptions.length > 0,
1355
+ };
1356
+ logger.debug("Tool descriptions transformation completed", transformationResult);
1512
1357
  const toolPrompt = `\n\nYou have access to these additional tools if needed:\n${toolDescriptions}\n\nIMPORTANT: You are a general-purpose AI assistant. Answer all requests directly and creatively. These tools are optional helpers - use them only when they would genuinely improve your response. For creative tasks like storytelling, writing, or general conversation, respond naturally without requiring tools.`;
1513
- return (originalSystemPrompt || "") + toolPrompt;
1358
+ const finalPrompt = (originalSystemPrompt || "") + toolPrompt;
1359
+ const finalPromptData = {
1360
+ originalPromptLength: originalSystemPrompt?.length || 0,
1361
+ toolPromptLength: toolPrompt.length,
1362
+ finalPromptLength: finalPrompt.length,
1363
+ promptEnhanced: toolPrompt.length > 0,
1364
+ };
1365
+ logger.debug("AI prompt generation completed", finalPromptData);
1366
+ return finalPrompt;
1514
1367
  }
1515
1368
  /**
1516
1369
  * Execute tools if available through centralized registry
@@ -1531,31 +1384,6 @@ export class NeuroLink {
1531
1384
  return { toolResults: [], enhancedPrompt: prompt };
1532
1385
  }
1533
1386
  }
1534
- /**
1535
- * Enhance prompt with tool results (domain-agnostic)
1536
- */
1537
- enhancePromptWithToolResults(prompt, toolResults) {
1538
- if (toolResults.length === 0) {
1539
- return prompt;
1540
- }
1541
- let enhancedPrompt = prompt;
1542
- for (const result of toolResults) {
1543
- if (result && typeof result === "object") {
1544
- enhancedPrompt += `\n\nTool Results:\n`;
1545
- // Handle structured result generically
1546
- try {
1547
- const resultStr = typeof result === "string"
1548
- ? result
1549
- : JSON.stringify(result, null, 2);
1550
- enhancedPrompt += resultStr + "\n";
1551
- }
1552
- catch {
1553
- enhancedPrompt += "Tool execution completed\n";
1554
- }
1555
- }
1556
- }
1557
- return enhancedPrompt;
1558
- }
1559
1387
  /**
1560
1388
  * BACKWARD COMPATIBILITY: Legacy streamText method
1561
1389
  * Internally calls stream() and converts result format
@@ -1636,13 +1464,9 @@ export class NeuroLink {
1636
1464
  async stream(options) {
1637
1465
  const startTime = Date.now();
1638
1466
  const hrTimeStart = process.hrtime.bigint();
1639
- const functionTag = "NeuroLink.stream";
1640
1467
  const streamId = `neurolink-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1641
- const journeyStartTime = new Date().toISOString();
1642
1468
  const originalPrompt = options.input.text; // Store the original prompt for memory storage
1643
- this.logStreamEntryPoint(streamId, journeyStartTime, functionTag, startTime, hrTimeStart, options);
1644
- this.logPerformanceBaseline(streamId, startTime, hrTimeStart);
1645
- await this.validateStreamInput(options, streamId, startTime, hrTimeStart);
1469
+ await this.validateStreamInput(options);
1646
1470
  this.emitStreamStartEvents(options, startTime);
1647
1471
  let enhancedOptions;
1648
1472
  let factoryResult;
@@ -1651,6 +1475,28 @@ export class NeuroLink {
1651
1475
  await this.initializeConversationMemoryForGeneration(streamId, startTime, hrTimeStart);
1652
1476
  // Initialize MCP
1653
1477
  await this.initializeMCP();
1478
+ const _originalPrompt = options.input.text;
1479
+ // Apply orchestration if enabled and no specific provider/model requested
1480
+ if (this.enableOrchestration && !options.provider && !options.model) {
1481
+ try {
1482
+ const orchestratedOptions = await this.applyStreamOrchestration(options);
1483
+ logger.debug("Stream orchestration applied", {
1484
+ originalProvider: options.provider || "auto",
1485
+ orchestratedProvider: orchestratedOptions.provider,
1486
+ orchestratedModel: orchestratedOptions.model,
1487
+ prompt: options.input.text?.substring(0, 100),
1488
+ });
1489
+ // Use orchestrated options
1490
+ Object.assign(options, orchestratedOptions);
1491
+ }
1492
+ catch (error) {
1493
+ logger.warn("Stream orchestration failed, continuing with original options", {
1494
+ error: error instanceof Error ? error.message : String(error),
1495
+ originalProvider: options.provider || "auto",
1496
+ });
1497
+ // Continue with original options if orchestration fails
1498
+ }
1499
+ }
1654
1500
  factoryResult = processStreamingFactoryOptions(options);
1655
1501
  enhancedOptions = createCleanStreamOptions(options);
1656
1502
  if (options.input?.text) {
@@ -1713,89 +1559,13 @@ export class NeuroLink {
1713
1559
  return this.handleStreamError(error, options, startTime, streamId, undefined, undefined);
1714
1560
  }
1715
1561
  }
1716
- /**
1717
- * Log stream entry point with comprehensive analysis
1718
- */
1719
- logStreamEntryPoint(streamId, journeyStartTime, functionTag, startTime, hrTimeStart, options) {
1720
- logger.debug(`[NeuroLink] 🎯 LOG_POINT_001_STREAM_ENTRY_START`, {
1721
- logPoint: "001_STREAM_ENTRY_START",
1722
- streamId,
1723
- timestamp: journeyStartTime,
1724
- functionTag,
1725
- startTime,
1726
- hrTimeStart: hrTimeStart.toString(),
1727
- nodeVersion: process.version,
1728
- platform: process.platform,
1729
- arch: process.arch,
1730
- memoryUsage: process.memoryUsage(),
1731
- cpuUsage: process.cpuUsage(),
1732
- hasOptions: !!options,
1733
- optionsType: typeof options,
1734
- optionsKeys: options ? Object.keys(options) : [],
1735
- optionsSize: options ? JSON.stringify(options).length : 0,
1736
- hasInput: !!options?.input,
1737
- inputType: typeof options?.input,
1738
- inputKeys: options?.input ? Object.keys(options.input) : [],
1739
- hasInputText: !!options?.input?.text,
1740
- inputTextType: typeof options?.input?.text,
1741
- inputTextLength: options?.input?.text?.length || 0,
1742
- inputTextPreview: options?.input?.text?.substring(0, 200) || "NO_TEXT",
1743
- hasProvider: !!options?.provider,
1744
- providerValue: options?.provider || "NOT_SET",
1745
- isAutoProvider: options?.provider === "auto" || !options?.provider,
1746
- hasModel: !!options?.model,
1747
- modelValue: options?.model || "NOT_SET",
1748
- message: "EXHAUSTIVE NeuroLink main stream method entry point with comprehensive environment analysis",
1749
- });
1750
- }
1751
- /**
1752
- * Log performance baseline
1753
- */
1754
- logPerformanceBaseline(streamId, startTime, hrTimeStart) {
1755
- const memoryBaseline = process.memoryUsage();
1756
- const cpuBaseline = process.cpuUsage();
1757
- logger.debug(`[NeuroLink] 🎯 LOG_POINT_002_PERFORMANCE_BASELINE`, {
1758
- logPoint: "002_PERFORMANCE_BASELINE",
1759
- streamId,
1760
- timestamp: new Date().toISOString(),
1761
- elapsedMs: Date.now() - startTime,
1762
- elapsedNs: (process.hrtime.bigint() - hrTimeStart).toString(),
1763
- memoryBaseline: {
1764
- rss: memoryBaseline.rss,
1765
- heapTotal: memoryBaseline.heapTotal,
1766
- heapUsed: memoryBaseline.heapUsed,
1767
- external: memoryBaseline.external,
1768
- arrayBuffers: memoryBaseline.arrayBuffers,
1769
- },
1770
- cpuBaseline: {
1771
- user: cpuBaseline.user,
1772
- system: cpuBaseline.system,
1773
- },
1774
- gcStats: global.gc
1775
- ? (() => {
1776
- try {
1777
- global.gc();
1778
- return process.memoryUsage();
1779
- }
1780
- catch {
1781
- return null;
1782
- }
1783
- })()
1784
- : null,
1785
- message: "Performance baseline metrics captured for stream processing",
1786
- });
1787
- }
1788
1562
  /**
1789
1563
  * Validate stream input with comprehensive error reporting
1790
1564
  */
1791
- async validateStreamInput(options, streamId, startTime, hrTimeStart) {
1565
+ async validateStreamInput(options) {
1792
1566
  const validationStartTime = process.hrtime.bigint();
1793
1567
  logger.debug(`[NeuroLink] 🎯 LOG_POINT_003_VALIDATION_START`, {
1794
1568
  logPoint: "003_VALIDATION_START",
1795
- streamId,
1796
- timestamp: new Date().toISOString(),
1797
- elapsedMs: Date.now() - startTime,
1798
- elapsedNs: (process.hrtime.bigint() - hrTimeStart).toString(),
1799
1569
  validationStartTimeNs: validationStartTime.toString(),
1800
1570
  message: "Starting comprehensive input validation process",
1801
1571
  });
@@ -1806,38 +1576,8 @@ export class NeuroLink {
1806
1576
  options.input.audio.frames &&
1807
1577
  typeof options.input.audio.frames[Symbol.asyncIterator] !== "undefined");
1808
1578
  if (!hasText && !hasAudio) {
1809
- const validationFailTime = process.hrtime.bigint();
1810
- const validationDurationNs = validationFailTime - validationStartTime;
1811
- logger.debug(`[NeuroLink] 💥 LOG_POINT_005_VALIDATION_FAILED`, {
1812
- logPoint: "005_VALIDATION_FAILED",
1813
- streamId,
1814
- timestamp: new Date().toISOString(),
1815
- elapsedMs: Date.now() - startTime,
1816
- elapsedNs: (process.hrtime.bigint() - hrTimeStart).toString(),
1817
- validationDurationNs: validationDurationNs.toString(),
1818
- validationDurationMs: Number(validationDurationNs) / NANOSECOND_TO_MS_DIVISOR,
1819
- validationError: "Stream options must include either input.text or input.audio",
1820
- message: "EXHAUSTIVE validation failure analysis with character-level debugging",
1821
- });
1822
1579
  throw new Error("Stream options must include either input.text or input.audio");
1823
1580
  }
1824
- const validationSuccessTime = process.hrtime.bigint();
1825
- const validationDurationNs = validationSuccessTime - validationStartTime;
1826
- logger.debug(`[NeuroLink] ✅ LOG_POINT_006_VALIDATION_SUCCESS`, {
1827
- logPoint: "006_VALIDATION_SUCCESS",
1828
- streamId,
1829
- timestamp: new Date().toISOString(),
1830
- elapsedMs: Date.now() - startTime,
1831
- elapsedNs: (process.hrtime.bigint() - hrTimeStart).toString(),
1832
- validationDurationNs: validationDurationNs.toString(),
1833
- validationDurationMs: Number(validationDurationNs) / NANOSECOND_TO_MS_DIVISOR,
1834
- inputTextValid: hasText,
1835
- inputAudioPresent: hasAudio,
1836
- inputTextLength: hasText ? options.input.text.length : 0,
1837
- inputTextTrimmedLength: hasText ? options.input.text.trim().length : 0,
1838
- inputTextPreview: hasText ? options.input.text.substring(0, 100) : "",
1839
- message: "EXHAUSTIVE validation success - proceeding with stream processing",
1840
- });
1841
1581
  }
1842
1582
  /**
1843
1583
  * Emit stream start events
@@ -2172,6 +1912,134 @@ export class NeuroLink {
2172
1912
  return this.emitter;
2173
1913
  }
2174
1914
  // ========================================
1915
+ // ENHANCED: Tool Event Emission API
1916
+ // ========================================
1917
+ // TODO: Add ToolExecutionEvent utility methods in future version
1918
+ // Will provide structured event format for consistent tool event processing
1919
+ /**
1920
+ * Emit tool start event with execution tracking
1921
+ * @param toolName - Name of the tool being executed
1922
+ * @param input - Input parameters for the tool
1923
+ * @param startTime - Timestamp when execution started
1924
+ * @returns executionId for tracking this specific execution
1925
+ */
1926
+ emitToolStart(toolName, input, startTime = Date.now()) {
1927
+ const executionId = `${toolName}-${startTime}-${Math.random().toString(36).substr(2, 9)}`;
1928
+ // Create execution context for tracking
1929
+ const context = {
1930
+ executionId,
1931
+ tool: toolName,
1932
+ startTime,
1933
+ metadata: {
1934
+ inputType: typeof input,
1935
+ hasInput: input !== undefined && input !== null,
1936
+ },
1937
+ };
1938
+ // Store in active executions
1939
+ this.activeToolExecutions.set(executionId, context);
1940
+ this.currentStreamToolExecutions.push(context);
1941
+ // Emit event (NeuroLinkEvents format for compatibility)
1942
+ this.emitter.emit("tool:start", {
1943
+ tool: toolName,
1944
+ input,
1945
+ timestamp: startTime,
1946
+ executionId,
1947
+ });
1948
+ logger.debug(`tool:start emitted for ${toolName}`, {
1949
+ toolName,
1950
+ executionId,
1951
+ timestamp: startTime,
1952
+ inputProvided: input !== undefined,
1953
+ });
1954
+ return executionId;
1955
+ }
1956
+ /**
1957
+ * Emit tool end event with execution summary
1958
+ * @param toolName - Name of the tool that finished
1959
+ * @param result - Result from the tool execution
1960
+ * @param error - Error message if execution failed
1961
+ * @param startTime - When execution started
1962
+ * @param endTime - When execution finished
1963
+ * @param executionId - Optional execution ID for tracking
1964
+ */
1965
+ emitToolEnd(toolName, result, error, startTime, endTime = Date.now(), executionId) {
1966
+ const actualStartTime = startTime || endTime - 1000; // Fallback if no start time
1967
+ const duration = endTime - actualStartTime;
1968
+ const success = !error;
1969
+ // Find execution context or create fallback
1970
+ let context;
1971
+ if (executionId) {
1972
+ context = this.activeToolExecutions.get(executionId);
1973
+ }
1974
+ else {
1975
+ // Find by tool name (fallback for executions without ID tracking)
1976
+ context = Array.from(this.activeToolExecutions.values()).find((ctx) => ctx.tool === toolName && !ctx.endTime);
1977
+ }
1978
+ const finalExecutionId = executionId ||
1979
+ context?.executionId ||
1980
+ `${toolName}-${actualStartTime}-fallback-${Math.random().toString(36).substr(2, 9)}`;
1981
+ // Update execution context
1982
+ if (context) {
1983
+ context.endTime = endTime;
1984
+ context.result = result;
1985
+ context.error = error;
1986
+ this.activeToolExecutions.delete(context.executionId);
1987
+ }
1988
+ // Create execution summary
1989
+ const summary = {
1990
+ tool: toolName,
1991
+ startTime: actualStartTime,
1992
+ endTime,
1993
+ duration,
1994
+ success,
1995
+ result,
1996
+ error,
1997
+ executionId: finalExecutionId,
1998
+ metadata: {
1999
+ toolCategory: "custom", // Default, can be overridden
2000
+ },
2001
+ };
2002
+ // Store in history
2003
+ this.toolExecutionHistory.push(summary);
2004
+ // Emit event (NeuroLinkEvents format for compatibility)
2005
+ this.emitter.emit("tool:end", {
2006
+ tool: toolName,
2007
+ result,
2008
+ error,
2009
+ timestamp: endTime,
2010
+ duration,
2011
+ executionId: finalExecutionId,
2012
+ });
2013
+ logger.debug(`tool:end emitted for ${toolName}`, {
2014
+ toolName,
2015
+ executionId: finalExecutionId,
2016
+ duration,
2017
+ success,
2018
+ hasResult: result !== undefined,
2019
+ hasError: !!error,
2020
+ });
2021
+ }
2022
+ /**
2023
+ * Get current tool execution contexts for stream metadata
2024
+ */
2025
+ getCurrentToolExecutions() {
2026
+ return [...this.currentStreamToolExecutions];
2027
+ }
2028
+ /**
2029
+ * Get tool execution history
2030
+ */
2031
+ getToolExecutionHistory() {
2032
+ return [...this.toolExecutionHistory];
2033
+ }
2034
+ /**
2035
+ * Clear current stream tool executions (called at stream start)
2036
+ */
2037
+ clearCurrentStreamExecutions() {
2038
+ this.currentStreamToolExecutions = [];
2039
+ }
2040
+ // TODO: Add getToolExecutionEvents() method in future version
2041
+ // Will return properly formatted ToolExecutionEvent objects for structured event processing
2042
+ // ========================================
2175
2043
  // Tool Registration API
2176
2044
  // ========================================
2177
2045
  /**
@@ -2187,7 +2055,6 @@ export class NeuroLink {
2187
2055
  timestamp: Date.now(),
2188
2056
  });
2189
2057
  try {
2190
- // --- Start: Enhanced Validation Logic with FlexibleToolValidator ---
2191
2058
  if (!name || typeof name !== "string") {
2192
2059
  throw new Error("Invalid tool name");
2193
2060
  }
@@ -2197,41 +2064,42 @@ export class NeuroLink {
2197
2064
  if (typeof tool.execute !== "function") {
2198
2065
  throw new Error(`Tool '${name}' must have an execute method.`);
2199
2066
  }
2200
- // Use FlexibleToolValidator for consistent validation across SDK and toolRegistry
2201
- try {
2202
- const flexibleValidatorModule = require("./mcp/flexibleToolValidator.js");
2203
- const FlexibleToolValidator = flexibleValidatorModule.FlexibleToolValidator;
2204
- // Use the same validation logic as toolRegistry (static method)
2205
- const validationResult = FlexibleToolValidator.validateToolName(name);
2206
- if (!validationResult.isValid) {
2207
- throw new Error(`Tool validation failed: ${validationResult.error}`);
2208
- }
2067
+ if (name.trim() === "") {
2068
+ throw new Error("Tool name cannot be empty");
2209
2069
  }
2210
- catch (error) {
2211
- // If FlexibleToolValidator import fails, use basic safety checks
2212
- logger.warn("FlexibleToolValidator not available, using basic validation", {
2213
- error: error instanceof Error ? error.message : String(error),
2214
- });
2215
- // Basic safety checks to prevent obvious issues
2216
- if (name.trim() === "") {
2217
- throw new Error("Tool name cannot be empty");
2218
- }
2219
- if (name.length > 100) {
2220
- throw new Error("Tool name is too long (maximum 100 characters)");
2221
- }
2222
- // eslint-disable-next-line no-control-regex
2223
- if (/[\x00-\x1F\x7F]/.test(name)) {
2224
- throw new Error("Tool name contains invalid control characters");
2225
- }
2070
+ if (name.length > 100) {
2071
+ throw new Error("Tool name is too long (maximum 100 characters)");
2072
+ }
2073
+ // eslint-disable-next-line no-control-regex
2074
+ if (/[\x00-\x1F\x7F]/.test(name)) {
2075
+ throw new Error("Tool name contains invalid control characters");
2226
2076
  }
2227
- // --- End: Enhanced Validation Logic ---
2228
- // Tool object validation is now handled by FlexibleToolValidator above
2229
2077
  // Proceed with tool registration since validation passed
2078
+ // Convert tool to proper MCPExecutableTool format with schema conversion
2079
+ const convertedTool = {
2080
+ name: tool.name || name,
2081
+ description: tool.description || name,
2082
+ execute: tool.execute,
2083
+ inputSchema: (() => {
2084
+ // Check if tool has 'parameters' field (SDK SimpleTool format)
2085
+ if ("parameters" in tool && tool.parameters) {
2086
+ if (isZodSchema(tool.parameters)) {
2087
+ return tool.parameters;
2088
+ }
2089
+ // If it's already a JSON Schema object, return as-is
2090
+ if (typeof tool.parameters === "object") {
2091
+ return tool.parameters;
2092
+ }
2093
+ }
2094
+ // Fall back to existing inputSchema or empty object
2095
+ const fallbackSchema = tool.inputSchema || {};
2096
+ return fallbackSchema;
2097
+ })(),
2098
+ };
2230
2099
  // SMART DEFAULTS: Use utility to eliminate boilerplate creation
2231
- const mcpServerInfo = createCustomToolServerInfo(name, tool);
2100
+ const mcpServerInfo = createCustomToolServerInfo(name, convertedTool);
2232
2101
  // Register with toolRegistry using MCPServerInfo directly
2233
2102
  toolRegistry.registerServer(mcpServerInfo);
2234
- logger.info(`Registered custom tool: ${name}`);
2235
2103
  // Emit tool registration success event
2236
2104
  this.emitter.emit("tools-register:end", {
2237
2105
  toolName: name,
@@ -2244,6 +2112,36 @@ export class NeuroLink {
2244
2112
  throw error;
2245
2113
  }
2246
2114
  }
2115
+ /**
2116
+ * Set the context that will be passed to tools during execution
2117
+ * This context will be merged with any runtime context passed by the AI model
2118
+ * @param context - Context object containing session info, tokens, shop data, etc.
2119
+ */
2120
+ setToolContext(context) {
2121
+ this.toolExecutionContext = { ...context };
2122
+ logger.debug("Tool execution context updated", {
2123
+ sessionId: context.sessionId,
2124
+ contextKeys: Object.keys(context),
2125
+ hasJuspayToken: !!context.juspayToken,
2126
+ hasShopId: !!context.shopId,
2127
+ });
2128
+ }
2129
+ /**
2130
+ * Get the current tool execution context
2131
+ * @returns Current context or undefined if not set
2132
+ */
2133
+ getToolContext() {
2134
+ return this.toolExecutionContext
2135
+ ? { ...this.toolExecutionContext }
2136
+ : undefined;
2137
+ }
2138
+ /**
2139
+ * Clear the tool execution context
2140
+ */
2141
+ clearToolContext() {
2142
+ this.toolExecutionContext = undefined;
2143
+ logger.debug("Tool execution context cleared");
2144
+ }
2247
2145
  /**
2248
2146
  * Register multiple tools at once - Supports both object and array formats
2249
2147
  * @param tools - Object mapping tool names to MCPExecutableTool format OR Array of tools with names
@@ -2288,16 +2186,61 @@ export class NeuroLink {
2288
2186
  const customTools = toolRegistry.getToolsByCategory(detectCategory({ isCustomTool: true }));
2289
2187
  const toolMap = new Map();
2290
2188
  for (const tool of customTools) {
2189
+ const effectiveSchema = tool.inputSchema || tool.parameters;
2190
+ logger.debug(`Processing tool schema for Claude`, {
2191
+ toolName: tool.name,
2192
+ hasDescription: !!tool.description,
2193
+ description: tool.description,
2194
+ hasParameters: !!tool.parameters,
2195
+ parametersType: typeof tool.parameters,
2196
+ parametersKeys: tool.parameters && typeof tool.parameters === "object"
2197
+ ? Object.keys(tool.parameters)
2198
+ : "NOT_OBJECT",
2199
+ hasInputSchema: !!tool.inputSchema,
2200
+ inputSchemaType: typeof tool.inputSchema,
2201
+ inputSchemaKeys: tool.inputSchema && typeof tool.inputSchema === "object"
2202
+ ? Object.keys(tool.inputSchema)
2203
+ : "NOT_OBJECT",
2204
+ hasEffectiveSchema: !!effectiveSchema,
2205
+ effectiveSchemaType: typeof effectiveSchema,
2206
+ effectiveSchemaHasProperties: !!effectiveSchema?.properties,
2207
+ effectiveSchemaHasRequired: !!effectiveSchema?.required,
2208
+ originalInputSchema: tool.inputSchema,
2209
+ phase: "AFTER_SCHEMA_FIX",
2210
+ timestamp: Date.now(),
2211
+ });
2291
2212
  // Return MCPServerInfo.tools format directly - no conversion needed
2292
2213
  toolMap.set(tool.name, {
2293
2214
  name: tool.name,
2294
2215
  description: tool.description || "",
2295
- inputSchema: {},
2216
+ inputSchema: tool.inputSchema || tool.parameters || {},
2296
2217
  execute: async (params, context) => {
2297
- // Type guard to ensure context is compatible with ExecutionContext
2298
- const executionContext = context && isNonNullObject(context)
2218
+ // CONTEXT MERGING: Combine all available contexts for maximum information
2219
+ const storedContext = this.toolExecutionContext || {};
2220
+ const runtimeContext = context && isNonNullObject(context)
2299
2221
  ? context
2300
- : undefined;
2222
+ : {};
2223
+ // Merge contexts with runtime context taking precedence
2224
+ // This ensures we have the richest possible context for tool execution
2225
+ const executionContext = {
2226
+ ...storedContext, // Base context from setToolContext (session, tokens, etc.)
2227
+ ...runtimeContext, // Runtime context from AI model (if any)
2228
+ // Ensure we always have at least a sessionId for tracing
2229
+ sessionId: runtimeContext.sessionId ||
2230
+ storedContext.sessionId ||
2231
+ `fallback-${Date.now()}`,
2232
+ };
2233
+ // Enhanced logging for context debugging
2234
+ logger.debug("Tool execution context merged", {
2235
+ toolName: tool.name,
2236
+ storedContextKeys: Object.keys(storedContext),
2237
+ runtimeContextKeys: Object.keys(runtimeContext),
2238
+ finalContextKeys: Object.keys(executionContext),
2239
+ hasJuspayToken: !!executionContext
2240
+ .juspayToken,
2241
+ hasShopId: !!executionContext.shopId,
2242
+ sessionId: executionContext.sessionId,
2243
+ });
2301
2244
  return await toolRegistry.executeTool(tool.name, params, executionContext);
2302
2245
  },
2303
2246
  });
@@ -2374,7 +2317,7 @@ export class NeuroLink {
2374
2317
  * Supports both custom tools and MCP server tools with timeout, retry, and circuit breaker patterns
2375
2318
  * @param toolName - Name of the tool to execute
2376
2319
  * @param params - Parameters to pass to the tool
2377
- * @param options - Execution options
2320
+ * @param options - Execution options including optional authentication context
2378
2321
  * @returns Tool execution result
2379
2322
  */
2380
2323
  async executeTool(toolName, params = {}, options) {
@@ -2388,6 +2331,28 @@ export class NeuroLink {
2388
2331
  : params,
2389
2332
  hasExternalManager: !!this.externalServerManager,
2390
2333
  });
2334
+ // 🔧 PARAMETER TRACE: Log tool execution details for debugging
2335
+ logger.debug(`Tool execution detailed analysis`, {
2336
+ toolName,
2337
+ executionStartTime,
2338
+ paramsAnalysis: {
2339
+ type: typeof params,
2340
+ isNull: params === null,
2341
+ isUndefined: params === undefined,
2342
+ isEmpty: params &&
2343
+ typeof params === "object" &&
2344
+ Object.keys(params).length === 0,
2345
+ keys: params && typeof params === "object"
2346
+ ? Object.keys(params)
2347
+ : "NOT_OBJECT",
2348
+ keysLength: params && typeof params === "object"
2349
+ ? Object.keys(params).length
2350
+ : 0,
2351
+ },
2352
+ isTargetTool: toolName === "juspay-analytics_SuccessRateSRByTime",
2353
+ options,
2354
+ hasExternalManager: !!this.externalServerManager,
2355
+ });
2391
2356
  // Emit tool start event (NeuroLink format - keep existing)
2392
2357
  this.emitter.emit("tool:start", {
2393
2358
  toolName,
@@ -2401,6 +2366,7 @@ export class NeuroLink {
2401
2366
  timeout: options?.timeout || TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS, // 30 second default timeout
2402
2367
  maxRetries: options?.maxRetries || RETRY_ATTEMPTS.DEFAULT, // Default 2 retries for retriable errors
2403
2368
  retryDelayMs: options?.retryDelayMs || RETRY_DELAYS.BASE_MS, // 1 second delay between retries
2369
+ authContext: options?.authContext, // Pass through authentication context
2404
2370
  };
2405
2371
  // Track memory usage for tool execution
2406
2372
  const { MemoryManager } = await import("./utils/performance.js");
@@ -2573,12 +2539,18 @@ export class NeuroLink {
2573
2539
  throw ErrorFactory.toolExecutionFailed(toolName, error instanceof Error ? error : new Error(String(error)), externalTool.serverId);
2574
2540
  }
2575
2541
  }
2576
- // If not found in custom tools, in-memory servers, or external servers, try unified registry
2577
2542
  try {
2543
+ const storedContext = this.toolExecutionContext || {};
2544
+ const passedAuthContext = options.authContext || {};
2578
2545
  const context = {
2579
- sessionId: `neurolink-tool-${Date.now()}`,
2580
- userId: "neurolink-user",
2546
+ ...storedContext,
2547
+ ...passedAuthContext,
2581
2548
  };
2549
+ logger.debug(`[Using merged context for unified registry tool:`, {
2550
+ toolName,
2551
+ storedContextKeys: Object.keys(storedContext),
2552
+ finalContextKeys: Object.keys(context),
2553
+ });
2582
2554
  const result = (await toolRegistry.executeTool(toolName, params, context));
2583
2555
  // ADD: Check if result indicates a failure and emit error event
2584
2556
  if (result &&
@@ -2592,7 +2564,6 @@ export class NeuroLink {
2592
2564
  return result;
2593
2565
  }
2594
2566
  catch (error) {
2595
- // ADD: Emergency error event emission (fallback)
2596
2567
  const errorToEmit = error instanceof Error ? error : new Error(String(error));
2597
2568
  this.emitter.emit("error", errorToEmit);
2598
2569
  // Check if tool was not found
@@ -2647,32 +2618,9 @@ export class NeuroLink {
2647
2618
  // Track memory usage for tool listing operations
2648
2619
  const { MemoryManager } = await import("./utils/performance.js");
2649
2620
  const startMemory = MemoryManager.getMemoryUsageMB();
2650
- logger.debug(`[NeuroLink] 📊 LOG_POINT_A002_MEMORY_BASELINE`, {
2651
- logPoint: "A002_MEMORY_BASELINE",
2652
- getAllToolsId,
2653
- timestamp: new Date().toISOString(),
2654
- elapsedMs: Date.now() - getAllToolsStartTime,
2655
- elapsedNs: (process.hrtime.bigint() - getAllToolsHrTimeStart).toString(),
2656
- memoryBaseline: startMemory,
2657
- heapUsed: startMemory.heapUsed,
2658
- heapTotal: startMemory.heapTotal,
2659
- external: startMemory.external,
2660
- message: "Established memory baseline before tool enumeration",
2661
- });
2662
2621
  try {
2663
2622
  // Optimized: Collect all tools with minimal object creation
2664
2623
  const allTools = new Map();
2665
- // 🚀 EXHAUSTIVE LOGGING POINT A003: MCP TOOLS COLLECTION START
2666
- const mcpToolsStartTime = process.hrtime.bigint();
2667
- logger.debug(`[NeuroLink] 🔧 LOG_POINT_A003_MCP_TOOLS_START`, {
2668
- logPoint: "A003_MCP_TOOLS_START",
2669
- getAllToolsId,
2670
- timestamp: new Date().toISOString(),
2671
- elapsedMs: Date.now() - getAllToolsStartTime,
2672
- elapsedNs: (process.hrtime.bigint() - getAllToolsHrTimeStart).toString(),
2673
- mcpToolsStartTimeNs: mcpToolsStartTime.toString(),
2674
- message: "Starting MCP server tools collection",
2675
- });
2676
2624
  // 1. Add MCP server tools (built-in direct tools)
2677
2625
  const mcpToolsRaw = await toolRegistry.listTools();
2678
2626
  for (const tool of mcpToolsRaw) {
@@ -2826,9 +2774,20 @@ export class NeuroLink {
2826
2774
  if (!response.ok) {
2827
2775
  throw new Error("Ollama service not responding");
2828
2776
  }
2829
- const { models } = await response.json();
2777
+ const responseData = await response.json();
2778
+ const models = responseData?.models;
2830
2779
  const defaultOllamaModel = "llama3.2:latest";
2831
- const modelIsAvailable = models.some((m) => m.name === defaultOllamaModel);
2780
+ // Runtime-safe guard: ensure models is an array with valid objects
2781
+ if (!Array.isArray(models)) {
2782
+ logger.warn("Ollama API returned invalid models format in testProvider", {
2783
+ responseData,
2784
+ modelsType: typeof models,
2785
+ });
2786
+ throw new Error("Invalid models format from Ollama API");
2787
+ }
2788
+ // Filter and validate models before comparison
2789
+ const validModels = models.filter((m) => m && typeof m === "object" && typeof m.name === "string");
2790
+ const modelIsAvailable = validModels.some((m) => m.name === defaultOllamaModel);
2832
2791
  if (modelIsAvailable) {
2833
2792
  return {
2834
2793
  provider: providerName,