@juspay/neurolink 9.14.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 +12 -0
- package/README.md +15 -15
- package/dist/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/adapters/video/videoAnalyzer.js +10 -8
- package/dist/auth/anthropicOAuth.d.ts +377 -0
- package/dist/auth/anthropicOAuth.js +914 -0
- package/dist/auth/index.d.ts +20 -0
- package/dist/auth/index.js +29 -0
- package/dist/auth/tokenStore.d.ts +225 -0
- package/dist/auth/tokenStore.js +521 -0
- package/dist/cli/commands/auth.d.ts +50 -0
- package/dist/cli/commands/auth.js +1115 -0
- 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/authCommandFactory.d.ts +52 -0
- package/dist/cli/factories/authCommandFactory.js +146 -0
- package/dist/cli/factories/commandFactory.d.ts +6 -0
- package/dist/cli/factories/commandFactory.js +171 -22
- package/dist/cli/index.js +0 -1
- package/dist/cli/parser.js +14 -2
- 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 +119 -15
- package/dist/constants/enums.js +182 -22
- package/dist/constants/index.d.ts +3 -1
- package/dist/constants/index.js +11 -1
- 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 +3 -3
- 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/auth/anthropicOAuth.d.ts +377 -0
- package/dist/lib/auth/anthropicOAuth.js +915 -0
- package/dist/lib/auth/index.d.ts +20 -0
- package/dist/lib/auth/index.js +30 -0
- package/dist/lib/auth/tokenStore.d.ts +225 -0
- package/dist/lib/auth/tokenStore.js +522 -0
- package/dist/lib/constants/contextWindows.js +107 -16
- package/dist/lib/constants/enums.d.ts +119 -15
- package/dist/lib/constants/enums.js +182 -22
- package/dist/lib/constants/index.d.ts +3 -1
- package/dist/lib/constants/index.js +11 -1
- 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 +3 -3
- 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/models/anthropicModels.d.ts +267 -0
- package/dist/lib/models/anthropicModels.js +528 -0
- 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.d.ts +123 -2
- package/dist/lib/providers/anthropic.js +873 -27
- 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/errors.d.ts +62 -0
- package/dist/lib/types/errors.js +107 -0
- package/dist/lib/types/index.d.ts +2 -1
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/providers.d.ts +107 -0
- package/dist/lib/types/providers.js +69 -0
- package/dist/lib/types/streamTypes.d.ts +14 -0
- package/dist/lib/types/subscriptionTypes.d.ts +893 -0
- package/dist/lib/types/subscriptionTypes.js +8 -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/providerConfig.d.ts +167 -0
- package/dist/lib/utils/providerConfig.js +619 -9
- 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/models/anthropicModels.d.ts +267 -0
- package/dist/models/anthropicModels.js +527 -0
- 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.d.ts +123 -2
- package/dist/providers/anthropic.js +873 -27
- 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/errors.d.ts +62 -0
- package/dist/types/errors.js +107 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +2 -0
- package/dist/types/providers.d.ts +107 -0
- package/dist/types/providers.js +69 -0
- package/dist/types/streamTypes.d.ts +14 -0
- package/dist/types/subscriptionTypes.d.ts +893 -0
- package/dist/types/subscriptionTypes.js +7 -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/providerConfig.d.ts +167 -0
- package/dist/utils/providerConfig.js +619 -9
- 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 +2 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-level retry utility for AI SDK calls (NL11)
|
|
3
|
+
*
|
|
4
|
+
* The Vercel AI SDK's `generateText()` and `streamText()` have built-in retry
|
|
5
|
+
* logic (`_retryWithExponentialBackoff()` with default `maxRetries: 2`) that
|
|
6
|
+
* retries on HTTP 429/500/503. These retries are completely invisible to OTel
|
|
7
|
+
* because they happen inside the AI SDK.
|
|
8
|
+
*
|
|
9
|
+
* This module provides an instrumented retry wrapper that:
|
|
10
|
+
* 1. Disables the AI SDK's internal retries (via `maxRetries: 0`)
|
|
11
|
+
* 2. Implements our own retry loop with full OTel span events
|
|
12
|
+
* 3. Records retry attempts, delays, status codes, and total attempt count
|
|
13
|
+
*
|
|
14
|
+
* @module utils/providerRetry
|
|
15
|
+
*/
|
|
16
|
+
import {} from "@opentelemetry/api";
|
|
17
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
18
|
+
import { logger } from "./logger.js";
|
|
19
|
+
/** Maximum number of retry attempts after the initial call (total = 1 + MAX_PROVIDER_RETRIES). */
|
|
20
|
+
export const MAX_PROVIDER_RETRIES = 2;
|
|
21
|
+
/** Base delay in ms for exponential backoff between retries. */
|
|
22
|
+
export const BASE_RETRY_DELAY_MS = 1000;
|
|
23
|
+
/**
|
|
24
|
+
* Check whether an error thrown by the AI SDK is retryable.
|
|
25
|
+
*
|
|
26
|
+
* Uses `APICallError.isInstance()` for proper type-safe detection (the class
|
|
27
|
+
* uses a branded symbol marker, so `instanceof` doesn't work across package
|
|
28
|
+
* boundaries). Falls back to duck-typing for non-APICallError cases.
|
|
29
|
+
*/
|
|
30
|
+
export function isRetryableProviderError(error) {
|
|
31
|
+
// Preferred path: use the AI SDK's own branded type check + isRetryable flag
|
|
32
|
+
if (APICallError.isInstance(error)) {
|
|
33
|
+
return error.isRetryable;
|
|
34
|
+
}
|
|
35
|
+
// Fallback: duck-type for status codes on errors that aren't APICallError
|
|
36
|
+
if (error && typeof error === "object" && "statusCode" in error) {
|
|
37
|
+
const statusCode = error.statusCode;
|
|
38
|
+
return statusCode === 429 || statusCode >= 500;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extract the HTTP status code from an AI SDK error, if available.
|
|
44
|
+
*/
|
|
45
|
+
export function getErrorStatusCode(error) {
|
|
46
|
+
if (APICallError.isInstance(error)) {
|
|
47
|
+
return error.statusCode;
|
|
48
|
+
}
|
|
49
|
+
if (error && typeof error === "object" && "statusCode" in error) {
|
|
50
|
+
return error.statusCode;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Execute a provider call with instrumented retry logic.
|
|
56
|
+
*
|
|
57
|
+
* @param operation - The async operation to execute (should already use `maxRetries: 0`)
|
|
58
|
+
* @param span - The OTel span to annotate with retry events and attributes
|
|
59
|
+
* @param label - A human-readable label for log messages (e.g. "generateText", "streamText")
|
|
60
|
+
* @returns The result of the operation
|
|
61
|
+
*/
|
|
62
|
+
export async function withProviderRetry(operation, span, label) {
|
|
63
|
+
for (let attempt = 0; attempt <= MAX_PROVIDER_RETRIES; attempt++) {
|
|
64
|
+
try {
|
|
65
|
+
const result = await operation();
|
|
66
|
+
// Record how many attempts it took on the span
|
|
67
|
+
span.setAttribute("gen_ai.provider.total_attempts", attempt + 1);
|
|
68
|
+
if (attempt > 0) {
|
|
69
|
+
logger.info(`[providerRetry] ${label} succeeded after ${attempt + 1} attempts`);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const retryable = isRetryableProviderError(error);
|
|
75
|
+
const statusCode = getErrorStatusCode(error);
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
77
|
+
if (!retryable || attempt === MAX_PROVIDER_RETRIES) {
|
|
78
|
+
// Record failure details before re-throwing
|
|
79
|
+
span.setAttribute("gen_ai.provider.total_attempts", attempt + 1);
|
|
80
|
+
if (attempt > 0) {
|
|
81
|
+
span.setAttribute("gen_ai.provider.retries_exhausted", true);
|
|
82
|
+
}
|
|
83
|
+
logger.warn(`[providerRetry] ${label} failed (non-retryable or retries exhausted)`, {
|
|
84
|
+
attempt: attempt + 1,
|
|
85
|
+
retryable,
|
|
86
|
+
statusCode,
|
|
87
|
+
error: errorMessage,
|
|
88
|
+
});
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
// Calculate exponential backoff delay
|
|
92
|
+
const delay = BASE_RETRY_DELAY_MS * Math.pow(2, attempt);
|
|
93
|
+
// Record retry event on the OTel span
|
|
94
|
+
span.addEvent("gen_ai.provider.retry", {
|
|
95
|
+
"retry.attempt": attempt + 1,
|
|
96
|
+
"retry.delay_ms": delay,
|
|
97
|
+
...(statusCode !== undefined && { "retry.status_code": statusCode }),
|
|
98
|
+
"retry.error": errorMessage.slice(0, 256),
|
|
99
|
+
});
|
|
100
|
+
logger.warn(`[providerRetry] ${label} retrying after ${statusCode || "unknown"} error`, {
|
|
101
|
+
attempt: attempt + 1,
|
|
102
|
+
maxRetries: MAX_PROVIDER_RETRIES,
|
|
103
|
+
delayMs: delay,
|
|
104
|
+
statusCode,
|
|
105
|
+
error: errorMessage,
|
|
106
|
+
});
|
|
107
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// This should never be reached due to the throw inside the loop,
|
|
111
|
+
// but TypeScript requires it for exhaustiveness.
|
|
112
|
+
throw new Error(`[providerRetry] ${label} exhausted all retries`);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=providerRetry.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP retryability constants.
|
|
3
|
+
*
|
|
4
|
+
* Centralises the status-code lists that were duplicated across
|
|
5
|
+
* httpRetryHandler, neurolink.ts, fileDetector.ts, and errorHelpers.
|
|
6
|
+
*/
|
|
7
|
+
/** Server-side and rate-limiting codes worth retrying. */
|
|
8
|
+
export declare const RETRYABLE_HTTP_STATUS_CODES: readonly number[];
|
|
9
|
+
/** Client-error codes where retrying is pointless. */
|
|
10
|
+
export declare const NON_RETRYABLE_HTTP_STATUS_CODES: readonly number[];
|
|
11
|
+
/** Check whether an HTTP status code is retryable. */
|
|
12
|
+
export declare function isRetryableStatusCode(code: number): boolean;
|
|
13
|
+
/** Check whether an HTTP status code is non-retryable. */
|
|
14
|
+
export declare function isNonRetryableStatusCode(code: number): boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP retryability constants.
|
|
3
|
+
*
|
|
4
|
+
* Centralises the status-code lists that were duplicated across
|
|
5
|
+
* httpRetryHandler, neurolink.ts, fileDetector.ts, and errorHelpers.
|
|
6
|
+
*/
|
|
7
|
+
/** Server-side and rate-limiting codes worth retrying. */
|
|
8
|
+
export const RETRYABLE_HTTP_STATUS_CODES = [
|
|
9
|
+
408, 429, 500, 502, 503, 504,
|
|
10
|
+
];
|
|
11
|
+
/** Client-error codes where retrying is pointless. */
|
|
12
|
+
export const NON_RETRYABLE_HTTP_STATUS_CODES = [
|
|
13
|
+
400, 401, 403, 404, 405, 409, 422,
|
|
14
|
+
];
|
|
15
|
+
/** Check whether an HTTP status code is retryable. */
|
|
16
|
+
export function isRetryableStatusCode(code) {
|
|
17
|
+
return RETRYABLE_HTTP_STATUS_CODES.includes(code);
|
|
18
|
+
}
|
|
19
|
+
/** Check whether an HTTP status code is non-retryable. */
|
|
20
|
+
export function isNonRetryableStatusCode(code) {
|
|
21
|
+
return NON_RETRYABLE_HTTP_STATUS_CODES.includes(code);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=retryability.js.map
|
|
@@ -339,8 +339,7 @@ function removeDangerousAttributes(content, removedItems) {
|
|
|
339
339
|
// Parse attributes
|
|
340
340
|
const attrRegex = /([a-zA-Z][a-zA-Z0-9:_-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
341
341
|
const safeAttrs = [];
|
|
342
|
-
let attrMatch = attrRegex.exec(attrs);
|
|
343
|
-
while (attrMatch !== null) {
|
|
342
|
+
for (let attrMatch = attrRegex.exec(attrs); attrMatch !== null; attrMatch = attrRegex.exec(attrs)) {
|
|
344
343
|
const attrName = attrMatch[1];
|
|
345
344
|
const attrValue = attrMatch[2] ?? attrMatch[3] ?? "";
|
|
346
345
|
const lowerAttrName = attrName.toLowerCase();
|
|
@@ -390,13 +389,13 @@ function removeDangerousAttributes(content, removedItems) {
|
|
|
390
389
|
}
|
|
391
390
|
// Attribute is safe, keep it
|
|
392
391
|
safeAttrs.push(`${attrName}="${escapeAttributeValue(attrValue)}"`);
|
|
393
|
-
// Get next match
|
|
394
|
-
attrMatch = attrRegex.exec(attrs);
|
|
395
392
|
}
|
|
396
393
|
// Also keep standalone attributes (like xmlns without value in some cases)
|
|
397
394
|
const standaloneAttrRegex = /\s([a-zA-Z][a-zA-Z0-9:_-]*)(?=\s|>|$|\/)/g;
|
|
398
395
|
let standaloneMatch = standaloneAttrRegex.exec(attrs);
|
|
399
|
-
|
|
396
|
+
let iterations = 0;
|
|
397
|
+
const MAX_ITERATIONS = 1000;
|
|
398
|
+
while (standaloneMatch !== null && iterations++ < MAX_ITERATIONS) {
|
|
400
399
|
const attrName = standaloneMatch[1];
|
|
401
400
|
// Only keep if it looks like a valid attribute and is safe
|
|
402
401
|
if (SAFE_SVG_ATTRIBUTES.has(attrName) ||
|
|
@@ -16,7 +16,17 @@ import type { ChatMessage } from "../types/conversation.js";
|
|
|
16
16
|
export declare const CHARS_PER_TOKEN = 4;
|
|
17
17
|
/** Characters per token for code */
|
|
18
18
|
export declare const CODE_CHARS_PER_TOKEN = 3;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Safety margin: additive fraction of baseTokens added to the provider-adjusted estimate.
|
|
21
|
+
* Using additive margin prevents compounding with provider multipliers.
|
|
22
|
+
*
|
|
23
|
+
* Old behavior: baseTokens * providerMultiplier * 1.15 (compounding)
|
|
24
|
+
* e.g. Anthropic: baseTokens * 1.23 * 1.15 = baseTokens * 1.4145
|
|
25
|
+
* New behavior: baseTokens * providerMultiplier + baseTokens * 0.05 (additive)
|
|
26
|
+
* e.g. Anthropic: baseTokens * 1.23 + baseTokens * 0.05 = baseTokens * 1.28
|
|
27
|
+
*/
|
|
28
|
+
export declare const TOKEN_SAFETY_MARGIN_ADDITIVE = 0.05;
|
|
29
|
+
/** @deprecated Use TOKEN_SAFETY_MARGIN_ADDITIVE instead. Kept for backward compatibility. */
|
|
20
30
|
export declare const TOKEN_SAFETY_MARGIN = 1.15;
|
|
21
31
|
/** Message framing overhead in tokens (role + delimiters) */
|
|
22
32
|
export declare const TOKENS_PER_MESSAGE = 4;
|
|
@@ -15,7 +15,17 @@
|
|
|
15
15
|
export const CHARS_PER_TOKEN = 4;
|
|
16
16
|
/** Characters per token for code */
|
|
17
17
|
export const CODE_CHARS_PER_TOKEN = 3;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Safety margin: additive fraction of baseTokens added to the provider-adjusted estimate.
|
|
20
|
+
* Using additive margin prevents compounding with provider multipliers.
|
|
21
|
+
*
|
|
22
|
+
* Old behavior: baseTokens * providerMultiplier * 1.15 (compounding)
|
|
23
|
+
* e.g. Anthropic: baseTokens * 1.23 * 1.15 = baseTokens * 1.4145
|
|
24
|
+
* New behavior: baseTokens * providerMultiplier + baseTokens * 0.05 (additive)
|
|
25
|
+
* e.g. Anthropic: baseTokens * 1.23 + baseTokens * 0.05 = baseTokens * 1.28
|
|
26
|
+
*/
|
|
27
|
+
export const TOKEN_SAFETY_MARGIN_ADDITIVE = 0.05;
|
|
28
|
+
/** @deprecated Use TOKEN_SAFETY_MARGIN_ADDITIVE instead. Kept for backward compatibility. */
|
|
19
29
|
export const TOKEN_SAFETY_MARGIN = 1.15;
|
|
20
30
|
/** Message framing overhead in tokens (role + delimiters) */
|
|
21
31
|
export const TOKENS_PER_MESSAGE = 4;
|
|
@@ -64,7 +74,11 @@ export function estimateTokens(text, provider, isCode) {
|
|
|
64
74
|
const charsPerToken = isCode ? CODE_CHARS_PER_TOKEN : CHARS_PER_TOKEN;
|
|
65
75
|
const baseTokens = Math.ceil(text.length / charsPerToken);
|
|
66
76
|
const multiplier = getProviderMultiplier(provider);
|
|
67
|
-
|
|
77
|
+
// Apply provider multiplier and additive safety margin separately
|
|
78
|
+
// This prevents compounding (e.g. Anthropic: 1.23 * 1.15 = 1.41x was too aggressive)
|
|
79
|
+
const providerAdjusted = baseTokens * multiplier;
|
|
80
|
+
const safetyBuffer = baseTokens * TOKEN_SAFETY_MARGIN_ADDITIVE;
|
|
81
|
+
return Math.ceil(providerAdjusted + safetyBuffer);
|
|
68
82
|
}
|
|
69
83
|
/**
|
|
70
84
|
* Estimate token count for a single ChatMessage.
|
|
@@ -114,8 +128,9 @@ export function truncateToTokenBudget(text, maxTokens, provider) {
|
|
|
114
128
|
return { text, truncated: false };
|
|
115
129
|
}
|
|
116
130
|
const multiplier = getProviderMultiplier(provider);
|
|
117
|
-
|
|
118
|
-
const
|
|
131
|
+
// Use additive safety margin: effective multiplier = multiplier + additive margin
|
|
132
|
+
const effectiveMultiplier = multiplier + TOKEN_SAFETY_MARGIN_ADDITIVE;
|
|
133
|
+
const maxChars = Math.floor((maxTokens / effectiveMultiplier) * CHARS_PER_TOKEN);
|
|
119
134
|
if (maxChars <= 0) {
|
|
120
135
|
return { text: "", truncated: true };
|
|
121
136
|
}
|
|
@@ -21,10 +21,14 @@ export function hasVideoFrames(messages) {
|
|
|
21
21
|
return false;
|
|
22
22
|
}
|
|
23
23
|
if (Array.isArray(msg.content)) {
|
|
24
|
-
|
|
24
|
+
// Count image parts — only route to video analysis pipeline when there are
|
|
25
|
+
// multiple frames (3+), indicating actual video frame extraction.
|
|
26
|
+
// Single images or pairs should use the model's native vision capability.
|
|
27
|
+
const imageCount = msg.content.filter((part) => typeof part === "object" &&
|
|
25
28
|
part !== null &&
|
|
26
29
|
"type" in part &&
|
|
27
|
-
part.type === "image");
|
|
30
|
+
part.type === "image").length;
|
|
31
|
+
return imageCount >= 3;
|
|
28
32
|
}
|
|
29
33
|
return false;
|
|
30
34
|
});
|
|
@@ -53,7 +57,7 @@ export async function executeVideoAnalysis(messages, options) {
|
|
|
53
57
|
? undefined
|
|
54
58
|
: process.env.GOOGLE_VERTEX_PROJECT || process.env.GOOGLE_CLOUD_PROJECT,
|
|
55
59
|
location: options.region || process.env.GOOGLE_VERTEX_LOCATION,
|
|
56
|
-
model: options.model || "gemini-2.
|
|
60
|
+
model: options.model || "gemini-2.5-flash",
|
|
57
61
|
});
|
|
58
62
|
logger.debug("[VideoAnalysisProcessor] Video analysis completed", {
|
|
59
63
|
hasResult: !!videoAnalysisText,
|
|
@@ -14,6 +14,9 @@ import { toolRegistry } from "./toolRegistry.js";
|
|
|
14
14
|
import { HITLUserRejectedError, HITLTimeoutError } from "../hitl/hitlErrors.js";
|
|
15
15
|
import { detectCategory } from "../utils/mcpDefaults.js";
|
|
16
16
|
import { isObject, isNonNullObject } from "../utils/typeUtils.js";
|
|
17
|
+
import { TelemetryService } from "../telemetry/telemetryService.js";
|
|
18
|
+
import { tracers } from "../telemetry/tracers.js";
|
|
19
|
+
import { SpanStatusCode } from "@opentelemetry/api";
|
|
17
20
|
/**
|
|
18
21
|
* Recursively substitute environment variables in strings
|
|
19
22
|
* Replaces ${VAR_NAME} with the value from process.env.VAR_NAME
|
|
@@ -745,6 +748,16 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
745
748
|
throw new Error(`Server '${serverId}' not found`);
|
|
746
749
|
}
|
|
747
750
|
const config = instance.config;
|
|
751
|
+
const span = tracers.mcp.startSpan("neurolink.mcp.server.start", {
|
|
752
|
+
attributes: {
|
|
753
|
+
"mcp.server_id": serverId,
|
|
754
|
+
"mcp.transport": config.transport,
|
|
755
|
+
"mcp.command_name": config.command
|
|
756
|
+
? config.command.split(/[\\/]/).pop() || ""
|
|
757
|
+
: "",
|
|
758
|
+
"mcp.command_present": Boolean(config.command),
|
|
759
|
+
},
|
|
760
|
+
});
|
|
748
761
|
try {
|
|
749
762
|
this.updateServerStatus(serverId, "connecting");
|
|
750
763
|
mcpLogger.debug(`[ExternalServerManager] Starting server: ${serverId}`, {
|
|
@@ -803,6 +816,8 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
803
816
|
toolCount: instance.toolsMap.size,
|
|
804
817
|
timestamp: new Date(),
|
|
805
818
|
});
|
|
819
|
+
span.setAttribute("mcp.tool_count", instance.toolsMap.size);
|
|
820
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
806
821
|
mcpLogger.info(`[ExternalServerManager] Server started successfully: ${serverId}`);
|
|
807
822
|
}
|
|
808
823
|
catch (error) {
|
|
@@ -810,8 +825,16 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
810
825
|
this.updateServerStatus(serverId, "failed");
|
|
811
826
|
instance.lastError =
|
|
812
827
|
error instanceof Error ? error.message : String(error);
|
|
828
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
829
|
+
span.setStatus({
|
|
830
|
+
code: SpanStatusCode.ERROR,
|
|
831
|
+
message: error instanceof Error ? error.message : String(error),
|
|
832
|
+
});
|
|
813
833
|
throw error;
|
|
814
834
|
}
|
|
835
|
+
finally {
|
|
836
|
+
span.end();
|
|
837
|
+
}
|
|
815
838
|
}
|
|
816
839
|
/**
|
|
817
840
|
* Stop an external MCP server
|
|
@@ -821,6 +844,11 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
821
844
|
if (!instance) {
|
|
822
845
|
return;
|
|
823
846
|
}
|
|
847
|
+
const span = tracers.mcp.startSpan("neurolink.mcp.server.stop", {
|
|
848
|
+
attributes: {
|
|
849
|
+
"mcp.server_id": serverId,
|
|
850
|
+
},
|
|
851
|
+
});
|
|
824
852
|
try {
|
|
825
853
|
this.updateServerStatus(serverId, "stopping");
|
|
826
854
|
// Clear timers
|
|
@@ -851,11 +879,20 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
851
879
|
instance.process = null;
|
|
852
880
|
}
|
|
853
881
|
this.updateServerStatus(serverId, "stopped");
|
|
882
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
854
883
|
mcpLogger.info(`[ExternalServerManager] Server stopped: ${serverId}`);
|
|
855
884
|
}
|
|
856
885
|
catch (error) {
|
|
857
886
|
mcpLogger.error(`[ExternalServerManager] Error stopping server ${serverId}:`, error);
|
|
858
887
|
this.updateServerStatus(serverId, "failed");
|
|
888
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
889
|
+
span.setStatus({
|
|
890
|
+
code: SpanStatusCode.ERROR,
|
|
891
|
+
message: error instanceof Error ? error.message : String(error),
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
finally {
|
|
895
|
+
span.end();
|
|
859
896
|
}
|
|
860
897
|
}
|
|
861
898
|
/**
|
|
@@ -959,16 +996,32 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
959
996
|
return;
|
|
960
997
|
} // already scheduled
|
|
961
998
|
instance.restartTimer = setTimeout(async () => {
|
|
999
|
+
const restartSpan = tracers.mcp.startSpan("neurolink.mcp.server.restart", {
|
|
1000
|
+
attributes: {
|
|
1001
|
+
"mcp.server_id": serverId,
|
|
1002
|
+
"mcp.restart_attempt": instance.reconnectAttempts,
|
|
1003
|
+
"mcp.restart_delay_ms": delay,
|
|
1004
|
+
},
|
|
1005
|
+
});
|
|
962
1006
|
try {
|
|
963
1007
|
await this.stopServer(serverId);
|
|
964
1008
|
await this.startServer(serverId);
|
|
965
1009
|
// Reset restart attempts on successful restart
|
|
966
1010
|
instance.reconnectAttempts = 0;
|
|
1011
|
+
restartSpan.setStatus({ code: SpanStatusCode.OK });
|
|
967
1012
|
}
|
|
968
1013
|
catch (error) {
|
|
969
1014
|
mcpLogger.error(`[ExternalServerManager] Restart failed for ${serverId}:`, error);
|
|
1015
|
+
restartSpan.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
1016
|
+
restartSpan.setStatus({
|
|
1017
|
+
code: SpanStatusCode.ERROR,
|
|
1018
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1019
|
+
});
|
|
970
1020
|
this.scheduleRestart(serverId); // Try again
|
|
971
1021
|
}
|
|
1022
|
+
finally {
|
|
1023
|
+
restartSpan.end();
|
|
1024
|
+
}
|
|
972
1025
|
}, delay);
|
|
973
1026
|
}
|
|
974
1027
|
/**
|
|
@@ -1376,6 +1429,12 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
1376
1429
|
mcpLogger.debug(`[ExternalServerManager] Tool executed successfully: ${toolName} on ${serverId}`, {
|
|
1377
1430
|
duration,
|
|
1378
1431
|
});
|
|
1432
|
+
try {
|
|
1433
|
+
TelemetryService.getInstance()?.recordMCPToolCall(toolName, duration, true);
|
|
1434
|
+
}
|
|
1435
|
+
catch {
|
|
1436
|
+
/* telemetry should not break execution */
|
|
1437
|
+
}
|
|
1379
1438
|
return result.data;
|
|
1380
1439
|
}
|
|
1381
1440
|
else {
|
|
@@ -1384,6 +1443,13 @@ export class ExternalServerManager extends EventEmitter {
|
|
|
1384
1443
|
}
|
|
1385
1444
|
catch (error) {
|
|
1386
1445
|
instance.metrics.totalErrors++;
|
|
1446
|
+
try {
|
|
1447
|
+
const errorDuration = Date.now() - startTime;
|
|
1448
|
+
TelemetryService.getInstance()?.recordMCPToolCall(toolName, errorDuration, false);
|
|
1449
|
+
}
|
|
1450
|
+
catch {
|
|
1451
|
+
/* telemetry should not break execution */
|
|
1452
|
+
}
|
|
1387
1453
|
mcpLogger.error(`[ExternalServerManager] Tool execution failed: ${toolName} on ${serverId}`, error);
|
|
1388
1454
|
throw error;
|
|
1389
1455
|
}
|
|
@@ -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
|