@juspay/neurolink 9.15.0 → 9.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/adapters/video/videoAnalyzer.js +10 -8
- package/dist/cli/commands/setup-anthropic.js +1 -14
- package/dist/cli/commands/setup-azure.js +1 -12
- package/dist/cli/commands/setup-bedrock.js +1 -9
- package/dist/cli/commands/setup-google-ai.js +1 -12
- package/dist/cli/commands/setup-openai.js +1 -14
- package/dist/cli/commands/workflow.d.ts +27 -0
- package/dist/cli/commands/workflow.js +216 -0
- package/dist/cli/factories/commandFactory.js +79 -20
- package/dist/cli/index.js +0 -1
- package/dist/cli/parser.js +4 -1
- package/dist/cli/utils/maskCredential.d.ts +11 -0
- package/dist/cli/utils/maskCredential.js +23 -0
- package/dist/constants/contextWindows.js +107 -16
- package/dist/constants/enums.d.ts +99 -15
- package/dist/constants/enums.js +152 -22
- package/dist/context/budgetChecker.js +1 -1
- package/dist/context/contextCompactor.js +31 -4
- package/dist/context/emergencyTruncation.d.ts +21 -0
- package/dist/context/emergencyTruncation.js +88 -0
- package/dist/context/errorDetection.d.ts +16 -0
- package/dist/context/errorDetection.js +48 -1
- package/dist/context/errors.d.ts +19 -0
- package/dist/context/errors.js +21 -0
- package/dist/context/stages/slidingWindowTruncator.d.ts +6 -0
- package/dist/context/stages/slidingWindowTruncator.js +159 -24
- package/dist/core/baseProvider.js +306 -200
- package/dist/core/conversationMemoryManager.js +104 -61
- package/dist/core/evaluationProviders.js +16 -33
- package/dist/core/factory.js +237 -164
- package/dist/core/modules/GenerationHandler.js +175 -116
- package/dist/core/modules/MessageBuilder.js +222 -170
- package/dist/core/modules/StreamHandler.d.ts +1 -0
- package/dist/core/modules/StreamHandler.js +95 -27
- package/dist/core/modules/TelemetryHandler.d.ts +10 -1
- package/dist/core/modules/TelemetryHandler.js +25 -7
- package/dist/core/modules/ToolsManager.js +115 -191
- package/dist/core/redisConversationMemoryManager.js +418 -282
- package/dist/factories/providerRegistry.d.ts +5 -0
- package/dist/factories/providerRegistry.js +20 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -2
- package/dist/lib/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/lib/adapters/video/videoAnalyzer.js +10 -8
- package/dist/lib/constants/contextWindows.js +107 -16
- package/dist/lib/constants/enums.d.ts +99 -15
- package/dist/lib/constants/enums.js +152 -22
- package/dist/lib/context/budgetChecker.js +1 -1
- package/dist/lib/context/contextCompactor.js +31 -4
- package/dist/lib/context/emergencyTruncation.d.ts +21 -0
- package/dist/lib/context/emergencyTruncation.js +89 -0
- package/dist/lib/context/errorDetection.d.ts +16 -0
- package/dist/lib/context/errorDetection.js +48 -1
- package/dist/lib/context/errors.d.ts +19 -0
- package/dist/lib/context/errors.js +22 -0
- package/dist/lib/context/stages/slidingWindowTruncator.d.ts +6 -0
- package/dist/lib/context/stages/slidingWindowTruncator.js +159 -24
- package/dist/lib/core/baseProvider.js +306 -200
- package/dist/lib/core/conversationMemoryManager.js +104 -61
- package/dist/lib/core/evaluationProviders.js +16 -33
- package/dist/lib/core/factory.js +237 -164
- package/dist/lib/core/modules/GenerationHandler.js +175 -116
- package/dist/lib/core/modules/MessageBuilder.js +222 -170
- package/dist/lib/core/modules/StreamHandler.d.ts +1 -0
- package/dist/lib/core/modules/StreamHandler.js +95 -27
- package/dist/lib/core/modules/TelemetryHandler.d.ts +10 -1
- package/dist/lib/core/modules/TelemetryHandler.js +25 -7
- package/dist/lib/core/modules/ToolsManager.js +115 -191
- package/dist/lib/core/redisConversationMemoryManager.js +418 -282
- package/dist/lib/factories/providerRegistry.d.ts +5 -0
- package/dist/lib/factories/providerRegistry.js +20 -2
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +4 -2
- package/dist/lib/mcp/externalServerManager.js +66 -0
- package/dist/lib/mcp/mcpCircuitBreaker.js +24 -0
- package/dist/lib/mcp/mcpClientFactory.js +16 -0
- package/dist/lib/mcp/toolDiscoveryService.js +32 -6
- package/dist/lib/mcp/toolRegistry.js +193 -123
- package/dist/lib/neurolink.d.ts +6 -0
- package/dist/lib/neurolink.js +1162 -646
- package/dist/lib/providers/amazonBedrock.d.ts +1 -1
- package/dist/lib/providers/amazonBedrock.js +521 -319
- package/dist/lib/providers/anthropic.js +73 -17
- package/dist/lib/providers/anthropicBaseProvider.js +77 -17
- package/dist/lib/providers/googleAiStudio.d.ts +1 -1
- package/dist/lib/providers/googleAiStudio.js +292 -227
- package/dist/lib/providers/googleVertex.d.ts +36 -1
- package/dist/lib/providers/googleVertex.js +553 -260
- package/dist/lib/providers/ollama.js +329 -278
- package/dist/lib/providers/openAI.js +77 -19
- package/dist/lib/providers/sagemaker/parsers.js +3 -3
- package/dist/lib/providers/sagemaker/streaming.js +3 -3
- package/dist/lib/proxy/proxyFetch.js +81 -48
- package/dist/lib/rag/ChunkerFactory.js +1 -1
- package/dist/lib/rag/chunkers/MarkdownChunker.d.ts +22 -0
- package/dist/lib/rag/chunkers/MarkdownChunker.js +213 -9
- package/dist/lib/rag/chunking/markdownChunker.d.ts +16 -0
- package/dist/lib/rag/chunking/markdownChunker.js +174 -2
- package/dist/lib/rag/pipeline/contextAssembly.js +2 -1
- package/dist/lib/rag/ragIntegration.d.ts +18 -1
- package/dist/lib/rag/ragIntegration.js +94 -14
- package/dist/lib/rag/retrieval/vectorQueryTool.js +21 -4
- package/dist/lib/server/abstract/baseServerAdapter.js +4 -1
- package/dist/lib/server/adapters/fastifyAdapter.js +35 -30
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +32 -0
- package/dist/lib/services/server/ai/observability/instrumentation.js +39 -0
- package/dist/lib/telemetry/attributes.d.ts +52 -0
- package/dist/lib/telemetry/attributes.js +61 -0
- package/dist/lib/telemetry/index.d.ts +3 -0
- package/dist/lib/telemetry/index.js +3 -0
- package/dist/lib/telemetry/telemetryService.d.ts +6 -0
- package/dist/lib/telemetry/telemetryService.js +6 -0
- package/dist/lib/telemetry/tracers.d.ts +15 -0
- package/dist/lib/telemetry/tracers.js +17 -0
- package/dist/lib/telemetry/withSpan.d.ts +9 -0
- package/dist/lib/telemetry/withSpan.js +35 -0
- package/dist/lib/types/contextTypes.d.ts +10 -0
- package/dist/lib/types/streamTypes.d.ts +14 -0
- package/dist/lib/utils/conversationMemory.js +121 -82
- package/dist/lib/utils/logger.d.ts +5 -0
- package/dist/lib/utils/logger.js +50 -2
- package/dist/lib/utils/messageBuilder.js +22 -42
- package/dist/lib/utils/modelDetection.js +3 -3
- package/dist/lib/utils/providerRetry.d.ts +41 -0
- package/dist/lib/utils/providerRetry.js +114 -0
- package/dist/lib/utils/retryability.d.ts +14 -0
- package/dist/lib/utils/retryability.js +23 -0
- package/dist/lib/utils/sanitizers/svg.js +4 -5
- package/dist/lib/utils/tokenEstimation.d.ts +11 -1
- package/dist/lib/utils/tokenEstimation.js +19 -4
- package/dist/lib/utils/videoAnalysisProcessor.js +7 -3
- package/dist/mcp/externalServerManager.js +66 -0
- package/dist/mcp/mcpCircuitBreaker.js +24 -0
- package/dist/mcp/mcpClientFactory.js +16 -0
- package/dist/mcp/toolDiscoveryService.js +32 -6
- package/dist/mcp/toolRegistry.js +193 -123
- package/dist/neurolink.d.ts +6 -0
- package/dist/neurolink.js +1162 -646
- package/dist/providers/amazonBedrock.d.ts +1 -1
- package/dist/providers/amazonBedrock.js +521 -319
- package/dist/providers/anthropic.js +73 -17
- package/dist/providers/anthropicBaseProvider.js +77 -17
- package/dist/providers/googleAiStudio.d.ts +1 -1
- package/dist/providers/googleAiStudio.js +292 -227
- package/dist/providers/googleVertex.d.ts +36 -1
- package/dist/providers/googleVertex.js +553 -260
- package/dist/providers/ollama.js +329 -278
- package/dist/providers/openAI.js +77 -19
- package/dist/providers/sagemaker/parsers.js +3 -3
- package/dist/providers/sagemaker/streaming.js +3 -3
- package/dist/proxy/proxyFetch.js +81 -48
- package/dist/rag/ChunkerFactory.js +1 -1
- package/dist/rag/chunkers/MarkdownChunker.d.ts +22 -0
- package/dist/rag/chunkers/MarkdownChunker.js +213 -9
- package/dist/rag/chunking/markdownChunker.d.ts +16 -0
- package/dist/rag/chunking/markdownChunker.js +174 -2
- package/dist/rag/pipeline/contextAssembly.js +2 -1
- package/dist/rag/ragIntegration.d.ts +18 -1
- package/dist/rag/ragIntegration.js +94 -14
- package/dist/rag/retrieval/vectorQueryTool.js +21 -4
- package/dist/server/abstract/baseServerAdapter.js +4 -1
- package/dist/server/adapters/fastifyAdapter.js +35 -30
- package/dist/services/server/ai/observability/instrumentation.d.ts +32 -0
- package/dist/services/server/ai/observability/instrumentation.js +39 -0
- package/dist/telemetry/attributes.d.ts +52 -0
- package/dist/telemetry/attributes.js +60 -0
- package/dist/telemetry/index.d.ts +3 -0
- package/dist/telemetry/index.js +3 -0
- package/dist/telemetry/telemetryService.d.ts +6 -0
- package/dist/telemetry/telemetryService.js +6 -0
- package/dist/telemetry/tracers.d.ts +15 -0
- package/dist/telemetry/tracers.js +16 -0
- package/dist/telemetry/withSpan.d.ts +9 -0
- package/dist/telemetry/withSpan.js +34 -0
- package/dist/types/contextTypes.d.ts +10 -0
- package/dist/types/streamTypes.d.ts +14 -0
- package/dist/utils/conversationMemory.js +121 -82
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +50 -2
- package/dist/utils/messageBuilder.js +22 -42
- package/dist/utils/modelDetection.js +3 -3
- package/dist/utils/providerRetry.d.ts +41 -0
- package/dist/utils/providerRetry.js +113 -0
- package/dist/utils/retryability.d.ts +14 -0
- package/dist/utils/retryability.js +22 -0
- package/dist/utils/sanitizers/svg.js +4 -5
- package/dist/utils/tokenEstimation.d.ts +11 -1
- package/dist/utils/tokenEstimation.js +19 -4
- package/dist/utils/videoAnalysisProcessor.js +7 -3
- package/dist/workflow/config.d.ts +26 -26
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Provides fault tolerance and prevents cascading failures
|
|
5
5
|
*/
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
|
+
import { trace } from "@opentelemetry/api";
|
|
7
8
|
import { mcpLogger } from "../utils/logger.js";
|
|
8
9
|
/**
|
|
9
10
|
* MCPCircuitBreaker
|
|
@@ -53,6 +54,17 @@ export class MCPCircuitBreaker extends EventEmitter {
|
|
|
53
54
|
this.halfOpenCalls >= this.config.halfOpenMaxCalls) {
|
|
54
55
|
throw new Error(`Circuit breaker '${this.name}' is half-open but call limit reached`);
|
|
55
56
|
}
|
|
57
|
+
// NLK-GAP-009: Record half-open test event when executing in half-open state
|
|
58
|
+
if (this.state === "half-open") {
|
|
59
|
+
const activeSpan = trace.getActiveSpan();
|
|
60
|
+
if (activeSpan) {
|
|
61
|
+
activeSpan.addEvent("circuit.half_open_test", {
|
|
62
|
+
"circuit.name": this.name,
|
|
63
|
+
"circuit.half_open_call": this.halfOpenCalls + 1,
|
|
64
|
+
"circuit.half_open_max_calls": this.config.halfOpenMaxCalls,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
56
68
|
// Execute operation with timeout
|
|
57
69
|
const result = await Promise.race([
|
|
58
70
|
operation(),
|
|
@@ -145,6 +157,18 @@ export class MCPCircuitBreaker extends EventEmitter {
|
|
|
145
157
|
const oldState = this.state;
|
|
146
158
|
this.state = newState;
|
|
147
159
|
this.lastStateChange = new Date();
|
|
160
|
+
// NLK-GAP-009: Record state transition on active OTel span
|
|
161
|
+
const activeSpan = trace.getActiveSpan();
|
|
162
|
+
if (activeSpan) {
|
|
163
|
+
activeSpan.addEvent("circuit.state_change", {
|
|
164
|
+
"circuit.name": this.name,
|
|
165
|
+
"circuit.from_state": oldState,
|
|
166
|
+
"circuit.to_state": newState,
|
|
167
|
+
"circuit.reason": reason.slice(0, 128),
|
|
168
|
+
"circuit.failure_count": this.callHistory.filter((c) => !c.success)
|
|
169
|
+
.length,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
148
172
|
// Reset counters based on state
|
|
149
173
|
if (newState === "half-open") {
|
|
150
174
|
this.halfOpenCalls = 0;
|
|
@@ -15,6 +15,8 @@ import { globalCircuitBreakerManager } from "./mcpCircuitBreaker.js";
|
|
|
15
15
|
import { withHTTPRetry, DEFAULT_HTTP_RETRY_CONFIG, } from "./httpRetryHandler.js";
|
|
16
16
|
import { globalRateLimiterManager } from "./httpRateLimiter.js";
|
|
17
17
|
import { NeuroLinkOAuthProvider, InMemoryTokenStorage } from "./auth/index.js";
|
|
18
|
+
import { tracers } from "../telemetry/tracers.js";
|
|
19
|
+
import { SpanStatusCode } from "@opentelemetry/api";
|
|
18
20
|
/**
|
|
19
21
|
* MCPClientFactory
|
|
20
22
|
* Factory class for creating MCP clients with different transports
|
|
@@ -36,6 +38,13 @@ export class MCPClientFactory {
|
|
|
36
38
|
*/
|
|
37
39
|
static async createClient(config, timeout = 10000) {
|
|
38
40
|
const startTime = Date.now();
|
|
41
|
+
const span = tracers.mcp.startSpan("neurolink.mcp.client.create", {
|
|
42
|
+
attributes: {
|
|
43
|
+
"mcp.server_id": config.id,
|
|
44
|
+
"mcp.transport": config.transport,
|
|
45
|
+
"mcp.timeout_ms": timeout,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
39
48
|
try {
|
|
40
49
|
mcpLogger.info(`[MCPClientFactory] Creating client for ${config.id}`, {
|
|
41
50
|
transport: config.transport,
|
|
@@ -94,6 +103,7 @@ export class MCPClientFactory {
|
|
|
94
103
|
duration: Date.now() - startTime,
|
|
95
104
|
capabilities: result.capabilities,
|
|
96
105
|
});
|
|
106
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
97
107
|
return {
|
|
98
108
|
...result,
|
|
99
109
|
success: true,
|
|
@@ -103,12 +113,18 @@ export class MCPClientFactory {
|
|
|
103
113
|
catch (error) {
|
|
104
114
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
105
115
|
mcpLogger.error(`[MCPClientFactory] Failed to create client for ${config.id}:`, error);
|
|
116
|
+
// NLK-GAP-004 fix: Record both exception AND error status on span
|
|
117
|
+
span.recordException(error instanceof Error ? error : new Error(errorMessage));
|
|
118
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage });
|
|
106
119
|
return {
|
|
107
120
|
success: false,
|
|
108
121
|
error: errorMessage,
|
|
109
122
|
duration: Date.now() - startTime,
|
|
110
123
|
};
|
|
111
124
|
}
|
|
125
|
+
finally {
|
|
126
|
+
span.end();
|
|
127
|
+
}
|
|
112
128
|
}
|
|
113
129
|
/**
|
|
114
130
|
* Internal client creation logic
|
|
@@ -8,6 +8,10 @@ import { mcpLogger } from "../utils/logger.js";
|
|
|
8
8
|
import { globalCircuitBreakerManager } from "./mcpCircuitBreaker.js";
|
|
9
9
|
import { isObject, isNullish } from "../utils/typeUtils.js";
|
|
10
10
|
import { validateToolName, validateToolDescription, } from "../utils/parameterValidation.js";
|
|
11
|
+
import { withTimeout } from "../utils/errorHandling.js";
|
|
12
|
+
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
13
|
+
import { tracers } from "../telemetry/tracers.js";
|
|
14
|
+
const mcpTracer = tracers.mcp;
|
|
11
15
|
/**
|
|
12
16
|
* ToolDiscoveryService
|
|
13
17
|
* Handles automatic tool discovery and registration from external MCP servers
|
|
@@ -336,13 +340,35 @@ export class ToolDiscoveryService extends EventEmitter {
|
|
|
336
340
|
});
|
|
337
341
|
// Execute tool with circuit breaker protection
|
|
338
342
|
const result = await circuitBreaker.execute(async () => {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
+
return mcpTracer.startActiveSpan("neurolink.mcp.callTool", {
|
|
344
|
+
kind: SpanKind.CLIENT,
|
|
345
|
+
attributes: {
|
|
346
|
+
"mcp.server_id": serverId,
|
|
347
|
+
"mcp.tool_name": toolName,
|
|
348
|
+
"mcp.timeout_ms": options.timeout || 30000,
|
|
349
|
+
},
|
|
350
|
+
}, async (callSpan) => {
|
|
351
|
+
try {
|
|
352
|
+
const timeout = options.timeout || 30000;
|
|
353
|
+
const callResult = await withTimeout(client.callTool({
|
|
354
|
+
name: toolName,
|
|
355
|
+
arguments: parameters,
|
|
356
|
+
}), timeout, new Error(`Tool execution timeout: ${toolName}`));
|
|
357
|
+
callSpan.setStatus({ code: SpanStatusCode.OK });
|
|
358
|
+
return callResult;
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
callSpan.setStatus({
|
|
362
|
+
code: SpanStatusCode.ERROR,
|
|
363
|
+
message: err.message,
|
|
364
|
+
});
|
|
365
|
+
callSpan.recordException(err);
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
finally {
|
|
369
|
+
callSpan.end();
|
|
370
|
+
}
|
|
343
371
|
});
|
|
344
|
-
const timeoutPromise = this.createTimeoutPromise(timeout, `Tool execution timeout: ${toolName}`);
|
|
345
|
-
return await Promise.race([executePromise, timeoutPromise]);
|
|
346
372
|
});
|
|
347
373
|
const duration = Date.now() - startTime;
|
|
348
374
|
// Update tool statistics
|
package/dist/mcp/toolRegistry.js
CHANGED
|
@@ -10,6 +10,7 @@ import { directAgentTools } from "../agent/directTools.js";
|
|
|
10
10
|
import { detectCategory, createMCPServerInfo } from "../utils/mcpDefaults.js";
|
|
11
11
|
import { FlexibleToolValidator } from "./flexibleToolValidator.js";
|
|
12
12
|
import { HITLUserRejectedError, HITLTimeoutError } from "../hitl/hitlErrors.js";
|
|
13
|
+
import { withSpan, tracers, ATTR } from "../telemetry/index.js";
|
|
13
14
|
export class MCPToolRegistry extends MCPRegistry {
|
|
14
15
|
tools = new Map();
|
|
15
16
|
toolImplementations = new Map(); // Store actual tool implementations
|
|
@@ -227,149 +228,218 @@ export class MCPToolRegistry extends MCPRegistry {
|
|
|
227
228
|
*/
|
|
228
229
|
async executeTool(toolName, args, context) {
|
|
229
230
|
const startTime = Date.now();
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
231
|
+
// Resolve serverId eagerly for span attributes
|
|
232
|
+
let preResolvedServerId;
|
|
233
|
+
const toolEntry = this.tools.get(toolName);
|
|
234
|
+
if (toolEntry) {
|
|
235
|
+
preResolvedServerId = toolEntry.serverId;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
for (const toolInfo of this.tools.values()) {
|
|
239
|
+
if (toolInfo.name === toolName) {
|
|
240
|
+
preResolvedServerId = toolInfo.serverId;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return withSpan({
|
|
246
|
+
name: "neurolink.tool.execute",
|
|
247
|
+
tracer: tracers.mcp,
|
|
248
|
+
attributes: {
|
|
249
|
+
[ATTR.GEN_AI_TOOL_NAME]: toolName,
|
|
250
|
+
[ATTR.MCP_SERVER_ID]: preResolvedServerId || "builtin",
|
|
251
|
+
},
|
|
252
|
+
}, async (span) => {
|
|
253
|
+
try {
|
|
254
|
+
registryLogger.info(`🔧 [TOOL_EXECUTION] Starting execution: ${toolName}`, {
|
|
255
|
+
hasArgs: args !== undefined,
|
|
256
|
+
hasContext: context !== undefined,
|
|
257
|
+
sessionId: context?.sessionId,
|
|
258
|
+
});
|
|
259
|
+
// Try to find the tool by fully-qualified name first
|
|
260
|
+
let tool = this.tools.get(toolName);
|
|
261
|
+
registryLogger.info(`🔍 [TOOL_LOOKUP] Direct lookup result for '${toolName}':`, !!tool);
|
|
262
|
+
// If not found, search for tool by name across all entries (for backward compatibility)
|
|
263
|
+
let toolId = toolName;
|
|
264
|
+
if (!tool) {
|
|
265
|
+
const matches = Array.from(this.tools.entries()).filter(([, toolInfo]) => toolInfo.name === toolName);
|
|
266
|
+
if (matches.length > 1) {
|
|
267
|
+
throw new Error(`Ambiguous tool name '${toolName}'. Use fully-qualified name 'serverId.${toolName}'.`);
|
|
268
|
+
}
|
|
269
|
+
if (matches.length === 1) {
|
|
270
|
+
const [candidateToolId, toolInfo] = matches[0];
|
|
240
271
|
tool = toolInfo;
|
|
241
272
|
toolId = candidateToolId;
|
|
242
|
-
break;
|
|
243
273
|
}
|
|
244
274
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
if (!tool) {
|
|
276
|
+
throw new Error(`Tool '${toolName}' not found in registry`);
|
|
277
|
+
}
|
|
278
|
+
// Classify tool type for observability
|
|
279
|
+
const serverId = tool.serverId || "unknown";
|
|
280
|
+
const toolType = serverId === "direct"
|
|
281
|
+
? "builtin"
|
|
282
|
+
: serverId.startsWith("custom-tool-")
|
|
283
|
+
? "custom"
|
|
284
|
+
: "mcp";
|
|
285
|
+
span.setAttribute("tool.type", toolType);
|
|
286
|
+
span.setAttribute(ATTR.MCP_SERVER_ID, serverId);
|
|
287
|
+
// Create execution context if not provided
|
|
288
|
+
const execContext = {
|
|
289
|
+
...context,
|
|
290
|
+
sessionId: context?.sessionId ?? randomUUID(),
|
|
291
|
+
userId: context?.userId,
|
|
292
|
+
};
|
|
293
|
+
// Get the tool implementation using the resolved toolId
|
|
294
|
+
const toolImpl = this.toolImplementations.get(toolId);
|
|
295
|
+
registryLogger.debug(`Looking for tool '${toolName}' (toolId: '${toolId}'), found: ${!!toolImpl}, type: ${typeof toolImpl?.execute}`);
|
|
296
|
+
registryLogger.debug(`Available tools:`, Array.from(this.toolImplementations.keys()));
|
|
297
|
+
if (!toolImpl || typeof toolImpl?.execute !== "function") {
|
|
298
|
+
throw new Error(`Tool '${toolName}' implementation not found or not executable`);
|
|
299
|
+
}
|
|
300
|
+
// Capture argument metadata (avoid logging raw values which may contain secrets)
|
|
301
|
+
let argsStr;
|
|
302
|
+
try {
|
|
303
|
+
argsStr = JSON.stringify(args).slice(0, 4096);
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
argsStr = "[unserializable]";
|
|
307
|
+
}
|
|
308
|
+
span.setAttribute("tool.arguments_present", args !== undefined);
|
|
309
|
+
span.setAttribute("tool.arguments_size", argsStr.length);
|
|
310
|
+
// HITL Safety Check: Request confirmation if required
|
|
311
|
+
let finalArgs = args;
|
|
312
|
+
if (this.hitlManager && this.hitlManager.isEnabled()) {
|
|
313
|
+
const requiresConfirmation = this.hitlManager.requiresConfirmation(toolName, args);
|
|
314
|
+
if (requiresConfirmation) {
|
|
315
|
+
registryLogger.info(`Tool '${toolName}' requires HITL confirmation`);
|
|
316
|
+
span.addEvent("tool.hitl_requested");
|
|
317
|
+
try {
|
|
318
|
+
const confirmationResult = await this.hitlManager.requestConfirmation(toolName, args, {
|
|
319
|
+
serverId: tool.serverId,
|
|
320
|
+
sessionId: execContext.sessionId,
|
|
321
|
+
userId: execContext.userId,
|
|
322
|
+
});
|
|
323
|
+
if (!confirmationResult.approved) {
|
|
324
|
+
// User rejected the tool execution
|
|
325
|
+
span.addEvent("tool.hitl_rejected");
|
|
326
|
+
throw new HITLUserRejectedError(`Tool execution rejected by user: ${confirmationResult.reason || "No reason provided"}`, toolName, confirmationResult.reason);
|
|
327
|
+
}
|
|
328
|
+
span.addEvent("tool.hitl_approved");
|
|
329
|
+
// User approved - use modified arguments if provided
|
|
330
|
+
if (confirmationResult.modifiedArguments !== undefined) {
|
|
331
|
+
finalArgs = confirmationResult.modifiedArguments;
|
|
332
|
+
registryLogger.info(`Tool '${toolName}' arguments modified by user`);
|
|
333
|
+
}
|
|
334
|
+
registryLogger.info(`Tool '${toolName}' approved for execution (response time: ${confirmationResult.responseTime}ms)`);
|
|
277
335
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
336
|
+
catch (error) {
|
|
337
|
+
if (error instanceof HITLTimeoutError) {
|
|
338
|
+
// Timeout occurred - user didn't respond in time
|
|
339
|
+
registryLogger.warn(`Tool '${toolName}' execution timed out waiting for user confirmation`);
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
else if (error instanceof HITLUserRejectedError) {
|
|
343
|
+
// User explicitly rejected
|
|
344
|
+
registryLogger.info(`Tool '${toolName}' execution rejected by user`);
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
// Other HITL error (configuration, system error, etc.)
|
|
349
|
+
registryLogger.error(`HITL confirmation failed for tool '${toolName}':`, error);
|
|
350
|
+
throw new Error(`HITL confirmation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
351
|
+
}
|
|
282
352
|
}
|
|
283
|
-
registryLogger.info(`Tool '${toolName}' approved for execution (response time: ${confirmationResult.responseTime}ms)`);
|
|
284
353
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// Timeout occurred - user didn't respond in time
|
|
288
|
-
registryLogger.warn(`Tool '${toolName}' execution timed out waiting for user confirmation`);
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
291
|
-
else if (error instanceof HITLUserRejectedError) {
|
|
292
|
-
// User explicitly rejected
|
|
293
|
-
registryLogger.info(`Tool '${toolName}' execution rejected by user`);
|
|
294
|
-
throw error;
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
// Other HITL error (configuration, system error, etc.)
|
|
298
|
-
registryLogger.error(`HITL confirmation failed for tool '${toolName}':`, error);
|
|
299
|
-
throw new Error(`HITL confirmation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
300
|
-
}
|
|
354
|
+
else {
|
|
355
|
+
registryLogger.debug(`Tool '${toolName}' does not require HITL confirmation`);
|
|
301
356
|
}
|
|
302
357
|
}
|
|
358
|
+
// Execute the actual tool (with potentially modified arguments)
|
|
359
|
+
registryLogger.debug(`Executing tool '${toolName}' with args:`, finalArgs);
|
|
360
|
+
const toolResult = await toolImpl.execute(finalArgs, execContext);
|
|
361
|
+
// Properly wrap raw results in ToolResult format
|
|
362
|
+
let result;
|
|
363
|
+
// Check if result is already a ToolResult object
|
|
364
|
+
if (toolResult &&
|
|
365
|
+
typeof toolResult === "object" &&
|
|
366
|
+
"success" in toolResult &&
|
|
367
|
+
typeof toolResult.success === "boolean") {
|
|
368
|
+
// Result is already a ToolResult, enhance with metadata
|
|
369
|
+
const toolResultObj = toolResult;
|
|
370
|
+
result = {
|
|
371
|
+
...toolResultObj,
|
|
372
|
+
usage: {
|
|
373
|
+
...(toolResultObj.usage || {}),
|
|
374
|
+
executionTime: Date.now() - startTime,
|
|
375
|
+
},
|
|
376
|
+
metadata: {
|
|
377
|
+
...(toolResultObj.metadata || {}),
|
|
378
|
+
toolName,
|
|
379
|
+
serverId: tool.serverId,
|
|
380
|
+
sessionId: execContext.sessionId,
|
|
381
|
+
executionTime: Date.now() - startTime,
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
303
385
|
else {
|
|
304
|
-
|
|
386
|
+
// Result is a raw value, wrap it in ToolResult format
|
|
387
|
+
result = {
|
|
388
|
+
success: true,
|
|
389
|
+
data: toolResult,
|
|
390
|
+
usage: {
|
|
391
|
+
executionTime: Date.now() - startTime,
|
|
392
|
+
},
|
|
393
|
+
metadata: {
|
|
394
|
+
toolName,
|
|
395
|
+
serverId: tool.serverId,
|
|
396
|
+
sessionId: execContext.sessionId,
|
|
397
|
+
executionTime: Date.now() - startTime,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
305
400
|
}
|
|
401
|
+
// Update statistics
|
|
402
|
+
const duration = Date.now() - startTime;
|
|
403
|
+
this.updateStats(toolName, duration);
|
|
404
|
+
// Record success on span
|
|
405
|
+
let resultStr;
|
|
406
|
+
try {
|
|
407
|
+
resultStr = JSON.stringify(result.data);
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
resultStr = "[unserializable]";
|
|
411
|
+
}
|
|
412
|
+
span.setAttribute("tool.result_length", resultStr.length);
|
|
413
|
+
span.setAttribute("tool.success", true);
|
|
414
|
+
registryLogger.debug(`Tool '${toolName}' executed successfully in ${duration}ms`);
|
|
415
|
+
return result;
|
|
306
416
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
//
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
...(toolResultObj.usage || {}),
|
|
323
|
-
executionTime: Date.now() - startTime,
|
|
324
|
-
},
|
|
325
|
-
metadata: {
|
|
326
|
-
...(toolResultObj.metadata || {}),
|
|
327
|
-
toolName,
|
|
328
|
-
serverId: tool.serverId,
|
|
329
|
-
sessionId: execContext.sessionId,
|
|
330
|
-
executionTime: Date.now() - startTime,
|
|
331
|
-
},
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
// Result is a raw value, wrap it in ToolResult format
|
|
336
|
-
result = {
|
|
337
|
-
success: true,
|
|
338
|
-
data: toolResult,
|
|
417
|
+
catch (error) {
|
|
418
|
+
registryLogger.error(`Tool execution failed: ${toolName}`, error);
|
|
419
|
+
// Record failure on span
|
|
420
|
+
span.setAttribute("tool.success", false);
|
|
421
|
+
// Rethrow precondition errors (tool not found, not executable)
|
|
422
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
423
|
+
if (errMsg.includes("not found in registry") ||
|
|
424
|
+
errMsg.includes("not executable")) {
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
// Return runtime execution errors in ToolResult format
|
|
428
|
+
const errorResult = {
|
|
429
|
+
success: false,
|
|
430
|
+
data: null,
|
|
431
|
+
error: error instanceof Error ? error.message : String(error),
|
|
339
432
|
usage: {
|
|
340
433
|
executionTime: Date.now() - startTime,
|
|
341
434
|
},
|
|
342
435
|
metadata: {
|
|
343
436
|
toolName,
|
|
344
|
-
|
|
345
|
-
sessionId: execContext.sessionId,
|
|
346
|
-
executionTime: Date.now() - startTime,
|
|
437
|
+
sessionId: context?.sessionId,
|
|
347
438
|
},
|
|
348
439
|
};
|
|
440
|
+
return errorResult;
|
|
349
441
|
}
|
|
350
|
-
|
|
351
|
-
const duration = Date.now() - startTime;
|
|
352
|
-
this.updateStats(toolName, duration);
|
|
353
|
-
registryLogger.debug(`Tool '${toolName}' executed successfully in ${duration}ms`);
|
|
354
|
-
return result;
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
registryLogger.error(`Tool execution failed: ${toolName}`, error);
|
|
358
|
-
// Return error in ToolResult format
|
|
359
|
-
const errorResult = {
|
|
360
|
-
success: false,
|
|
361
|
-
data: null,
|
|
362
|
-
error: error instanceof Error ? error.message : String(error),
|
|
363
|
-
usage: {
|
|
364
|
-
executionTime: Date.now() - startTime,
|
|
365
|
-
},
|
|
366
|
-
metadata: {
|
|
367
|
-
toolName,
|
|
368
|
-
sessionId: context?.sessionId,
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
|
-
return errorResult;
|
|
372
|
-
}
|
|
442
|
+
});
|
|
373
443
|
}
|
|
374
444
|
async listTools(filterOrContext) {
|
|
375
445
|
// FIXED: Return unique tools (avoid duplicates from dual registration)
|
package/dist/neurolink.d.ts
CHANGED
|
@@ -95,6 +95,7 @@ import type { BatchOperationResult } from "./types/typeAliases.js";
|
|
|
95
95
|
*/
|
|
96
96
|
export declare class NeuroLink {
|
|
97
97
|
private mcpInitialized;
|
|
98
|
+
private mcpInitPromise;
|
|
98
99
|
private emitter;
|
|
99
100
|
private toolRegistry;
|
|
100
101
|
private autoDiscoveredServerInfos;
|
|
@@ -270,6 +271,11 @@ export declare class NeuroLink {
|
|
|
270
271
|
* Uses isolated async context to prevent hanging
|
|
271
272
|
*/
|
|
272
273
|
private initializeMCP;
|
|
274
|
+
/**
|
|
275
|
+
* Actual one-shot MCP initialization logic. Called at most once per
|
|
276
|
+
* NeuroLink instance lifetime (unless cleanup() resets the flag).
|
|
277
|
+
*/
|
|
278
|
+
private performMCPInitializationOnce;
|
|
273
279
|
/**
|
|
274
280
|
* Import performance manager with error handling
|
|
275
281
|
*/
|