@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.
- package/CHANGELOG.md +12 -0
- package/dist/cli/commands/config.d.ts +18 -18
- package/dist/cli/factories/commandFactory.d.ts +24 -0
- package/dist/cli/factories/commandFactory.js +297 -245
- package/dist/config/taskClassificationConfig.d.ts +51 -0
- package/dist/config/taskClassificationConfig.js +148 -0
- package/dist/core/baseProvider.d.ts +40 -3
- package/dist/core/baseProvider.js +689 -352
- package/dist/core/constants.d.ts +2 -30
- package/dist/core/constants.js +15 -43
- package/dist/factories/providerFactory.js +23 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -3
- package/dist/lib/config/taskClassificationConfig.d.ts +51 -0
- package/dist/lib/config/taskClassificationConfig.js +148 -0
- package/dist/lib/core/baseProvider.d.ts +40 -3
- package/dist/lib/core/baseProvider.js +689 -352
- package/dist/lib/core/constants.d.ts +2 -30
- package/dist/lib/core/constants.js +15 -43
- package/dist/lib/factories/providerFactory.js +23 -6
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/index.js +4 -3
- package/dist/lib/mcp/externalServerManager.js +2 -2
- package/dist/lib/mcp/registry.js +2 -2
- package/dist/lib/mcp/servers/agent/directToolsServer.js +19 -10
- package/dist/lib/mcp/toolRegistry.js +4 -8
- package/dist/lib/neurolink.d.ts +82 -27
- package/dist/lib/neurolink.js +672 -713
- package/dist/lib/providers/amazonBedrock.js +2 -2
- package/dist/lib/providers/googleVertex.d.ts +3 -23
- package/dist/lib/providers/googleVertex.js +14 -342
- package/dist/lib/providers/openAI.d.ts +23 -0
- package/dist/lib/providers/openAI.js +313 -6
- package/dist/lib/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/lib/sdk/toolRegistration.js +18 -1
- package/dist/lib/types/common.d.ts +98 -0
- package/dist/lib/types/index.d.ts +2 -0
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/streamTypes.d.ts +13 -6
- package/dist/lib/types/taskClassificationTypes.d.ts +52 -0
- package/dist/lib/types/taskClassificationTypes.js +5 -0
- package/dist/lib/types/typeAliases.d.ts +3 -2
- package/dist/lib/utils/modelRouter.d.ts +107 -0
- package/dist/lib/utils/modelRouter.js +292 -0
- package/dist/lib/utils/parameterValidation.js +6 -25
- package/dist/lib/utils/promptRedaction.d.ts +29 -0
- package/dist/lib/utils/promptRedaction.js +62 -0
- package/dist/lib/utils/schemaConversion.d.ts +14 -0
- package/dist/lib/utils/schemaConversion.js +140 -0
- package/dist/lib/utils/taskClassificationUtils.d.ts +55 -0
- package/dist/lib/utils/taskClassificationUtils.js +149 -0
- package/dist/lib/utils/taskClassifier.d.ts +23 -0
- package/dist/lib/utils/taskClassifier.js +94 -0
- package/dist/lib/utils/transformationUtils.js +143 -5
- package/dist/mcp/externalServerManager.js +2 -2
- package/dist/mcp/registry.js +2 -2
- package/dist/mcp/servers/agent/directToolsServer.js +19 -10
- package/dist/mcp/toolRegistry.js +4 -8
- package/dist/neurolink.d.ts +82 -27
- package/dist/neurolink.js +672 -713
- package/dist/providers/amazonBedrock.js +2 -2
- package/dist/providers/googleVertex.d.ts +3 -23
- package/dist/providers/googleVertex.js +14 -342
- package/dist/providers/openAI.d.ts +23 -0
- package/dist/providers/openAI.js +313 -6
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/sdk/toolRegistration.js +18 -1
- package/dist/types/common.d.ts +98 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/streamTypes.d.ts +13 -6
- package/dist/types/taskClassificationTypes.d.ts +52 -0
- package/dist/types/taskClassificationTypes.js +5 -0
- package/dist/types/typeAliases.d.ts +3 -2
- package/dist/utils/modelRouter.d.ts +107 -0
- package/dist/utils/modelRouter.js +292 -0
- package/dist/utils/parameterValidation.js +6 -25
- package/dist/utils/promptRedaction.d.ts +29 -0
- package/dist/utils/promptRedaction.js +62 -0
- package/dist/utils/schemaConversion.d.ts +14 -0
- package/dist/utils/schemaConversion.js +140 -0
- package/dist/utils/taskClassificationUtils.d.ts +55 -0
- package/dist/utils/taskClassificationUtils.js +149 -0
- package/dist/utils/taskClassifier.d.ts +23 -0
- package/dist/utils/taskClassifier.js +94 -0
- package/dist/utils/transformationUtils.js +143 -5
- package/package.json +3 -2
package/dist/lib/neurolink.js
CHANGED
@@ -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,
|
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
|
-
|
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
|
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
|
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.
|
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(
|
564
|
-
await this.initializeProviderRegistryInternal(
|
410
|
+
await this.initializeToolRegistryInternal();
|
411
|
+
await this.initializeProviderRegistryInternal();
|
565
412
|
await this.registerDirectToolsServerInternal(mcpInitId, mcpInitStartTime, mcpInitHrTimeStart);
|
566
|
-
await this.loadMCPConfigurationInternal(
|
413
|
+
await this.loadMCPConfigurationInternal();
|
567
414
|
}
|
568
415
|
/**
|
569
416
|
* Initialize tool registry with timeout protection
|
570
417
|
*/
|
571
|
-
async initializeToolRegistryInternal(
|
572
|
-
const
|
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(
|
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
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
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(
|
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
|
-
//
|
1291
|
-
|
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
|
-
//
|
1433
|
-
const
|
1434
|
-
|
1435
|
-
|
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
|
-
|
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.
|
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
|
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
|
-
|
2201
|
-
|
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
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
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,
|
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
|
-
//
|
2298
|
-
const
|
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
|
-
:
|
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
|
-
|
2580
|
-
|
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
|
2777
|
+
const responseData = await response.json();
|
2778
|
+
const models = responseData?.models;
|
2830
2779
|
const defaultOllamaModel = "llama3.2:latest";
|
2831
|
-
|
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,
|