@juspay/neurolink 9.40.0 → 9.42.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 +7 -1
- package/dist/auth/anthropicOAuth.d.ts +18 -3
- package/dist/auth/anthropicOAuth.js +137 -4
- package/dist/auth/providers/firebase.js +5 -1
- package/dist/auth/providers/jwt.js +5 -1
- package/dist/auth/providers/workos.js +5 -1
- package/dist/auth/sessionManager.d.ts +1 -1
- package/dist/auth/sessionManager.js +58 -27
- package/dist/browser/neurolink.min.js +471 -445
- package/dist/cli/commands/mcp.js +3 -0
- package/dist/cli/commands/proxy.d.ts +2 -1
- package/dist/cli/commands/proxy.js +279 -16
- package/dist/cli/commands/task.d.ts +56 -0
- package/dist/cli/commands/task.js +838 -0
- package/dist/cli/factories/commandFactory.d.ts +2 -0
- package/dist/cli/factories/commandFactory.js +38 -0
- package/dist/cli/parser.js +8 -4
- package/dist/client/aiSdkAdapter.js +3 -0
- package/dist/client/streamingClient.js +30 -10
- package/dist/core/modules/GenerationHandler.js +3 -2
- package/dist/core/redisConversationMemoryManager.js +7 -3
- package/dist/evaluation/BatchEvaluator.js +4 -1
- package/dist/evaluation/hooks/observabilityHooks.js +5 -3
- package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
- package/dist/evaluation/pipeline/evaluationPipeline.js +20 -8
- package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
- package/dist/lib/auth/anthropicOAuth.js +137 -4
- package/dist/lib/auth/providers/firebase.js +5 -1
- package/dist/lib/auth/providers/jwt.js +5 -1
- package/dist/lib/auth/providers/workos.js +5 -1
- package/dist/lib/auth/sessionManager.d.ts +1 -1
- package/dist/lib/auth/sessionManager.js +58 -27
- package/dist/lib/client/aiSdkAdapter.js +3 -0
- package/dist/lib/client/streamingClient.js +30 -10
- package/dist/lib/core/modules/GenerationHandler.js +3 -2
- package/dist/lib/core/redisConversationMemoryManager.js +7 -3
- package/dist/lib/evaluation/BatchEvaluator.js +4 -1
- package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
- package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
- package/dist/lib/evaluation/pipeline/evaluationPipeline.js +20 -8
- package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/lib/neurolink.d.ts +18 -1
- package/dist/lib/neurolink.js +367 -484
- package/dist/lib/observability/otelBridge.d.ts +2 -2
- package/dist/lib/observability/otelBridge.js +12 -3
- package/dist/lib/providers/amazonBedrock.js +2 -4
- package/dist/lib/providers/anthropic.d.ts +9 -5
- package/dist/lib/providers/anthropic.js +19 -14
- package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
- package/dist/lib/providers/anthropicBaseProvider.js +5 -4
- package/dist/lib/providers/azureOpenai.d.ts +1 -1
- package/dist/lib/providers/azureOpenai.js +5 -4
- package/dist/lib/providers/googleAiStudio.js +30 -1
- package/dist/lib/providers/googleVertex.js +28 -6
- package/dist/lib/providers/huggingFace.d.ts +3 -3
- package/dist/lib/providers/huggingFace.js +6 -8
- package/dist/lib/providers/litellm.js +41 -29
- package/dist/lib/providers/mistral.js +2 -1
- package/dist/lib/providers/ollama.js +80 -23
- package/dist/lib/providers/openAI.js +3 -2
- package/dist/lib/providers/openRouter.js +2 -1
- package/dist/lib/providers/openaiCompatible.d.ts +4 -4
- package/dist/lib/providers/openaiCompatible.js +4 -4
- package/dist/lib/proxy/claudeFormat.d.ts +3 -2
- package/dist/lib/proxy/claudeFormat.js +25 -20
- package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
- package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
- package/dist/lib/proxy/modelRouter.js +3 -0
- package/dist/lib/proxy/oauthFetch.d.ts +1 -1
- package/dist/lib/proxy/oauthFetch.js +65 -72
- package/dist/lib/proxy/proxyConfig.js +44 -24
- package/dist/lib/proxy/proxyEnv.d.ts +19 -0
- package/dist/lib/proxy/proxyEnv.js +73 -0
- package/dist/lib/proxy/proxyFetch.js +50 -4
- package/dist/lib/proxy/proxyTracer.d.ts +133 -0
- package/dist/lib/proxy/proxyTracer.js +645 -0
- package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
- package/dist/lib/proxy/rawStreamCapture.js +83 -0
- package/dist/lib/proxy/requestLogger.d.ts +32 -5
- package/dist/lib/proxy/requestLogger.js +406 -37
- package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
- package/dist/lib/proxy/sseInterceptor.js +402 -0
- package/dist/lib/proxy/usageStats.d.ts +4 -3
- package/dist/lib/proxy/usageStats.js +25 -12
- package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
- package/dist/lib/rag/chunking/markdownChunker.js +15 -6
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +7 -2
- package/dist/lib/server/routes/claudeProxyRoutes.js +1737 -508
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/lib/services/server/ai/observability/instrumentation.js +240 -40
- package/dist/lib/tasks/backends/bullmqBackend.d.ts +33 -0
- package/dist/lib/tasks/backends/bullmqBackend.js +196 -0
- package/dist/lib/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
- package/dist/lib/tasks/backends/nodeTimeoutBackend.js +141 -0
- package/dist/lib/tasks/backends/taskBackendRegistry.d.ts +31 -0
- package/dist/lib/tasks/backends/taskBackendRegistry.js +66 -0
- package/dist/lib/tasks/errors.d.ts +31 -0
- package/dist/lib/tasks/errors.js +18 -0
- package/dist/lib/tasks/store/fileTaskStore.d.ts +43 -0
- package/dist/lib/tasks/store/fileTaskStore.js +179 -0
- package/dist/lib/tasks/store/redisTaskStore.d.ts +43 -0
- package/dist/lib/tasks/store/redisTaskStore.js +197 -0
- package/dist/lib/tasks/taskExecutor.d.ts +21 -0
- package/dist/lib/tasks/taskExecutor.js +166 -0
- package/dist/lib/tasks/taskManager.d.ts +63 -0
- package/dist/lib/tasks/taskManager.js +426 -0
- package/dist/lib/tasks/tools/taskTools.d.ts +135 -0
- package/dist/lib/tasks/tools/taskTools.js +274 -0
- package/dist/lib/telemetry/index.d.ts +2 -1
- package/dist/lib/telemetry/index.js +2 -1
- package/dist/lib/telemetry/telemetryService.d.ts +3 -0
- package/dist/lib/telemetry/telemetryService.js +65 -5
- package/dist/lib/types/cli.d.ts +10 -0
- package/dist/lib/types/configTypes.d.ts +3 -0
- package/dist/lib/types/generateTypes.d.ts +13 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/proxyTypes.d.ts +37 -5
- package/dist/lib/types/streamTypes.d.ts +25 -3
- package/dist/lib/types/taskTypes.d.ts +275 -0
- package/dist/lib/types/taskTypes.js +37 -0
- package/dist/lib/utils/messageBuilder.js +3 -2
- package/dist/lib/utils/providerHealth.d.ts +18 -0
- package/dist/lib/utils/providerHealth.js +240 -9
- package/dist/lib/utils/providerUtils.js +14 -8
- package/dist/lib/utils/toolChoice.d.ts +4 -0
- package/dist/lib/utils/toolChoice.js +7 -0
- package/dist/neurolink.d.ts +18 -1
- package/dist/neurolink.js +367 -484
- package/dist/observability/otelBridge.d.ts +2 -2
- package/dist/observability/otelBridge.js +12 -3
- package/dist/providers/amazonBedrock.js +2 -4
- package/dist/providers/anthropic.d.ts +9 -5
- package/dist/providers/anthropic.js +19 -14
- package/dist/providers/anthropicBaseProvider.d.ts +3 -3
- package/dist/providers/anthropicBaseProvider.js +5 -4
- package/dist/providers/azureOpenai.d.ts +1 -1
- package/dist/providers/azureOpenai.js +5 -4
- package/dist/providers/googleAiStudio.js +30 -1
- package/dist/providers/googleVertex.js +28 -6
- package/dist/providers/huggingFace.d.ts +3 -3
- package/dist/providers/huggingFace.js +6 -7
- package/dist/providers/litellm.js +41 -29
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/ollama.js +80 -23
- package/dist/providers/openAI.js +3 -2
- package/dist/providers/openRouter.js +2 -1
- package/dist/providers/openaiCompatible.d.ts +4 -4
- package/dist/providers/openaiCompatible.js +4 -3
- package/dist/proxy/claudeFormat.d.ts +3 -2
- package/dist/proxy/claudeFormat.js +25 -20
- package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
- package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
- package/dist/proxy/modelRouter.js +3 -0
- package/dist/proxy/oauthFetch.d.ts +1 -1
- package/dist/proxy/oauthFetch.js +65 -72
- package/dist/proxy/proxyConfig.js +44 -24
- package/dist/proxy/proxyEnv.d.ts +19 -0
- package/dist/proxy/proxyEnv.js +72 -0
- package/dist/proxy/proxyFetch.js +50 -4
- package/dist/proxy/proxyTracer.d.ts +133 -0
- package/dist/proxy/proxyTracer.js +644 -0
- package/dist/proxy/rawStreamCapture.d.ts +10 -0
- package/dist/proxy/rawStreamCapture.js +82 -0
- package/dist/proxy/requestLogger.d.ts +32 -5
- package/dist/proxy/requestLogger.js +406 -37
- package/dist/proxy/sseInterceptor.d.ts +97 -0
- package/dist/proxy/sseInterceptor.js +401 -0
- package/dist/proxy/usageStats.d.ts +4 -3
- package/dist/proxy/usageStats.js +25 -12
- package/dist/rag/chunkers/MarkdownChunker.js +13 -5
- package/dist/rag/chunking/markdownChunker.js +15 -6
- package/dist/server/routes/claudeProxyRoutes.d.ts +7 -2
- package/dist/server/routes/claudeProxyRoutes.js +1737 -508
- package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/services/server/ai/observability/instrumentation.js +240 -40
- package/dist/tasks/backends/bullmqBackend.d.ts +33 -0
- package/dist/tasks/backends/bullmqBackend.js +195 -0
- package/dist/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
- package/dist/tasks/backends/nodeTimeoutBackend.js +140 -0
- package/dist/tasks/backends/taskBackendRegistry.d.ts +31 -0
- package/dist/tasks/backends/taskBackendRegistry.js +65 -0
- package/dist/tasks/errors.d.ts +31 -0
- package/dist/tasks/errors.js +17 -0
- package/dist/tasks/store/fileTaskStore.d.ts +43 -0
- package/dist/tasks/store/fileTaskStore.js +178 -0
- package/dist/tasks/store/redisTaskStore.d.ts +43 -0
- package/dist/tasks/store/redisTaskStore.js +196 -0
- package/dist/tasks/taskExecutor.d.ts +21 -0
- package/dist/tasks/taskExecutor.js +165 -0
- package/dist/tasks/taskManager.d.ts +63 -0
- package/dist/tasks/taskManager.js +425 -0
- package/dist/tasks/tools/taskTools.d.ts +135 -0
- package/dist/tasks/tools/taskTools.js +273 -0
- package/dist/telemetry/index.d.ts +2 -1
- package/dist/telemetry/index.js +2 -1
- package/dist/telemetry/telemetryService.d.ts +3 -0
- package/dist/telemetry/telemetryService.js +65 -5
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/configTypes.d.ts +3 -0
- package/dist/types/generateTypes.d.ts +13 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/proxyTypes.d.ts +37 -5
- package/dist/types/streamTypes.d.ts +25 -3
- package/dist/types/taskTypes.d.ts +275 -0
- package/dist/types/taskTypes.js +36 -0
- package/dist/utils/messageBuilder.js +3 -2
- package/dist/utils/providerHealth.d.ts +18 -0
- package/dist/utils/providerHealth.js +240 -9
- package/dist/utils/providerUtils.js +14 -8
- package/dist/utils/toolChoice.d.ts +4 -0
- package/dist/utils/toolChoice.js +6 -0
- package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
- package/docs/changelog.md +252 -0
- package/package.json +19 -1
- package/scripts/observability/check-proxy-telemetry.mjs +235 -0
- package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
- package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
- package/scripts/observability/manage-local-openobserve.sh +184 -0
- package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
- package/scripts/observability/proxy-observability.env.example +23 -0
package/dist/neurolink.js
CHANGED
|
@@ -22,7 +22,7 @@ import pLimit from "p-limit";
|
|
|
22
22
|
import { ErrorCategory, ErrorSeverity } from "./constants/enums.js";
|
|
23
23
|
import { CIRCUIT_BREAKER, CIRCUIT_BREAKER_RESET_MS, MEMORY_THRESHOLDS, NANOSECOND_TO_MS_DIVISOR, PERFORMANCE_THRESHOLDS, PROVIDER_TIMEOUTS, RETRY_ATTEMPTS, RETRY_DELAYS, TOOL_TIMEOUTS, } from "./constants/index.js";
|
|
24
24
|
import { checkContextBudget } from "./context/budgetChecker.js";
|
|
25
|
-
import { ContextCompactor
|
|
25
|
+
import { ContextCompactor } from "./context/contextCompactor.js";
|
|
26
26
|
import { emergencyContentTruncation } from "./context/emergencyTruncation.js";
|
|
27
27
|
import { getContextOverflowProvider, isContextOverflowError, parseProviderOverflowDetails, } from "./context/errorDetection.js";
|
|
28
28
|
import { ContextBudgetExceededError } from "./context/errors.js";
|
|
@@ -44,24 +44,27 @@ import { ToolRouter } from "./mcp/routing/index.js";
|
|
|
44
44
|
import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
|
|
45
45
|
import { inferAnnotations, isSafeToRetry } from "./mcp/toolAnnotations.js";
|
|
46
46
|
import { MCPToolRegistry } from "./mcp/toolRegistry.js";
|
|
47
|
-
import { initializeHippocampus
|
|
47
|
+
import { initializeHippocampus } from "./memory/hippocampusInitializer.js";
|
|
48
48
|
import { createMemoryRetrievalTools } from "./memory/memoryRetrievalTools.js";
|
|
49
|
-
import { getMetricsAggregator, MetricsAggregator
|
|
49
|
+
import { getMetricsAggregator, MetricsAggregator } from "./observability/metricsAggregator.js";
|
|
50
50
|
import { SpanStatus, SpanType } from "./observability/types/spanTypes.js";
|
|
51
51
|
import { SpanSerializer } from "./observability/utils/spanSerializer.js";
|
|
52
52
|
import { flushOpenTelemetry, getLangfuseHealthStatus, initializeOpenTelemetry, isOpenTelemetryInitialized, setLangfuseContext, shutdownOpenTelemetry, } from "./services/server/ai/observability/instrumentation.js";
|
|
53
|
+
import { TaskManager } from "./tasks/taskManager.js";
|
|
54
|
+
import { createTaskTools } from "./tasks/tools/taskTools.js";
|
|
53
55
|
import { ATTR } from "./telemetry/attributes.js";
|
|
54
56
|
import { tracers } from "./telemetry/tracers.js";
|
|
57
|
+
import { CircuitBreakerOpenError } from "./types/circuitBreakerErrors.js";
|
|
55
58
|
import { ConversationMemoryError } from "./types/conversation.js";
|
|
56
|
-
import { AuthenticationError, AuthorizationError, InvalidModelError
|
|
57
|
-
import { getConversationMessages, storeConversationTurn
|
|
59
|
+
import { AuthenticationError, AuthorizationError, InvalidModelError } from "./types/errors.js";
|
|
60
|
+
import { getConversationMessages, storeConversationTurn } from "./utils/conversationMemory.js";
|
|
58
61
|
// Enhanced error handling imports
|
|
59
62
|
import { CircuitBreaker, ERROR_CODES, ErrorFactory, isAbortError, isRetriableError, logStructuredError, NeuroLinkError, withRetry, withTimeout, } from "./utils/errorHandling.js";
|
|
60
|
-
import { CircuitBreakerOpenError } from "./types/circuitBreakerErrors.js";
|
|
61
63
|
// Factory processing imports
|
|
62
64
|
import { createCleanStreamOptions, enhanceTextGenerationOptions, processFactoryOptions, processStreamingFactoryOptions, validateFactoryConfig, } from "./utils/factoryProcessing.js";
|
|
63
65
|
import { logger, mcpLogger } from "./utils/logger.js";
|
|
64
|
-
import { createCustomToolServerInfo, detectCategory
|
|
66
|
+
import { createCustomToolServerInfo, detectCategory } from "./utils/mcpDefaults.js";
|
|
67
|
+
import { resolveModel } from "./utils/modelAliasResolver.js";
|
|
65
68
|
// Import orchestration components
|
|
66
69
|
import { ModelRouter } from "./utils/modelRouter.js";
|
|
67
70
|
import { getBestProvider } from "./utils/providerUtils.js";
|
|
@@ -72,7 +75,6 @@ import { BinaryTaskClassifier } from "./utils/taskClassifier.js";
|
|
|
72
75
|
// Transformation utilities
|
|
73
76
|
import { extractToolNames, optimizeToolForCollection, transformAvailableTools, transformParamsForLogging, transformToolExecutions, transformToolExecutionsForMCP, transformToolsForMCP, transformToolsToDescriptions, transformToolsToExpectedFormat, } from "./utils/transformationUtils.js";
|
|
74
77
|
import { isNonNullObject } from "./utils/typeUtils.js";
|
|
75
|
-
import { resolveModel } from "./utils/modelAliasResolver.js";
|
|
76
78
|
import { getWorkflow } from "./workflow/core/workflowRegistry.js";
|
|
77
79
|
import { runWorkflow } from "./workflow/core/workflowRunner.js";
|
|
78
80
|
/**
|
|
@@ -95,9 +97,7 @@ function classifyMcpErrorMessage(text) {
|
|
|
95
97
|
lower.includes("access denied")) {
|
|
96
98
|
return "permission_denied";
|
|
97
99
|
}
|
|
98
|
-
if (lower.includes("timeout") ||
|
|
99
|
-
lower.includes("timed out") ||
|
|
100
|
-
lower.includes("deadline exceeded")) {
|
|
100
|
+
if (lower.includes("timeout") || lower.includes("timed out") || lower.includes("deadline exceeded")) {
|
|
101
101
|
return "timeout";
|
|
102
102
|
}
|
|
103
103
|
if (lower.includes("rate limit") ||
|
|
@@ -154,11 +154,7 @@ function isNonRetryableProviderError(error) {
|
|
|
154
154
|
// Check for HTTP status codes on error objects (e.g., from Vercel AI SDK)
|
|
155
155
|
if (error && typeof error === "object") {
|
|
156
156
|
const err = error;
|
|
157
|
-
const status = typeof err.status === "number"
|
|
158
|
-
? err.status
|
|
159
|
-
: typeof err.statusCode === "number"
|
|
160
|
-
? err.statusCode
|
|
161
|
-
: undefined;
|
|
157
|
+
const status = typeof err.status === "number" ? err.status : typeof err.statusCode === "number" ? err.statusCode : undefined;
|
|
162
158
|
if (status && NON_RETRYABLE_HTTP_STATUS_CODES.includes(status)) {
|
|
163
159
|
return true;
|
|
164
160
|
}
|
|
@@ -187,6 +183,9 @@ export class NeuroLink {
|
|
|
187
183
|
mcpSkipped = false;
|
|
188
184
|
mcpInitPromise = null;
|
|
189
185
|
emitter = new EventEmitter();
|
|
186
|
+
// TaskManager — lazy-initialized on first access via `this.tasks`
|
|
187
|
+
_taskManager;
|
|
188
|
+
_taskManagerConfig;
|
|
190
189
|
toolRegistry;
|
|
191
190
|
autoDiscoveredServerInfos = [];
|
|
192
191
|
// External MCP server management
|
|
@@ -201,8 +200,7 @@ export class NeuroLink {
|
|
|
201
200
|
lastCompactionMessageCount = new Map();
|
|
202
201
|
/** Extract sessionId from options context for compaction watermark keying */
|
|
203
202
|
getCompactionSessionId(options) {
|
|
204
|
-
return
|
|
205
|
-
?.sessionId || "__default__");
|
|
203
|
+
return options.context?.sessionId || "__default__";
|
|
206
204
|
}
|
|
207
205
|
// MCP Enhancement modules - wired into core execution path
|
|
208
206
|
mcpToolResultCache;
|
|
@@ -265,28 +263,19 @@ export class NeuroLink {
|
|
|
265
263
|
* Extract and set Langfuse context from options with proper async scoping
|
|
266
264
|
*/
|
|
267
265
|
async setLangfuseContextFromOptions(options, callback) {
|
|
268
|
-
if (options.context &&
|
|
269
|
-
typeof options.context === "object" &&
|
|
270
|
-
options.context !== null) {
|
|
266
|
+
if (options.context && typeof options.context === "object" && options.context !== null) {
|
|
271
267
|
let callbackExecuted = false;
|
|
272
268
|
try {
|
|
273
269
|
const ctx = options.context;
|
|
274
270
|
// Trigger context scoping if any meaningful Langfuse field is present
|
|
275
|
-
if (ctx.userId ||
|
|
276
|
-
ctx.sessionId ||
|
|
277
|
-
ctx.conversationId ||
|
|
278
|
-
ctx.requestId ||
|
|
279
|
-
ctx.traceName ||
|
|
280
|
-
ctx.metadata) {
|
|
271
|
+
if (ctx.userId || ctx.sessionId || ctx.conversationId || ctx.requestId || ctx.traceName || ctx.metadata) {
|
|
281
272
|
// Build customAttributes from top-level metadata string/number/boolean fields
|
|
282
273
|
let customAttributes;
|
|
283
274
|
if (ctx.metadata && typeof ctx.metadata === "object") {
|
|
284
275
|
const metaObj = ctx.metadata;
|
|
285
276
|
const attrs = {};
|
|
286
277
|
for (const [k, v] of Object.entries(metaObj)) {
|
|
287
|
-
if (typeof v === "string" ||
|
|
288
|
-
typeof v === "number" ||
|
|
289
|
-
typeof v === "boolean") {
|
|
278
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
290
279
|
attrs[k] = v;
|
|
291
280
|
}
|
|
292
281
|
}
|
|
@@ -298,14 +287,10 @@ export class NeuroLink {
|
|
|
298
287
|
setLangfuseContext({
|
|
299
288
|
userId: typeof ctx.userId === "string" ? ctx.userId : null,
|
|
300
289
|
sessionId: typeof ctx.sessionId === "string" ? ctx.sessionId : null,
|
|
301
|
-
conversationId: typeof ctx.conversationId === "string"
|
|
302
|
-
? ctx.conversationId
|
|
303
|
-
: null,
|
|
290
|
+
conversationId: typeof ctx.conversationId === "string" ? ctx.conversationId : null,
|
|
304
291
|
requestId: typeof ctx.requestId === "string" ? ctx.requestId : null,
|
|
305
292
|
traceName: typeof ctx.traceName === "string" ? ctx.traceName : null,
|
|
306
|
-
metadata: ctx.metadata && typeof ctx.metadata === "object"
|
|
307
|
-
? ctx.metadata
|
|
308
|
-
: null,
|
|
293
|
+
metadata: ctx.metadata && typeof ctx.metadata === "object" ? ctx.metadata : null,
|
|
309
294
|
...(customAttributes !== undefined && { customAttributes }),
|
|
310
295
|
}, async () => {
|
|
311
296
|
try {
|
|
@@ -439,9 +424,7 @@ export class NeuroLink {
|
|
|
439
424
|
logger.setEventEmitter(this.emitter);
|
|
440
425
|
// Read tool cache duration from environment variables, with a default
|
|
441
426
|
const cacheDurationEnv = process.env.NEUROLINK_TOOL_CACHE_DURATION;
|
|
442
|
-
this.toolCacheDuration = cacheDurationEnv
|
|
443
|
-
? parseInt(cacheDurationEnv, 10)
|
|
444
|
-
: 20000;
|
|
427
|
+
this.toolCacheDuration = cacheDurationEnv ? parseInt(cacheDurationEnv, 10) : 20000;
|
|
445
428
|
const constructorStartTime = Date.now();
|
|
446
429
|
const constructorHrTimeStart = process.hrtime.bigint();
|
|
447
430
|
const constructorId = `neurolink-constructor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -459,6 +442,28 @@ export class NeuroLink {
|
|
|
459
442
|
if (config?.auth) {
|
|
460
443
|
this.pendingAuthConfig = config.auth;
|
|
461
444
|
}
|
|
445
|
+
// Store task config for lazy initialization
|
|
446
|
+
this._taskManagerConfig = config?.tasks;
|
|
447
|
+
// Eagerly create TaskManager and register tools if config is provided
|
|
448
|
+
if (this._taskManagerConfig) {
|
|
449
|
+
this._taskManager = new TaskManager(this, this._taskManagerConfig);
|
|
450
|
+
this._taskManager.setEmitter(this.emitter);
|
|
451
|
+
this.registerTaskTools(this._taskManager);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* TaskManager — scheduled and self-running tasks.
|
|
456
|
+
* Lazy-initialized on first access. Configurable via constructor `tasks` option.
|
|
457
|
+
* The actual async initialization (Redis connect, backend start) happens
|
|
458
|
+
* lazily inside TaskManager on first operation.
|
|
459
|
+
*/
|
|
460
|
+
get tasks() {
|
|
461
|
+
if (!this._taskManager) {
|
|
462
|
+
this._taskManager = new TaskManager(this, this._taskManagerConfig);
|
|
463
|
+
this._taskManager.setEmitter(this.emitter);
|
|
464
|
+
this.registerTaskTools(this._taskManager);
|
|
465
|
+
}
|
|
466
|
+
return this._taskManager;
|
|
462
467
|
}
|
|
463
468
|
/**
|
|
464
469
|
* Initialize provider registry with security settings
|
|
@@ -716,6 +721,52 @@ export class NeuroLink {
|
|
|
716
721
|
logger.debug(`[NeuroLink] Registered ${Object.keys(fileTools).length} file reference tools`);
|
|
717
722
|
});
|
|
718
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* Register task management tools bound to a TaskManager instance.
|
|
726
|
+
* Follows the same factory + registry pattern as registerFileTools().
|
|
727
|
+
* Called when TaskManager is created (eagerly or lazily via the `tasks` getter).
|
|
728
|
+
*/
|
|
729
|
+
registerTaskTools(manager) {
|
|
730
|
+
const taskTools = createTaskTools(manager);
|
|
731
|
+
for (const [toolName, toolDef] of Object.entries(taskTools)) {
|
|
732
|
+
const toolId = `direct.${toolName}`;
|
|
733
|
+
const toolInfo = {
|
|
734
|
+
name: toolName,
|
|
735
|
+
description: toolDef.description || `Task tool: ${toolName}`,
|
|
736
|
+
inputSchema: {},
|
|
737
|
+
serverId: "direct",
|
|
738
|
+
category: "built-in",
|
|
739
|
+
};
|
|
740
|
+
// registerTool is async but its core logic is synchronous (Map.set).
|
|
741
|
+
// We fire-and-forget here but tools are available immediately after
|
|
742
|
+
// the synchronous validation + map insertion completes.
|
|
743
|
+
void this.toolRegistry.registerTool(toolId, toolInfo, {
|
|
744
|
+
execute: async (params) => {
|
|
745
|
+
try {
|
|
746
|
+
const result = await toolDef.execute(params, {
|
|
747
|
+
toolCallId: "task-tool",
|
|
748
|
+
messages: [],
|
|
749
|
+
});
|
|
750
|
+
return {
|
|
751
|
+
success: true,
|
|
752
|
+
data: result,
|
|
753
|
+
metadata: { toolName, serverId: "direct", executionTime: 0 },
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
return {
|
|
758
|
+
success: false,
|
|
759
|
+
error: error instanceof Error ? error.message : String(error),
|
|
760
|
+
metadata: { toolName, serverId: "direct", executionTime: 0 },
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
description: toolDef.description,
|
|
765
|
+
inputSchema: {},
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
logger.debug(`[NeuroLink] Registered ${Object.keys(taskTools).length} task tools`);
|
|
769
|
+
}
|
|
719
770
|
/**
|
|
720
771
|
* Register memory retrieval tools that allow the AI to access
|
|
721
772
|
* conversation history, including full tool outputs.
|
|
@@ -728,9 +779,7 @@ export class NeuroLink {
|
|
|
728
779
|
// memory manager supports getSessionRaw.
|
|
729
780
|
const memConfig = this.conversationMemoryConfig?.conversationMemory;
|
|
730
781
|
const hasRedisConfig = !!memConfig?.redisConfig ||
|
|
731
|
-
(memConfig &&
|
|
732
|
-
"redis" in memConfig &&
|
|
733
|
-
!!memConfig.redis) ||
|
|
782
|
+
(memConfig && "redis" in memConfig && !!memConfig.redis) ||
|
|
734
783
|
process.env.STORAGE_TYPE === "redis";
|
|
735
784
|
if (!memConfig?.enabled || !hasRedisConfig) {
|
|
736
785
|
logger.debug("[NeuroLink] Skipping memory retrieval tools — requires Redis conversation memory");
|
|
@@ -761,13 +810,8 @@ export class NeuroLink {
|
|
|
761
810
|
messages: [],
|
|
762
811
|
});
|
|
763
812
|
// Check if the tool itself reported an error
|
|
764
|
-
const hasError = result &&
|
|
765
|
-
|
|
766
|
-
"error" in result &&
|
|
767
|
-
!("messages" in result);
|
|
768
|
-
const errorMsg = hasError
|
|
769
|
-
? result.error
|
|
770
|
-
: undefined;
|
|
813
|
+
const hasError = result && typeof result === "object" && "error" in result && !("messages" in result);
|
|
814
|
+
const errorMsg = hasError ? result.error : undefined;
|
|
771
815
|
return {
|
|
772
816
|
success: !hasError,
|
|
773
817
|
data: result,
|
|
@@ -844,8 +888,7 @@ Current user's request: ${currentInput}`;
|
|
|
844
888
|
* Respects both the global memory SDK config and per-call overrides.
|
|
845
889
|
*/
|
|
846
890
|
shouldReadMemory(perCallMemory, userId) {
|
|
847
|
-
if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled ||
|
|
848
|
-
!userId) {
|
|
891
|
+
if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled || !userId) {
|
|
849
892
|
return false;
|
|
850
893
|
}
|
|
851
894
|
if (perCallMemory?.enabled === false) {
|
|
@@ -861,8 +904,7 @@ Current user's request: ${currentInput}`;
|
|
|
861
904
|
* Respects both the global memory SDK config and per-call overrides.
|
|
862
905
|
*/
|
|
863
906
|
shouldWriteMemory(perCallMemory, userId, content) {
|
|
864
|
-
if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled ||
|
|
865
|
-
!userId) {
|
|
907
|
+
if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled || !userId) {
|
|
866
908
|
return false;
|
|
867
909
|
}
|
|
868
910
|
if (!content?.trim()) {
|
|
@@ -936,9 +978,7 @@ Current user's request: ${currentInput}`;
|
|
|
936
978
|
const writeOps = [client.add(userId, content)];
|
|
937
979
|
const writableAdditional = (additionalUsers || []).filter((u) => u.write !== false);
|
|
938
980
|
for (const user of writableAdditional) {
|
|
939
|
-
const addOptions = user.prompt || user.maxWords
|
|
940
|
-
? { prompt: user.prompt, maxWords: user.maxWords }
|
|
941
|
-
: undefined;
|
|
981
|
+
const addOptions = user.prompt || user.maxWords ? { prompt: user.prompt, maxWords: user.maxWords } : undefined;
|
|
942
982
|
writeOps.push(client.add(user.userId, content, addOptions));
|
|
943
983
|
}
|
|
944
984
|
await Promise.all(writeOps);
|
|
@@ -1097,8 +1137,7 @@ Current user's request: ${currentInput}`;
|
|
|
1097
1137
|
try {
|
|
1098
1138
|
const langfuseConfig = this.observabilityConfig?.langfuse;
|
|
1099
1139
|
// Check if we should use external provider mode - bypass enabled check
|
|
1100
|
-
const useExternalProvider = langfuseConfig?.autoDetectExternalProvider === true ||
|
|
1101
|
-
langfuseConfig?.useExternalTracerProvider === true;
|
|
1140
|
+
const useExternalProvider = langfuseConfig?.autoDetectExternalProvider === true || langfuseConfig?.useExternalTracerProvider === true;
|
|
1102
1141
|
if (langfuseConfig?.enabled || useExternalProvider) {
|
|
1103
1142
|
logger.debug(`[NeuroLink] 📊 LOG_POINT_C019_LANGFUSE_INIT_START`, {
|
|
1104
1143
|
logPoint: "C019_LANGFUSE_INIT_START",
|
|
@@ -1113,9 +1152,7 @@ Current user's request: ${currentInput}`;
|
|
|
1113
1152
|
initializeOpenTelemetry(langfuseConfig);
|
|
1114
1153
|
const healthStatus = getLangfuseHealthStatus();
|
|
1115
1154
|
const langfuseInitDurationNs = process.hrtime.bigint() - langfuseInitStartTime;
|
|
1116
|
-
if (healthStatus.initialized &&
|
|
1117
|
-
healthStatus.hasProcessor &&
|
|
1118
|
-
healthStatus.isHealthy) {
|
|
1155
|
+
if (healthStatus.initialized && healthStatus.hasProcessor && healthStatus.isHealthy) {
|
|
1119
1156
|
logger.debug(`[NeuroLink] ✅ LOG_POINT_C020_LANGFUSE_INIT_SUCCESS`, {
|
|
1120
1157
|
logPoint: "C020_LANGFUSE_INIT_SUCCESS",
|
|
1121
1158
|
constructorId,
|
|
@@ -1391,9 +1428,7 @@ Current user's request: ${currentInput}`;
|
|
|
1391
1428
|
}
|
|
1392
1429
|
catch (configError) {
|
|
1393
1430
|
mcpLogger.warn("[NeuroLink] MCP configuration loading failed", {
|
|
1394
|
-
error: configError instanceof Error
|
|
1395
|
-
? configError.message
|
|
1396
|
-
: String(configError),
|
|
1431
|
+
error: configError instanceof Error ? configError.message : String(configError),
|
|
1397
1432
|
});
|
|
1398
1433
|
}
|
|
1399
1434
|
}
|
|
@@ -1518,9 +1553,7 @@ Current user's request: ${currentInput}`;
|
|
|
1518
1553
|
taskType: classification.type,
|
|
1519
1554
|
routedProvider: route.provider,
|
|
1520
1555
|
routedModel: route.model,
|
|
1521
|
-
reason: error instanceof Error
|
|
1522
|
-
? error.message
|
|
1523
|
-
: "Ollama service check failed",
|
|
1556
|
+
reason: error instanceof Error ? error.message : "Ollama service check failed",
|
|
1524
1557
|
orchestrationTime: `${Date.now() - startTime}ms`,
|
|
1525
1558
|
});
|
|
1526
1559
|
return {}; // Return empty object to preserve existing fallback behavior
|
|
@@ -1656,9 +1689,7 @@ Current user's request: ${currentInput}`;
|
|
|
1656
1689
|
taskType: classification.type,
|
|
1657
1690
|
routedProvider: route.provider,
|
|
1658
1691
|
routedModel: route.model,
|
|
1659
|
-
reason: error instanceof Error
|
|
1660
|
-
? error.message
|
|
1661
|
-
: "Ollama service check failed",
|
|
1692
|
+
reason: error instanceof Error ? error.message : "Ollama service check failed",
|
|
1662
1693
|
orchestrationTime: `${Date.now() - startTime}ms`,
|
|
1663
1694
|
});
|
|
1664
1695
|
return {}; // Return empty object to preserve existing fallback behavior
|
|
@@ -1709,9 +1740,7 @@ Current user's request: ${currentInput}`;
|
|
|
1709
1740
|
const anyOptions = optionsOrPrompt;
|
|
1710
1741
|
if (anyOptions.messages && anyOptions.messages.length > 0) {
|
|
1711
1742
|
const lastMessage = anyOptions.messages[anyOptions.messages.length - 1];
|
|
1712
|
-
return typeof lastMessage.content === "string"
|
|
1713
|
-
? lastMessage.content
|
|
1714
|
-
: JSON.stringify(lastMessage.content);
|
|
1743
|
+
return typeof lastMessage.content === "string" ? lastMessage.content : JSON.stringify(lastMessage.content);
|
|
1715
1744
|
}
|
|
1716
1745
|
// Handle input.text format
|
|
1717
1746
|
return optionsOrPrompt.input?.text || "";
|
|
@@ -1803,8 +1832,7 @@ Current user's request: ${currentInput}`;
|
|
|
1803
1832
|
endpoint: otelConfig.endpoint,
|
|
1804
1833
|
serviceName: otelConfig.serviceName,
|
|
1805
1834
|
}
|
|
1806
|
-
: isOpenTelemetryInitialized() ||
|
|
1807
|
-
process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
1835
|
+
: isOpenTelemetryInitialized() || process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
1808
1836
|
? {
|
|
1809
1837
|
enabled: isOpenTelemetryInitialized(),
|
|
1810
1838
|
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
@@ -1906,6 +1934,18 @@ Current user's request: ${currentInput}`;
|
|
|
1906
1934
|
logger.warn("[NeuroLink] MCP servers shutdown failed:", error);
|
|
1907
1935
|
}
|
|
1908
1936
|
}
|
|
1937
|
+
// Shutdown TaskManager
|
|
1938
|
+
if (this._taskManager) {
|
|
1939
|
+
try {
|
|
1940
|
+
await withTimeout(this._taskManager.shutdown(), 5000, new Error("TaskManager shutdown timed out"));
|
|
1941
|
+
}
|
|
1942
|
+
catch (error) {
|
|
1943
|
+
logger.warn("[NeuroLink] TaskManager shutdown error:", error);
|
|
1944
|
+
}
|
|
1945
|
+
finally {
|
|
1946
|
+
this._taskManager = undefined;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1909
1949
|
// Close conversation memory manager (release Redis connections, etc.)
|
|
1910
1950
|
if (this.conversationMemory?.close) {
|
|
1911
1951
|
try {
|
|
@@ -1934,9 +1974,7 @@ Current user's request: ${currentInput}`;
|
|
|
1934
1974
|
const result = data.result;
|
|
1935
1975
|
const usage = result?.usage;
|
|
1936
1976
|
const analytics = result?.analytics;
|
|
1937
|
-
const provider = data.provider ||
|
|
1938
|
-
result?.provider ||
|
|
1939
|
-
"unknown";
|
|
1977
|
+
const provider = data.provider || result?.provider || "unknown";
|
|
1940
1978
|
const model = result?.model || "unknown";
|
|
1941
1979
|
const responseTime = data.responseTime || 0;
|
|
1942
1980
|
const traceCtx = this._metricsTraceContext;
|
|
@@ -1955,9 +1993,7 @@ Current user's request: ${currentInput}`;
|
|
|
1955
1993
|
span.parentSpanId = undefined;
|
|
1956
1994
|
}
|
|
1957
1995
|
// Mark failed generations with ERROR status so metrics count them correctly
|
|
1958
|
-
const spanStatus = data.success === false || data.error
|
|
1959
|
-
? SpanStatus.ERROR
|
|
1960
|
-
: SpanStatus.OK;
|
|
1996
|
+
const spanStatus = data.success === false || data.error ? SpanStatus.ERROR : SpanStatus.OK;
|
|
1961
1997
|
span = SpanSerializer.endSpan(span, spanStatus, data.error ? String(data.error) : undefined);
|
|
1962
1998
|
span.durationMs = responseTime;
|
|
1963
1999
|
if (usage) {
|
|
@@ -1993,9 +2029,7 @@ Current user's request: ${currentInput}`;
|
|
|
1993
2029
|
const content = result?.content || result?.text;
|
|
1994
2030
|
if (content) {
|
|
1995
2031
|
span = SpanSerializer.updateAttributes(span, {
|
|
1996
|
-
output: content.length > 5000
|
|
1997
|
-
? content.substring(0, 5000) + "...[truncated]"
|
|
1998
|
-
: content,
|
|
2032
|
+
output: content.length > 5000 ? content.substring(0, 5000) + "...[truncated]" : content,
|
|
1999
2033
|
});
|
|
2000
2034
|
}
|
|
2001
2035
|
this.metricsAggregator.recordSpan(span);
|
|
@@ -2034,18 +2068,14 @@ Current user's request: ${currentInput}`;
|
|
|
2034
2068
|
if (data.prompt) {
|
|
2035
2069
|
const promptStr = String(data.prompt);
|
|
2036
2070
|
span = SpanSerializer.updateAttributes(span, {
|
|
2037
|
-
input: promptStr.length > 5000
|
|
2038
|
-
? promptStr.substring(0, 5000) + "...[truncated]"
|
|
2039
|
-
: promptStr,
|
|
2071
|
+
input: promptStr.length > 5000 ? promptStr.substring(0, 5000) + "...[truncated]" : promptStr,
|
|
2040
2072
|
});
|
|
2041
2073
|
}
|
|
2042
2074
|
// Record streamed output (truncated for safety)
|
|
2043
2075
|
const streamContent = data.content;
|
|
2044
2076
|
if (streamContent) {
|
|
2045
2077
|
span = SpanSerializer.updateAttributes(span, {
|
|
2046
|
-
output: streamContent.length > 5000
|
|
2047
|
-
? streamContent.substring(0, 5000) + "...[truncated]"
|
|
2048
|
-
: streamContent,
|
|
2078
|
+
output: streamContent.length > 5000 ? streamContent.substring(0, 5000) + "...[truncated]" : streamContent,
|
|
2049
2079
|
});
|
|
2050
2080
|
}
|
|
2051
2081
|
// Enrich stream span with token usage if available
|
|
@@ -2062,8 +2092,7 @@ Current user's request: ${currentInput}`;
|
|
|
2062
2092
|
const pricing = tokenTracker.getModelPricing(model);
|
|
2063
2093
|
if (pricing) {
|
|
2064
2094
|
const inputCost = ((usage.input || 0) / 1_000_000) * pricing.inputPricePerMillion;
|
|
2065
|
-
const outputCost = ((usage.output || 0) / 1_000_000) *
|
|
2066
|
-
pricing.outputPricePerMillion;
|
|
2095
|
+
const outputCost = ((usage.output || 0) / 1_000_000) * pricing.outputPricePerMillion;
|
|
2067
2096
|
const totalCost = inputCost + outputCost;
|
|
2068
2097
|
if (totalCost > 0) {
|
|
2069
2098
|
span = SpanSerializer.enrichWithCost(span, {
|
|
@@ -2098,8 +2127,7 @@ Current user's request: ${currentInput}`;
|
|
|
2098
2127
|
span = SpanSerializer.endSpan(span, success ? SpanStatus.OK : SpanStatus.ERROR);
|
|
2099
2128
|
span.durationMs = responseTime;
|
|
2100
2129
|
if (!success && data.error) {
|
|
2101
|
-
span.statusMessage =
|
|
2102
|
-
data.error.message || String(data.error);
|
|
2130
|
+
span.statusMessage = data.error.message || String(data.error);
|
|
2103
2131
|
}
|
|
2104
2132
|
if (data.result) {
|
|
2105
2133
|
try {
|
|
@@ -2256,10 +2284,7 @@ Current user's request: ${currentInput}`;
|
|
|
2256
2284
|
// The generation span will be the root (no parentSpanId).
|
|
2257
2285
|
// Tool spans will be children of the root span via rootSpanId.
|
|
2258
2286
|
const metricsTraceId = crypto.randomUUID().replace(/-/g, "");
|
|
2259
|
-
const metricsRootSpanId = crypto
|
|
2260
|
-
.randomUUID()
|
|
2261
|
-
.replace(/-/g, "")
|
|
2262
|
-
.substring(0, 16);
|
|
2287
|
+
const metricsRootSpanId = crypto.randomUUID().replace(/-/g, "").substring(0, 16);
|
|
2263
2288
|
// Scope trace context to this request via AsyncLocalStorage
|
|
2264
2289
|
// so concurrent generate/stream calls don't race.
|
|
2265
2290
|
return metricsTraceContextStorage.run({ traceId: metricsTraceId, parentSpanId: metricsRootSpanId }, async () => {
|
|
@@ -2267,24 +2292,18 @@ Current user's request: ${currentInput}`;
|
|
|
2267
2292
|
const originalPrompt = this._extractOriginalPrompt(optionsOrPrompt);
|
|
2268
2293
|
// Convert string prompt to full options
|
|
2269
2294
|
// Shallow-copy caller's object to avoid mutating their original reference
|
|
2270
|
-
const options = typeof optionsOrPrompt === "string"
|
|
2271
|
-
? { input: { text: optionsOrPrompt } }
|
|
2272
|
-
: { ...optionsOrPrompt };
|
|
2295
|
+
const options = typeof optionsOrPrompt === "string" ? { input: { text: optionsOrPrompt } } : { ...optionsOrPrompt };
|
|
2273
2296
|
// NL-004: Resolve model aliases/deprecations before processing
|
|
2274
2297
|
options.model = resolveModel(options.model, this.modelAliasConfig);
|
|
2275
2298
|
// MCP Enhancement: propagate disableToolCache to tool execution
|
|
2276
|
-
this._disableToolCacheForCurrentRequest =
|
|
2277
|
-
!!options.disableToolCache;
|
|
2299
|
+
this._disableToolCacheForCurrentRequest = !!options.disableToolCache;
|
|
2278
2300
|
// Set span attributes for observability
|
|
2279
2301
|
generateSpan.setAttribute("neurolink.provider", options.provider || "default");
|
|
2280
2302
|
generateSpan.setAttribute("neurolink.model", options.model || "default");
|
|
2281
|
-
generateSpan.setAttribute("neurolink.input_length", typeof optionsOrPrompt === "string"
|
|
2282
|
-
? optionsOrPrompt.length
|
|
2283
|
-
: options.input?.text?.length || 0);
|
|
2303
|
+
generateSpan.setAttribute("neurolink.input_length", typeof optionsOrPrompt === "string" ? optionsOrPrompt.length : options.input?.text?.length || 0);
|
|
2284
2304
|
generateSpan.setAttribute("neurolink.has_tools", !!(options.tools && Object.keys(options.tools).length > 0));
|
|
2285
2305
|
// Validate prompt
|
|
2286
|
-
if (!options.input?.text ||
|
|
2287
|
-
typeof options.input.text !== "string") {
|
|
2306
|
+
if (!options.input?.text || typeof options.input.text !== "string") {
|
|
2288
2307
|
throw new Error("Input text is required and must be a non-empty string");
|
|
2289
2308
|
}
|
|
2290
2309
|
// Check budget limit before making API call
|
|
@@ -2314,10 +2333,9 @@ Current user's request: ${currentInput}`;
|
|
|
2314
2333
|
...options.middleware?.middlewareConfig?.lifecycle,
|
|
2315
2334
|
enabled: true,
|
|
2316
2335
|
config: {
|
|
2317
|
-
...options.middleware?.middlewareConfig?.lifecycle
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
onError: options.onError,
|
|
2336
|
+
...options.middleware?.middlewareConfig?.lifecycle?.config,
|
|
2337
|
+
...(options.onFinish !== undefined ? { onFinish: options.onFinish } : {}),
|
|
2338
|
+
...(options.onError !== undefined ? { onError: options.onError } : {}),
|
|
2321
2339
|
},
|
|
2322
2340
|
},
|
|
2323
2341
|
},
|
|
@@ -2336,9 +2354,7 @@ Current user's request: ${currentInput}`;
|
|
|
2336
2354
|
}
|
|
2337
2355
|
catch (err) {
|
|
2338
2356
|
// Rethrow auth errors as-is; wrap anything else
|
|
2339
|
-
if (err instanceof Error &&
|
|
2340
|
-
"feature" in err &&
|
|
2341
|
-
err.feature === "Auth") {
|
|
2357
|
+
if (err instanceof Error && "feature" in err && err.feature === "Auth") {
|
|
2342
2358
|
throw err;
|
|
2343
2359
|
}
|
|
2344
2360
|
throw AuthError.create("PROVIDER_ERROR", `Auth token validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -2398,9 +2414,7 @@ Current user's request: ${currentInput}`;
|
|
|
2398
2414
|
return await this.setLangfuseContextFromOptions(options, async () => {
|
|
2399
2415
|
const startTime = Date.now();
|
|
2400
2416
|
// Apply orchestration if enabled and no specific provider/model requested
|
|
2401
|
-
if (this.enableOrchestration &&
|
|
2402
|
-
!options.provider &&
|
|
2403
|
-
!options.model) {
|
|
2417
|
+
if (this.enableOrchestration && !options.provider && !options.model) {
|
|
2404
2418
|
try {
|
|
2405
2419
|
const orchestratedOptions = await this.applyOrchestration(options);
|
|
2406
2420
|
logger.debug("Orchestration applied", {
|
|
@@ -2418,9 +2432,7 @@ Current user's request: ${currentInput}`;
|
|
|
2418
2432
|
}
|
|
2419
2433
|
catch (error) {
|
|
2420
2434
|
logger.warn("Orchestration failed, continuing with original options", {
|
|
2421
|
-
error: error instanceof Error
|
|
2422
|
-
? error.message
|
|
2423
|
-
: String(error),
|
|
2435
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2424
2436
|
originalProvider: options.provider || "auto",
|
|
2425
2437
|
});
|
|
2426
2438
|
// Continue with original options if orchestration fails
|
|
@@ -2465,8 +2477,7 @@ Current user's request: ${currentInput}`;
|
|
|
2465
2477
|
`This tool searches your local knowledge base of pre-loaded documents and is the primary source of truth.`,
|
|
2466
2478
|
`Do NOT use websearchGrounding or any web search tools when the answer can be found in the loaded documents.`,
|
|
2467
2479
|
].join(" ");
|
|
2468
|
-
options.systemPrompt =
|
|
2469
|
-
(options.systemPrompt || "") + ragSystemInstruction;
|
|
2480
|
+
options.systemPrompt = (options.systemPrompt || "") + ragSystemInstruction;
|
|
2470
2481
|
logger.info("[RAG] Tool injected into generate()", {
|
|
2471
2482
|
toolName: ragResult.toolName,
|
|
2472
2483
|
filesLoaded: ragResult.filesLoaded,
|
|
@@ -2475,15 +2486,12 @@ Current user's request: ${currentInput}`;
|
|
|
2475
2486
|
}
|
|
2476
2487
|
catch (error) {
|
|
2477
2488
|
logger.warn("[RAG] Failed to prepare RAG tool, continuing without RAG", {
|
|
2478
|
-
error: error instanceof Error
|
|
2479
|
-
? error.message
|
|
2480
|
-
: String(error),
|
|
2489
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2481
2490
|
});
|
|
2482
2491
|
}
|
|
2483
2492
|
}
|
|
2484
2493
|
// Memory retrieval for generate path
|
|
2485
|
-
if (this.shouldReadMemory(options.memory, options.context?.userId) &&
|
|
2486
|
-
options.context?.userId) {
|
|
2494
|
+
if (this.shouldReadMemory(options.memory, options.context?.userId) && options.context?.userId) {
|
|
2487
2495
|
try {
|
|
2488
2496
|
options.input.text = await this.retrieveMemory(options.input.text, options.context.userId, options.memory?.additionalUsers);
|
|
2489
2497
|
logger.debug("Memory retrieval successful (generate)");
|
|
@@ -2521,6 +2529,8 @@ Current user's request: ${currentInput}`;
|
|
|
2521
2529
|
abortSignal: options.abortSignal,
|
|
2522
2530
|
skipToolPromptInjection: options.skipToolPromptInjection,
|
|
2523
2531
|
middleware: options.middleware,
|
|
2532
|
+
// Pass through conversation messages for task continuation and external callers
|
|
2533
|
+
conversationMessages: options.conversationMessages,
|
|
2524
2534
|
};
|
|
2525
2535
|
// Auto-map top-level sessionId/userId to context for convenience
|
|
2526
2536
|
// Tests and users may pass sessionId/userId as top-level options
|
|
@@ -2528,8 +2538,7 @@ Current user's request: ${currentInput}`;
|
|
|
2528
2538
|
if (extraContext.sessionId || extraContext.userId) {
|
|
2529
2539
|
baseOptions.context = {
|
|
2530
2540
|
...baseOptions.context,
|
|
2531
|
-
...(extraContext.sessionId &&
|
|
2532
|
-
!baseOptions.context?.sessionId
|
|
2541
|
+
...(extraContext.sessionId && !baseOptions.context?.sessionId
|
|
2533
2542
|
? { sessionId: extraContext.sessionId }
|
|
2534
2543
|
: {}),
|
|
2535
2544
|
...(extraContext.userId && !baseOptions.context?.userId
|
|
@@ -2541,8 +2550,7 @@ Current user's request: ${currentInput}`;
|
|
|
2541
2550
|
const textOptions = enhanceTextGenerationOptions(baseOptions, factoryResult);
|
|
2542
2551
|
// Pass conversation memory config if available
|
|
2543
2552
|
if (this.conversationMemory) {
|
|
2544
|
-
textOptions.conversationMemoryConfig =
|
|
2545
|
-
this.conversationMemory.config;
|
|
2553
|
+
textOptions.conversationMemoryConfig = this.conversationMemory.config;
|
|
2546
2554
|
// Include original prompt for context summarization
|
|
2547
2555
|
textOptions.originalPrompt = originalPrompt;
|
|
2548
2556
|
}
|
|
@@ -2565,8 +2573,7 @@ Current user's request: ${currentInput}`;
|
|
|
2565
2573
|
toolsUsed: textResult.toolsUsed,
|
|
2566
2574
|
timestamp: Date.now(),
|
|
2567
2575
|
result: textResult, // Enhanced: include full result
|
|
2568
|
-
prompt: options.input?.text ||
|
|
2569
|
-
options.prompt,
|
|
2576
|
+
prompt: options.input?.text || options.prompt,
|
|
2570
2577
|
temperature: textOptions.temperature,
|
|
2571
2578
|
maxTokens: textOptions.maxTokens,
|
|
2572
2579
|
});
|
|
@@ -2599,10 +2606,8 @@ Current user's request: ${currentInput}`;
|
|
|
2599
2606
|
? {
|
|
2600
2607
|
...textResult.evaluation,
|
|
2601
2608
|
isOffTopic: textResult.evaluation.isOffTopic ?? false,
|
|
2602
|
-
alertSeverity: textResult.evaluation.alertSeverity ??
|
|
2603
|
-
|
|
2604
|
-
reasoning: textResult.evaluation.reasoning ??
|
|
2605
|
-
"No evaluation provided",
|
|
2609
|
+
alertSeverity: textResult.evaluation.alertSeverity ?? "none",
|
|
2610
|
+
reasoning: textResult.evaluation.reasoning ?? "No evaluation provided",
|
|
2606
2611
|
evaluationModel: textResult.evaluation.evaluationModel ?? "unknown",
|
|
2607
2612
|
evaluationTime: textResult.evaluation.evaluationTime ?? Date.now(),
|
|
2608
2613
|
evaluationDomain: textResult.evaluation.evaluationDomain ??
|
|
@@ -2617,8 +2622,7 @@ Current user's request: ${currentInput}`;
|
|
|
2617
2622
|
...(textResult.retries && { retries: textResult.retries }),
|
|
2618
2623
|
};
|
|
2619
2624
|
// Accumulate session cost for budget tracking
|
|
2620
|
-
if (generateResult.analytics?.cost &&
|
|
2621
|
-
generateResult.analytics.cost > 0) {
|
|
2625
|
+
if (generateResult.analytics?.cost && generateResult.analytics.cost > 0) {
|
|
2622
2626
|
this._sessionCostUsd += generateResult.analytics.cost;
|
|
2623
2627
|
}
|
|
2624
2628
|
this.scheduleGenerateMemoryStorage(options, originalPrompt, generateResult);
|
|
@@ -2646,9 +2650,7 @@ Current user's request: ${currentInput}`;
|
|
|
2646
2650
|
const errProvider = typeof optionsOrPrompt === "object"
|
|
2647
2651
|
? optionsOrPrompt.provider || "unknown"
|
|
2648
2652
|
: "unknown";
|
|
2649
|
-
const errModel = typeof optionsOrPrompt === "object"
|
|
2650
|
-
? optionsOrPrompt.model || "unknown"
|
|
2651
|
-
: "unknown";
|
|
2653
|
+
const errModel = typeof optionsOrPrompt === "object" ? optionsOrPrompt.model || "unknown" : "unknown";
|
|
2652
2654
|
try {
|
|
2653
2655
|
this.emitter.emit("generation:end", {
|
|
2654
2656
|
provider: errProvider,
|
|
@@ -2745,7 +2747,12 @@ Current user's request: ${currentInput}`;
|
|
|
2745
2747
|
// Execute workflow
|
|
2746
2748
|
const workflowResult = await runWorkflow(workflowConfig, {
|
|
2747
2749
|
prompt: options.input.text,
|
|
2748
|
-
conversationHistory: options.
|
|
2750
|
+
conversationHistory: options.conversationMessages
|
|
2751
|
+
?.filter((m) => m.role === "user" || m.role === "assistant")
|
|
2752
|
+
.map((m) => ({
|
|
2753
|
+
role: m.role,
|
|
2754
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
|
|
2755
|
+
})) ?? options.conversationHistory,
|
|
2749
2756
|
timeout: options.timeout,
|
|
2750
2757
|
verbose: false,
|
|
2751
2758
|
metadata: options.context,
|
|
@@ -2755,10 +2762,8 @@ Current user's request: ${currentInput}`;
|
|
|
2755
2762
|
// Primary output (backward compatible) - use the original best response
|
|
2756
2763
|
content: workflowResult.content,
|
|
2757
2764
|
// Provider info from selected response
|
|
2758
|
-
provider: workflowResult.selectedResponse?.provider ||
|
|
2759
|
-
|
|
2760
|
-
model: workflowResult.selectedResponse?.model ||
|
|
2761
|
-
workflowConfig.models[0]?.model,
|
|
2765
|
+
provider: workflowResult.selectedResponse?.provider || workflowConfig.models[0]?.provider,
|
|
2766
|
+
model: workflowResult.selectedResponse?.model || workflowConfig.models[0]?.model,
|
|
2762
2767
|
// Basic usage info
|
|
2763
2768
|
usage: workflowResult.usage
|
|
2764
2769
|
? {
|
|
@@ -2836,7 +2841,12 @@ Current user's request: ${currentInput}`;
|
|
|
2836
2841
|
// Execute workflow with progressive streaming
|
|
2837
2842
|
const workflowStream = runWorkflowWithStreaming(workflowConfig, {
|
|
2838
2843
|
prompt: options.input.text,
|
|
2839
|
-
conversationHistory: options.
|
|
2844
|
+
conversationHistory: options.conversationMessages
|
|
2845
|
+
?.filter((m) => m.role === "user" || m.role === "assistant")
|
|
2846
|
+
.map((m) => ({
|
|
2847
|
+
role: m.role,
|
|
2848
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
|
|
2849
|
+
})) ?? options.conversationHistory,
|
|
2840
2850
|
timeout: options.timeout,
|
|
2841
2851
|
verbose: false,
|
|
2842
2852
|
metadata: options.context,
|
|
@@ -2960,9 +2970,7 @@ Current user's request: ${currentInput}`;
|
|
|
2960
2970
|
*/
|
|
2961
2971
|
async generateText(options) {
|
|
2962
2972
|
// Validate required parameters for backward compatibility
|
|
2963
|
-
if (!options.prompt ||
|
|
2964
|
-
typeof options.prompt !== "string" ||
|
|
2965
|
-
options.prompt.trim() === "") {
|
|
2973
|
+
if (!options.prompt || typeof options.prompt !== "string" || options.prompt.trim() === "") {
|
|
2966
2974
|
throw new Error("GenerateText options must include prompt as a non-empty string");
|
|
2967
2975
|
}
|
|
2968
2976
|
// NL-004: Resolve model aliases/deprecations before processing
|
|
@@ -3045,9 +3053,7 @@ Current user's request: ${currentInput}`;
|
|
|
3045
3053
|
// the catch block needs the originals for a more effective retry
|
|
3046
3054
|
if (this.conversationMemory) {
|
|
3047
3055
|
const originalMessages = await getConversationMessages(this.conversationMemory, options);
|
|
3048
|
-
options._originalConversationMessages = originalMessages
|
|
3049
|
-
? [...originalMessages]
|
|
3050
|
-
: undefined;
|
|
3056
|
+
options._originalConversationMessages = originalMessages ? [...originalMessages] : undefined;
|
|
3051
3057
|
}
|
|
3052
3058
|
const directResult = await this.directProviderGeneration(options);
|
|
3053
3059
|
logger.debug(`[${functionTag}] Direct generation successful`);
|
|
@@ -3097,8 +3103,7 @@ Current user's request: ${currentInput}`;
|
|
|
3097
3103
|
// IMPROVEMENT 1: Extract actual token count from provider error if available
|
|
3098
3104
|
const actualOverflow = parseProviderOverflowDetails(error);
|
|
3099
3105
|
// IMPROVEMENT 2: Use ORIGINAL messages (not already-compacted ones)
|
|
3100
|
-
const originalMessages = options._originalConversationMessages ??
|
|
3101
|
-
(await getConversationMessages(this.conversationMemory, options));
|
|
3106
|
+
const originalMessages = options._originalConversationMessages ?? (await getConversationMessages(this.conversationMemory, options));
|
|
3102
3107
|
// IMPROVEMENT 3: Calculate precise reduction target
|
|
3103
3108
|
const recoveryBudget = checkContextBudget({
|
|
3104
3109
|
provider: options.provider || "openai",
|
|
@@ -3108,16 +3113,12 @@ Current user's request: ${currentInput}`;
|
|
|
3108
3113
|
systemPrompt: options.systemPrompt,
|
|
3109
3114
|
});
|
|
3110
3115
|
// Use provider's reported token count if available (more accurate than our estimate)
|
|
3111
|
-
const actualTokens = actualOverflow?.actualTokens ??
|
|
3112
|
-
|
|
3113
|
-
const budgetTokens = actualOverflow?.budgetTokens ??
|
|
3114
|
-
recoveryBudget.availableInputTokens;
|
|
3116
|
+
const actualTokens = actualOverflow?.actualTokens ?? recoveryBudget.estimatedInputTokens;
|
|
3117
|
+
const budgetTokens = actualOverflow?.budgetTokens ?? recoveryBudget.availableInputTokens;
|
|
3115
3118
|
// Target = 70% of budget (aggressive safety margin for recovery)
|
|
3116
3119
|
const compactionTarget = Math.floor(budgetTokens * 0.7);
|
|
3117
3120
|
// IMPROVEMENT 4: Calculate adaptive truncation fraction from actual numbers
|
|
3118
|
-
const requiredReduction = actualTokens > 0
|
|
3119
|
-
? (actualTokens - compactionTarget) / actualTokens
|
|
3120
|
-
: 0.5;
|
|
3121
|
+
const requiredReduction = actualTokens > 0 ? (actualTokens - compactionTarget) / actualTokens : 0.5;
|
|
3121
3122
|
const compactor = new ContextCompactor({
|
|
3122
3123
|
enableSummarize: false, // Skip LLM call for recovery (speed)
|
|
3123
3124
|
enablePrune: true,
|
|
@@ -3170,9 +3171,7 @@ Current user's request: ${currentInput}`;
|
|
|
3170
3171
|
throw retryError;
|
|
3171
3172
|
}
|
|
3172
3173
|
logger.error(`[${functionTag}] Recovery attempt failed`, {
|
|
3173
|
-
error: retryError instanceof Error
|
|
3174
|
-
? retryError.message
|
|
3175
|
-
: String(retryError),
|
|
3174
|
+
error: retryError instanceof Error ? retryError.message : String(retryError),
|
|
3176
3175
|
});
|
|
3177
3176
|
}
|
|
3178
3177
|
}
|
|
@@ -3185,8 +3184,7 @@ Current user's request: ${currentInput}`;
|
|
|
3185
3184
|
logger.info(`[${functionTag}] Generation aborted — storing conversation turn for title generation`, {
|
|
3186
3185
|
hasMemory: !!this.conversationMemory,
|
|
3187
3186
|
memoryType: this.conversationMemory?.constructor?.name || "NONE",
|
|
3188
|
-
sessionId: options.context?.sessionId ||
|
|
3189
|
-
"unknown",
|
|
3187
|
+
sessionId: options.context?.sessionId || "unknown",
|
|
3190
3188
|
});
|
|
3191
3189
|
try {
|
|
3192
3190
|
const abortedResult = {
|
|
@@ -3199,9 +3197,7 @@ Current user's request: ${currentInput}`;
|
|
|
3199
3197
|
}
|
|
3200
3198
|
catch (storeError) {
|
|
3201
3199
|
logger.warn(`[${functionTag}] Failed to store conversation turn after abort`, {
|
|
3202
|
-
error: storeError instanceof Error
|
|
3203
|
-
? storeError.message
|
|
3204
|
-
: String(storeError),
|
|
3200
|
+
error: storeError instanceof Error ? storeError.message : String(storeError),
|
|
3205
3201
|
});
|
|
3206
3202
|
}
|
|
3207
3203
|
}
|
|
@@ -3218,9 +3214,7 @@ Current user's request: ${currentInput}`;
|
|
|
3218
3214
|
catch (spanError) {
|
|
3219
3215
|
internalSpan.setStatus({
|
|
3220
3216
|
code: SpanStatusCode.ERROR,
|
|
3221
|
-
message: spanError instanceof Error
|
|
3222
|
-
? spanError.message
|
|
3223
|
-
: String(spanError),
|
|
3217
|
+
message: spanError instanceof Error ? spanError.message : String(spanError),
|
|
3224
3218
|
});
|
|
3225
3219
|
throw spanError;
|
|
3226
3220
|
}
|
|
@@ -3300,8 +3294,7 @@ Current user's request: ${currentInput}`;
|
|
|
3300
3294
|
* Attempt MCP generation with retry logic
|
|
3301
3295
|
*/
|
|
3302
3296
|
async attemptMCPGeneration(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag) {
|
|
3303
|
-
if (!options.disableTools &&
|
|
3304
|
-
!(options.tts?.enabled && !options.tts?.useAiResponse)) {
|
|
3297
|
+
if (!options.disableTools && !(options.tts?.enabled && !options.tts?.useAiResponse)) {
|
|
3305
3298
|
return await this.performMCPGenerationRetries(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag);
|
|
3306
3299
|
}
|
|
3307
3300
|
return null;
|
|
@@ -3323,9 +3316,7 @@ Current user's request: ${currentInput}`;
|
|
|
3323
3316
|
try {
|
|
3324
3317
|
logger.debug(`[${functionTag}] Attempting MCP generation (attempt ${attempt}/${maxAttempts})...`);
|
|
3325
3318
|
const mcpResult = await this.tryMCPGeneration(options);
|
|
3326
|
-
if (mcpResult &&
|
|
3327
|
-
(mcpResult.content ||
|
|
3328
|
-
(mcpResult.toolExecutions && mcpResult.toolExecutions.length > 0))) {
|
|
3319
|
+
if (mcpResult && (mcpResult.content || (mcpResult.toolExecutions && mcpResult.toolExecutions.length > 0))) {
|
|
3329
3320
|
logger.debug(`[${functionTag}] MCP generation successful on attempt ${attempt}`, {
|
|
3330
3321
|
contentLength: mcpResult.content?.length || 0,
|
|
3331
3322
|
toolsUsed: mcpResult.toolsUsed?.length || 0,
|
|
@@ -3356,11 +3347,7 @@ Current user's request: ${currentInput}`;
|
|
|
3356
3347
|
// NL-007: Record retry error for observability
|
|
3357
3348
|
retryCount++;
|
|
3358
3349
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
3359
|
-
const errCode = error instanceof NeuroLinkError
|
|
3360
|
-
? error.code
|
|
3361
|
-
: error instanceof Error
|
|
3362
|
-
? error.name
|
|
3363
|
-
: "UNKNOWN";
|
|
3350
|
+
const errCode = error instanceof NeuroLinkError ? error.code : error instanceof Error ? error.name : "UNKNOWN";
|
|
3364
3351
|
retryErrors.push({ code: errCode, message: errMsg.substring(0, 500) });
|
|
3365
3352
|
logger.debug(`[${functionTag}] MCP generation failed on attempt ${attempt}/${maxAttempts}`, {
|
|
3366
3353
|
error: errMsg,
|
|
@@ -3379,11 +3366,8 @@ Current user's request: ${currentInput}`;
|
|
|
3379
3366
|
const isNonRetryable = isContextOverflowError(error) ||
|
|
3380
3367
|
isToolError ||
|
|
3381
3368
|
isNonRetryableProviderError(error) ||
|
|
3382
|
-
(error instanceof Error &&
|
|
3383
|
-
|
|
3384
|
-
false) ||
|
|
3385
|
-
(error instanceof Error &&
|
|
3386
|
-
error.statusCode === 400);
|
|
3369
|
+
(error instanceof Error && error.isRetryable === false) ||
|
|
3370
|
+
(error instanceof Error && error.statusCode === 400);
|
|
3387
3371
|
if (isNonRetryable) {
|
|
3388
3372
|
logger.debug(`[${functionTag}] Non-retryable error detected, skipping remaining retries`);
|
|
3389
3373
|
break;
|
|
@@ -3419,8 +3403,7 @@ Current user's request: ${currentInput}`;
|
|
|
3419
3403
|
throw new DOMException("The operation was aborted", "AbortError");
|
|
3420
3404
|
}
|
|
3421
3405
|
// 🚀 EXHAUSTIVE LOGGING POINT T001: TRY MCP GENERATION ENTRY
|
|
3422
|
-
const requestId = options.context?.requestId ||
|
|
3423
|
-
"unknown";
|
|
3406
|
+
const requestId = options.context?.requestId || "unknown";
|
|
3424
3407
|
const tryMCPId = `try-mcp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3425
3408
|
const tryMCPStartTime = Date.now();
|
|
3426
3409
|
const tryMCPHrTimeStart = process.hrtime.bigint();
|
|
@@ -3448,9 +3431,7 @@ Current user's request: ${currentInput}`;
|
|
|
3448
3431
|
}
|
|
3449
3432
|
// Context creation removed - was never used
|
|
3450
3433
|
// Determine provider
|
|
3451
|
-
const providerName = options.provider === "auto" || !options.provider
|
|
3452
|
-
? await getBestProvider()
|
|
3453
|
-
: options.provider;
|
|
3434
|
+
const providerName = options.provider === "auto" || !options.provider ? await getBestProvider() : options.provider;
|
|
3454
3435
|
// Get available tools
|
|
3455
3436
|
let availableTools = await this.getAllAvailableTools();
|
|
3456
3437
|
// NL-001: Filter out tools with OPEN circuit breakers
|
|
@@ -3460,8 +3441,7 @@ Current user's request: ${currentInput}`;
|
|
|
3460
3441
|
availableTools = availableTools.filter((t) => cbFilteredNames.has(t.name));
|
|
3461
3442
|
// Apply per-call tool filtering for system prompt tool descriptions
|
|
3462
3443
|
availableTools = this.applyToolInfoFiltering(availableTools, options);
|
|
3463
|
-
const targetTool = availableTools.find((t) => t.name.includes("SuccessRateSRByTime") ||
|
|
3464
|
-
t.name.includes("juspay-analytics"));
|
|
3444
|
+
const targetTool = availableTools.find((t) => t.name.includes("SuccessRateSRByTime") || t.name.includes("juspay-analytics"));
|
|
3465
3445
|
logger.debug("Available tools for AI prompt generation", {
|
|
3466
3446
|
toolsCount: availableTools.length,
|
|
3467
3447
|
toolNames: availableTools.map((t) => t.name),
|
|
@@ -3495,9 +3475,7 @@ Current user's request: ${currentInput}`;
|
|
|
3495
3475
|
logger.debug("[Observability] System prompt metadata", {
|
|
3496
3476
|
requestId,
|
|
3497
3477
|
systemPromptLength: enhancedSystemPrompt.length,
|
|
3498
|
-
systemPromptHash: enhancedSystemPrompt.length > 0
|
|
3499
|
-
? `sha256:${enhancedSystemPrompt.slice(0, 8)}...`
|
|
3500
|
-
: "empty",
|
|
3478
|
+
systemPromptHash: enhancedSystemPrompt.length > 0 ? `sha256:${enhancedSystemPrompt.slice(0, 8)}...` : "empty",
|
|
3501
3479
|
hasCustomSystemPrompt: !!options.systemPrompt,
|
|
3502
3480
|
});
|
|
3503
3481
|
// Get conversation messages for context
|
|
@@ -3524,9 +3502,7 @@ Current user's request: ${currentInput}`;
|
|
|
3524
3502
|
index: i,
|
|
3525
3503
|
role: msg.role,
|
|
3526
3504
|
contentLength,
|
|
3527
|
-
contentPreview: typeof msg.content === "string"
|
|
3528
|
-
? msg.content.substring(0, 200)
|
|
3529
|
-
: "[multimodal]",
|
|
3505
|
+
contentPreview: typeof msg.content === "string" ? msg.content.substring(0, 200) : "[multimodal]",
|
|
3530
3506
|
};
|
|
3531
3507
|
}),
|
|
3532
3508
|
});
|
|
@@ -3567,8 +3543,7 @@ Current user's request: ${currentInput}`;
|
|
|
3567
3543
|
const compactionSessionId = this.getCompactionSessionId(options);
|
|
3568
3544
|
if (budgetResult.shouldCompact &&
|
|
3569
3545
|
this.conversationMemory &&
|
|
3570
|
-
messageCount >
|
|
3571
|
-
(this.lastCompactionMessageCount.get(compactionSessionId) ?? 0)) {
|
|
3546
|
+
messageCount > (this.lastCompactionMessageCount.get(compactionSessionId) ?? 0)) {
|
|
3572
3547
|
logger.info("[NeuroLink] Context budget exceeded, triggering auto-compaction", {
|
|
3573
3548
|
usageRatio: budgetResult.usageRatio,
|
|
3574
3549
|
estimatedTokens: budgetResult.estimatedInputTokens,
|
|
@@ -3576,10 +3551,8 @@ Current user's request: ${currentInput}`;
|
|
|
3576
3551
|
});
|
|
3577
3552
|
const compactor = new ContextCompactor({
|
|
3578
3553
|
provider: providerName,
|
|
3579
|
-
summarizationProvider: this.conversationMemoryConfig?.conversationMemory
|
|
3580
|
-
|
|
3581
|
-
summarizationModel: this.conversationMemoryConfig?.conversationMemory
|
|
3582
|
-
?.summarizationModel,
|
|
3554
|
+
summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
|
|
3555
|
+
summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
3583
3556
|
});
|
|
3584
3557
|
const compactionResult = await compactor.compact(conversationMessages, budgetResult.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, requestId);
|
|
3585
3558
|
if (compactionResult.compacted) {
|
|
@@ -3759,18 +3732,12 @@ Current user's request: ${currentInput}`;
|
|
|
3759
3732
|
];
|
|
3760
3733
|
const requestedProvider = options.provider === "auto" ? undefined : options.provider;
|
|
3761
3734
|
// Check for orchestrated preferred provider in context
|
|
3762
|
-
const preferredOrchestrated = options.context &&
|
|
3763
|
-
|
|
3764
|
-
"__orchestratedPreferredProvider" in options.context
|
|
3765
|
-
? options.context
|
|
3766
|
-
.__orchestratedPreferredProvider
|
|
3735
|
+
const preferredOrchestrated = options.context && typeof options.context === "object" && "__orchestratedPreferredProvider" in options.context
|
|
3736
|
+
? options.context.__orchestratedPreferredProvider
|
|
3767
3737
|
: undefined;
|
|
3768
3738
|
// Build provider list with orchestrated preference first, then fallback to full list
|
|
3769
3739
|
const tryProviders = preferredOrchestrated
|
|
3770
|
-
? [
|
|
3771
|
-
preferredOrchestrated,
|
|
3772
|
-
...providerPriority.filter((p) => p !== preferredOrchestrated),
|
|
3773
|
-
]
|
|
3740
|
+
? [preferredOrchestrated, ...providerPriority.filter((p) => p !== preferredOrchestrated)]
|
|
3774
3741
|
: requestedProvider
|
|
3775
3742
|
? [requestedProvider]
|
|
3776
3743
|
: providerPriority;
|
|
@@ -3790,8 +3757,7 @@ Current user's request: ${currentInput}`;
|
|
|
3790
3757
|
logger.debug(`[${functionTag}] Attempting provider: ${providerName}`);
|
|
3791
3758
|
// Get conversation messages for context (use pre-compacted if provided)
|
|
3792
3759
|
const optionsWithMessages = options;
|
|
3793
|
-
let conversationMessages = optionsWithMessages.conversationMessages
|
|
3794
|
-
?.length
|
|
3760
|
+
let conversationMessages = optionsWithMessages.conversationMessages?.length
|
|
3795
3761
|
? optionsWithMessages.conversationMessages
|
|
3796
3762
|
: await getConversationMessages(this.conversationMemory, options);
|
|
3797
3763
|
// Pre-generation budget check
|
|
@@ -3802,22 +3768,17 @@ Current user's request: ${currentInput}`;
|
|
|
3802
3768
|
systemPrompt: options.systemPrompt,
|
|
3803
3769
|
conversationMessages: conversationMessages,
|
|
3804
3770
|
currentPrompt: options.prompt,
|
|
3805
|
-
toolDefinitions: options.tools
|
|
3806
|
-
? Object.values(options.tools)
|
|
3807
|
-
: undefined,
|
|
3771
|
+
toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
|
|
3808
3772
|
});
|
|
3809
3773
|
const dpgMessageCount = conversationMessages?.length || 0;
|
|
3810
3774
|
const dpgCompactionSessionId = this.getCompactionSessionId(options);
|
|
3811
3775
|
if (budgetCheck.shouldCompact &&
|
|
3812
3776
|
this.conversationMemory &&
|
|
3813
|
-
dpgMessageCount >
|
|
3814
|
-
(this.lastCompactionMessageCount.get(dpgCompactionSessionId) ?? 0)) {
|
|
3777
|
+
dpgMessageCount > (this.lastCompactionMessageCount.get(dpgCompactionSessionId) ?? 0)) {
|
|
3815
3778
|
const compactor = new ContextCompactor({
|
|
3816
3779
|
provider: providerName,
|
|
3817
|
-
summarizationProvider: this.conversationMemoryConfig?.conversationMemory
|
|
3818
|
-
|
|
3819
|
-
summarizationModel: this.conversationMemoryConfig?.conversationMemory
|
|
3820
|
-
?.summarizationModel,
|
|
3780
|
+
summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
|
|
3781
|
+
summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
3821
3782
|
});
|
|
3822
3783
|
const compactionResult = await compactor.compact(conversationMessages, budgetCheck.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, options.context?.requestId);
|
|
3823
3784
|
if (compactionResult.compacted) {
|
|
@@ -3833,9 +3794,7 @@ Current user's request: ${currentInput}`;
|
|
|
3833
3794
|
systemPrompt: options.systemPrompt,
|
|
3834
3795
|
conversationMessages: conversationMessages,
|
|
3835
3796
|
currentPrompt: options.prompt,
|
|
3836
|
-
toolDefinitions: options.tools
|
|
3837
|
-
? Object.values(options.tools)
|
|
3838
|
-
: undefined,
|
|
3797
|
+
toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
|
|
3839
3798
|
});
|
|
3840
3799
|
if (!postCompactBudget.withinBudget) {
|
|
3841
3800
|
logger.warn("[NeuroLink] directProviderGeneration: post-compaction still over budget, emergency truncation", {
|
|
@@ -3851,9 +3810,7 @@ Current user's request: ${currentInput}`;
|
|
|
3851
3810
|
systemPrompt: options.systemPrompt,
|
|
3852
3811
|
conversationMessages: conversationMessages,
|
|
3853
3812
|
currentPrompt: options.prompt,
|
|
3854
|
-
toolDefinitions: options.tools
|
|
3855
|
-
? Object.values(options.tools)
|
|
3856
|
-
: undefined,
|
|
3813
|
+
toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
|
|
3857
3814
|
});
|
|
3858
3815
|
if (!finalBudget.withinBudget) {
|
|
3859
3816
|
throw new ContextBudgetExceededError(`Context exceeds model budget after all compaction stages. ` +
|
|
@@ -4111,10 +4068,7 @@ Current user's request: ${currentInput}`;
|
|
|
4111
4068
|
options = { ...options };
|
|
4112
4069
|
// Set metrics trace context for parent-child span linking
|
|
4113
4070
|
const metricsTraceId = crypto.randomUUID().replace(/-/g, "");
|
|
4114
|
-
const metricsParentSpanId = crypto
|
|
4115
|
-
.randomUUID()
|
|
4116
|
-
.replace(/-/g, "")
|
|
4117
|
-
.substring(0, 16);
|
|
4071
|
+
const metricsParentSpanId = crypto.randomUUID().replace(/-/g, "").substring(0, 16);
|
|
4118
4072
|
// Scope trace context to this request via AsyncLocalStorage
|
|
4119
4073
|
// so concurrent generate/stream calls don't race.
|
|
4120
4074
|
return metricsTraceContextStorage.run({ traceId: metricsTraceId, parentSpanId: metricsParentSpanId }, async () => {
|
|
@@ -4173,9 +4127,7 @@ Current user's request: ${currentInput}`;
|
|
|
4173
4127
|
}
|
|
4174
4128
|
catch (err) {
|
|
4175
4129
|
// Rethrow auth errors as-is; wrap anything else
|
|
4176
|
-
if (err instanceof Error &&
|
|
4177
|
-
"feature" in err &&
|
|
4178
|
-
err.feature === "Auth") {
|
|
4130
|
+
if (err instanceof Error && "feature" in err && err.feature === "Auth") {
|
|
4179
4131
|
throw err;
|
|
4180
4132
|
}
|
|
4181
4133
|
throw AuthError.create("PROVIDER_ERROR", `Auth token validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4228,9 +4180,9 @@ Current user's request: ${currentInput}`;
|
|
|
4228
4180
|
enabled: true,
|
|
4229
4181
|
config: {
|
|
4230
4182
|
...options.middleware?.middlewareConfig?.lifecycle?.config,
|
|
4231
|
-
onFinish: options.onFinish,
|
|
4232
|
-
onError: options.onError,
|
|
4233
|
-
onChunk: options.onChunk,
|
|
4183
|
+
...(options.onFinish !== undefined ? { onFinish: options.onFinish } : {}),
|
|
4184
|
+
...(options.onError !== undefined ? { onError: options.onError } : {}),
|
|
4185
|
+
...(options.onChunk !== undefined ? { onChunk: options.onChunk } : {}),
|
|
4234
4186
|
},
|
|
4235
4187
|
},
|
|
4236
4188
|
},
|
|
@@ -4269,7 +4221,12 @@ Current user's request: ${currentInput}`;
|
|
|
4269
4221
|
try {
|
|
4270
4222
|
// Prepare options: init memory, MCP, orchestration, Ollama auto-disable, tool detection
|
|
4271
4223
|
const { enhancedOptions, factoryResult } = await this.prepareStreamOptions(options, streamId, startTime, hrTimeStart);
|
|
4272
|
-
const { stream: mcpStream, provider: providerName, usage: streamUsage, model: streamModel, analytics: streamAnalytics, } = await this.createMCPStream(enhancedOptions);
|
|
4224
|
+
const { stream: mcpStream, provider: providerName, usage: streamUsage, model: streamModel, finishReason: streamFinishReason, toolCalls: streamToolCalls, toolResults: streamToolResults, analytics: streamAnalytics, } = await this.createMCPStream(enhancedOptions);
|
|
4225
|
+
const streamState = {
|
|
4226
|
+
finishReason: streamFinishReason ?? "stop",
|
|
4227
|
+
toolCalls: streamToolCalls,
|
|
4228
|
+
toolResults: streamToolResults,
|
|
4229
|
+
};
|
|
4273
4230
|
// Update span with resolved provider name
|
|
4274
4231
|
streamSpan.setAttribute(ATTR.NL_PROVIDER, providerName || "unknown");
|
|
4275
4232
|
let accumulatedContent = "";
|
|
@@ -4291,9 +4248,7 @@ Current user's request: ${currentInput}`;
|
|
|
4291
4248
|
try {
|
|
4292
4249
|
for await (const chunk of mcpStream) {
|
|
4293
4250
|
chunkCount++;
|
|
4294
|
-
if (chunk &&
|
|
4295
|
-
"content" in chunk &&
|
|
4296
|
-
typeof chunk.content === "string") {
|
|
4251
|
+
if (chunk && "content" in chunk && typeof chunk.content === "string") {
|
|
4297
4252
|
accumulatedContent += chunk.content;
|
|
4298
4253
|
self.emitter.emit("response:chunk", chunk.content);
|
|
4299
4254
|
// Emit stream:chunk event (Observability Solution 8)
|
|
@@ -4309,8 +4264,12 @@ Current user's request: ${currentInput}`;
|
|
|
4309
4264
|
}
|
|
4310
4265
|
yield chunk;
|
|
4311
4266
|
}
|
|
4312
|
-
if (chunkCount === 0 &&
|
|
4313
|
-
|
|
4267
|
+
if (chunkCount === 0 &&
|
|
4268
|
+
!metadata.fallbackAttempted &&
|
|
4269
|
+
!enhancedOptions.disableInternalFallback &&
|
|
4270
|
+
streamState.toolCalls.length === 0 &&
|
|
4271
|
+
streamState.toolResults.length === 0) {
|
|
4272
|
+
yield* self.handleStreamFallback(metadata, streamState, originalPrompt, enhancedOptions, providerName, accumulatedContent, (content) => {
|
|
4314
4273
|
accumulatedContent += content;
|
|
4315
4274
|
});
|
|
4316
4275
|
}
|
|
@@ -4318,9 +4277,7 @@ Current user's request: ${currentInput}`;
|
|
|
4318
4277
|
// When fallback took over, attribute the completion to the
|
|
4319
4278
|
// fallback provider so downstream telemetry reflects reality.
|
|
4320
4279
|
const effectiveProvider = metadata.fallbackProvider ?? providerName;
|
|
4321
|
-
const effectiveModel = metadata.fallbackModel ??
|
|
4322
|
-
streamModel ??
|
|
4323
|
-
enhancedOptions.model;
|
|
4280
|
+
const effectiveModel = metadata.fallbackModel ?? streamModel ?? enhancedOptions.model;
|
|
4324
4281
|
// Resolve analytics promise to get final token usage
|
|
4325
4282
|
let resolvedUsage = streamUsage;
|
|
4326
4283
|
if (!resolvedUsage && streamAnalytics) {
|
|
@@ -4339,8 +4296,7 @@ Current user's request: ${currentInput}`;
|
|
|
4339
4296
|
content: accumulatedContent,
|
|
4340
4297
|
provider: effectiveProvider,
|
|
4341
4298
|
model: effectiveModel,
|
|
4342
|
-
prompt: enhancedOptions.input?.text ||
|
|
4343
|
-
enhancedOptions.prompt,
|
|
4299
|
+
prompt: enhancedOptions.input?.text || enhancedOptions.prompt,
|
|
4344
4300
|
metadata: {
|
|
4345
4301
|
chunkCount,
|
|
4346
4302
|
totalLength: accumulatedContent.length,
|
|
@@ -4394,10 +4350,7 @@ Current user's request: ${currentInput}`;
|
|
|
4394
4350
|
if (primaryFailed) {
|
|
4395
4351
|
streamSpan.setStatus({
|
|
4396
4352
|
code: SpanStatusCode.ERROR,
|
|
4397
|
-
message: metadata.error ||
|
|
4398
|
-
(streamError instanceof Error
|
|
4399
|
-
? streamError.message
|
|
4400
|
-
: String(streamError)),
|
|
4353
|
+
message: metadata.error || (streamError instanceof Error ? streamError.message : String(streamError)),
|
|
4401
4354
|
});
|
|
4402
4355
|
}
|
|
4403
4356
|
else {
|
|
@@ -4424,10 +4377,18 @@ Current user's request: ${currentInput}`;
|
|
|
4424
4377
|
}
|
|
4425
4378
|
})();
|
|
4426
4379
|
const streamResult = await this.processStreamResult(processedStream, enhancedOptions, factoryResult);
|
|
4380
|
+
streamResult.finishReason = streamState.finishReason || streamResult.finishReason;
|
|
4381
|
+
streamResult.toolCalls = streamState.toolCalls;
|
|
4382
|
+
streamResult.toolResults = streamState.toolResults;
|
|
4383
|
+
if (!streamResult.usage) {
|
|
4384
|
+
streamResult.usage = streamUsage;
|
|
4385
|
+
}
|
|
4386
|
+
if (!streamResult.analytics) {
|
|
4387
|
+
streamResult.analytics = streamAnalytics instanceof Promise ? await streamAnalytics : streamAnalytics;
|
|
4388
|
+
}
|
|
4427
4389
|
const responseTime = Date.now() - startTime;
|
|
4428
4390
|
// Accumulate session cost for budget tracking
|
|
4429
|
-
if (streamResult.analytics?.cost &&
|
|
4430
|
-
streamResult.analytics.cost > 0) {
|
|
4391
|
+
if (streamResult.analytics?.cost && streamResult.analytics.cost > 0) {
|
|
4431
4392
|
this._sessionCostUsd += streamResult.analytics.cost;
|
|
4432
4393
|
}
|
|
4433
4394
|
this.emitStreamEndEvents(streamResult);
|
|
@@ -4444,6 +4405,9 @@ Current user's request: ${currentInput}`;
|
|
|
4444
4405
|
});
|
|
4445
4406
|
}
|
|
4446
4407
|
catch (error) {
|
|
4408
|
+
if (options.disableInternalFallback) {
|
|
4409
|
+
throw error;
|
|
4410
|
+
}
|
|
4447
4411
|
return this.handleStreamError(error, options, startTime, streamId, undefined, undefined);
|
|
4448
4412
|
}
|
|
4449
4413
|
});
|
|
@@ -4472,8 +4436,7 @@ Current user's request: ${currentInput}`;
|
|
|
4472
4436
|
// Initialize MCP
|
|
4473
4437
|
await this.initializeMCP();
|
|
4474
4438
|
// Memory retrieval
|
|
4475
|
-
if (this.shouldReadMemory(options.memory, options.context?.userId) &&
|
|
4476
|
-
options.context?.userId) {
|
|
4439
|
+
if (this.shouldReadMemory(options.memory, options.context?.userId) && options.context?.userId) {
|
|
4477
4440
|
try {
|
|
4478
4441
|
options.input.text = await this.retrieveMemory(options.input.text, options.context.userId, options.memory?.additionalUsers);
|
|
4479
4442
|
logger.debug("Memory retrieval successful");
|
|
@@ -4518,8 +4481,7 @@ Current user's request: ${currentInput}`;
|
|
|
4518
4481
|
if (!options.tools) {
|
|
4519
4482
|
options.tools = {};
|
|
4520
4483
|
}
|
|
4521
|
-
options.tools[ragResult.toolName] =
|
|
4522
|
-
ragResult.tool;
|
|
4484
|
+
options.tools[ragResult.toolName] = ragResult.tool;
|
|
4523
4485
|
// Inject RAG-aware system prompt so the AI uses the RAG tool first
|
|
4524
4486
|
const ragSystemInstruction = [
|
|
4525
4487
|
`\n\nIMPORTANT: You have a tool called "${ragResult.toolName}" that searches through`,
|
|
@@ -4528,8 +4490,7 @@ Current user's request: ${currentInput}`;
|
|
|
4528
4490
|
`This tool searches your local knowledge base of pre-loaded documents and is the primary source of truth.`,
|
|
4529
4491
|
`Do NOT use websearchGrounding or any web search tools when the answer can be found in the loaded documents.`,
|
|
4530
4492
|
].join(" ");
|
|
4531
|
-
options.systemPrompt =
|
|
4532
|
-
(options.systemPrompt || "") + ragSystemInstruction;
|
|
4493
|
+
options.systemPrompt = (options.systemPrompt || "") + ragSystemInstruction;
|
|
4533
4494
|
logger.info("[RAG] Tool injected into stream()", {
|
|
4534
4495
|
toolName: ragResult.toolName,
|
|
4535
4496
|
filesLoaded: ragResult.filesLoaded,
|
|
@@ -4557,8 +4518,7 @@ Current user's request: ${currentInput}`;
|
|
|
4557
4518
|
* Prevents overwhelming smaller models with massive tool descriptions in the system message.
|
|
4558
4519
|
*/
|
|
4559
4520
|
async autoDisableOllamaStreamTools(options) {
|
|
4560
|
-
if ((options.provider === "ollama" ||
|
|
4561
|
-
options.provider?.toLowerCase().includes("ollama")) &&
|
|
4521
|
+
if ((options.provider === "ollama" || options.provider?.toLowerCase().includes("ollama")) &&
|
|
4562
4522
|
!options.disableTools) {
|
|
4563
4523
|
const { ModelConfigurationManager } = await import("./core/modelConfiguration.js");
|
|
4564
4524
|
const modelConfig = ModelConfigurationManager.getInstance();
|
|
@@ -4642,7 +4602,7 @@ Current user's request: ${currentInput}`;
|
|
|
4642
4602
|
* Handle fallback when the primary stream returns 0 chunks.
|
|
4643
4603
|
* Yields chunks from a fallback provider and updates metadata accordingly.
|
|
4644
4604
|
*/
|
|
4645
|
-
async *handleStreamFallback(metadata, originalPrompt, enhancedOptions, providerName, _accumulatedContent, appendContent) {
|
|
4605
|
+
async *handleStreamFallback(metadata, streamState, originalPrompt, enhancedOptions, providerName, _accumulatedContent, appendContent) {
|
|
4646
4606
|
metadata.fallbackAttempted = true;
|
|
4647
4607
|
const errorMsg = "Stream completed with 0 chunks (possible guardrails block)";
|
|
4648
4608
|
metadata.error = errorMsg;
|
|
@@ -4700,18 +4660,23 @@ Current user's request: ${currentInput}`;
|
|
|
4700
4660
|
model: fallbackRoute.model,
|
|
4701
4661
|
conversationMessages,
|
|
4702
4662
|
});
|
|
4663
|
+
const fallbackToolCalls = fallbackResult.toolCalls ?? [];
|
|
4664
|
+
const fallbackToolResults = fallbackResult.toolResults ?? [];
|
|
4665
|
+
if (fallbackToolCalls.length > 0 || fallbackToolResults.length > 0) {
|
|
4666
|
+
streamState.toolCalls = fallbackToolCalls;
|
|
4667
|
+
streamState.toolResults = fallbackToolResults;
|
|
4668
|
+
streamState.finishReason = fallbackResult.finishReason ?? streamState.finishReason;
|
|
4669
|
+
}
|
|
4703
4670
|
let fallbackChunkCount = 0;
|
|
4704
4671
|
for await (const fallbackChunk of fallbackResult.stream) {
|
|
4705
4672
|
fallbackChunkCount++;
|
|
4706
|
-
if (fallbackChunk &&
|
|
4707
|
-
"content" in fallbackChunk &&
|
|
4708
|
-
typeof fallbackChunk.content === "string") {
|
|
4673
|
+
if (fallbackChunk && "content" in fallbackChunk && typeof fallbackChunk.content === "string") {
|
|
4709
4674
|
appendContent(fallbackChunk.content);
|
|
4710
4675
|
this.emitter.emit("response:chunk", fallbackChunk.content);
|
|
4711
4676
|
}
|
|
4712
4677
|
yield fallbackChunk;
|
|
4713
4678
|
}
|
|
4714
|
-
if (fallbackChunkCount === 0) {
|
|
4679
|
+
if (fallbackChunkCount === 0 && fallbackToolCalls.length === 0 && fallbackToolResults.length === 0) {
|
|
4715
4680
|
throw new Error(`Fallback provider ${fallbackRoute.provider} also returned 0 chunks`);
|
|
4716
4681
|
}
|
|
4717
4682
|
// Fallback succeeded - likely guardrails blocked primary
|
|
@@ -4720,9 +4685,7 @@ Current user's request: ${currentInput}`;
|
|
|
4720
4685
|
metadata.guardrailsBlocked = true;
|
|
4721
4686
|
}
|
|
4722
4687
|
catch (fallbackError) {
|
|
4723
|
-
const fallbackErrorMsg = fallbackError instanceof Error
|
|
4724
|
-
? fallbackError.message
|
|
4725
|
-
: String(fallbackError);
|
|
4688
|
+
const fallbackErrorMsg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
4726
4689
|
metadata.error = `${errorMsg}; Fallback failed: ${fallbackErrorMsg}`;
|
|
4727
4690
|
logger.error("Fallback provider failed", {
|
|
4728
4691
|
fallbackProvider: fallbackRoute.provider,
|
|
@@ -4736,22 +4699,19 @@ Current user's request: ${currentInput}`;
|
|
|
4736
4699
|
* Handles conversation memory storage in the background.
|
|
4737
4700
|
*/
|
|
4738
4701
|
async storeStreamConversationMemory(params) {
|
|
4739
|
-
const { enhancedOptions, providerName, originalPrompt, accumulatedContent, startTime, eventSequence
|
|
4702
|
+
const { enhancedOptions, providerName, originalPrompt, accumulatedContent, startTime, eventSequence } = params;
|
|
4740
4703
|
// Guard: skip storing if no meaningful content was produced (no text AND no tool activity)
|
|
4741
4704
|
const hasToolEvents = eventSequence.some((e) => e.type === "tool:start" || e.type === "tool:end");
|
|
4742
4705
|
if (!accumulatedContent.trim() && !hasToolEvents) {
|
|
4743
4706
|
logger.warn("[NeuroLink.stream] Skipping conversation turn storage — no text content or tool activity", {
|
|
4744
|
-
sessionId: enhancedOptions.context
|
|
4745
|
-
?.sessionId,
|
|
4707
|
+
sessionId: enhancedOptions.context?.sessionId,
|
|
4746
4708
|
});
|
|
4747
4709
|
return;
|
|
4748
4710
|
}
|
|
4749
4711
|
// Store memory after stream consumption is complete
|
|
4750
4712
|
if (this.conversationMemory && enhancedOptions.context?.sessionId) {
|
|
4751
|
-
const sessionId = enhancedOptions.context
|
|
4752
|
-
|
|
4753
|
-
const userId = enhancedOptions.context
|
|
4754
|
-
?.userId;
|
|
4713
|
+
const sessionId = enhancedOptions.context?.sessionId;
|
|
4714
|
+
const userId = enhancedOptions.context?.userId;
|
|
4755
4715
|
let providerDetails;
|
|
4756
4716
|
if (enhancedOptions.model) {
|
|
4757
4717
|
providerDetails = {
|
|
@@ -4770,8 +4730,7 @@ Current user's request: ${currentInput}`;
|
|
|
4770
4730
|
providerDetails,
|
|
4771
4731
|
enableSummarization: enhancedOptions.enableSummarization,
|
|
4772
4732
|
events: eventSequence.length > 0 ? eventSequence : undefined,
|
|
4773
|
-
requestId: enhancedOptions.context
|
|
4774
|
-
?.requestId,
|
|
4733
|
+
requestId: enhancedOptions.context?.requestId,
|
|
4775
4734
|
});
|
|
4776
4735
|
this.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": "stream" }, Date.now() - memStoreStart, SpanStatus.OK);
|
|
4777
4736
|
logger.debug("[NeuroLink.stream] Stored conversation turn with events", {
|
|
@@ -4801,8 +4760,7 @@ Current user's request: ${currentInput}`;
|
|
|
4801
4760
|
validationStartTimeNs: validationStartTime.toString(),
|
|
4802
4761
|
message: "Starting comprehensive input validation process",
|
|
4803
4762
|
});
|
|
4804
|
-
const hasText = typeof options?.input?.text === "string" &&
|
|
4805
|
-
options.input.text.trim().length > 0;
|
|
4763
|
+
const hasText = typeof options?.input?.text === "string" && options.input.text.trim().length > 0;
|
|
4806
4764
|
// Accept audio when frames are present; sampleRateHz is optional (defaults applied later)
|
|
4807
4765
|
const hasAudio = !!(options?.input?.audio &&
|
|
4808
4766
|
options.input.audio.frames &&
|
|
@@ -4881,12 +4839,10 @@ Current user's request: ${currentInput}`;
|
|
|
4881
4839
|
const streamCompactionSessionId = this.getCompactionSessionId(options);
|
|
4882
4840
|
if (streamBudget.shouldCompact &&
|
|
4883
4841
|
(hasCallerConversationHistory || this.conversationMemory) &&
|
|
4884
|
-
streamMessageCount >
|
|
4885
|
-
(this.lastCompactionMessageCount.get(streamCompactionSessionId) ?? 0)) {
|
|
4842
|
+
streamMessageCount > (this.lastCompactionMessageCount.get(streamCompactionSessionId) ?? 0)) {
|
|
4886
4843
|
const compactor = new ContextCompactor({
|
|
4887
4844
|
provider: providerName,
|
|
4888
|
-
summarizationProvider: this.conversationMemoryConfig?.conversationMemory
|
|
4889
|
-
?.summarizationProvider,
|
|
4845
|
+
summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
|
|
4890
4846
|
summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
4891
4847
|
});
|
|
4892
4848
|
const compactionResult = await compactor.compact(conversationMessages, streamBudget.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, options.context?.requestId);
|
|
@@ -4956,6 +4912,9 @@ Current user's request: ${currentInput}`;
|
|
|
4956
4912
|
provider: providerName,
|
|
4957
4913
|
usage: streamResult.usage,
|
|
4958
4914
|
model: streamResult.model || options.model,
|
|
4915
|
+
finishReason: streamResult.finishReason,
|
|
4916
|
+
toolCalls: streamResult.toolCalls ?? [],
|
|
4917
|
+
toolResults: streamResult.toolResults ?? [],
|
|
4959
4918
|
analytics: streamResult.analytics,
|
|
4960
4919
|
};
|
|
4961
4920
|
}
|
|
@@ -5028,8 +4987,7 @@ Current user's request: ${currentInput}`;
|
|
|
5028
4987
|
parentSpanId: traceCtx?.parentSpanId,
|
|
5029
4988
|
});
|
|
5030
4989
|
failedSpan = SpanSerializer.endSpan(failedSpan, SpanStatus.ERROR);
|
|
5031
|
-
failedSpan.statusMessage =
|
|
5032
|
-
error instanceof Error ? error.message : String(error);
|
|
4990
|
+
failedSpan.statusMessage = error instanceof Error ? error.message : String(error);
|
|
5033
4991
|
failedSpan.durationMs = Date.now() - startTime;
|
|
5034
4992
|
this.metricsAggregator.recordSpan(failedSpan);
|
|
5035
4993
|
getMetricsAggregator().recordSpan(failedSpan);
|
|
@@ -5046,15 +5004,14 @@ Current user's request: ${currentInput}`;
|
|
|
5046
5004
|
model: options.model,
|
|
5047
5005
|
temperature: options.temperature,
|
|
5048
5006
|
maxTokens: options.maxTokens,
|
|
5007
|
+
conversationMessages: options.conversationMessages,
|
|
5049
5008
|
});
|
|
5050
5009
|
// Create a wrapper around the fallback stream that accumulates content
|
|
5051
5010
|
let fallbackAccumulatedContent = "";
|
|
5052
5011
|
const fallbackProcessedStream = (async function* (self) {
|
|
5053
5012
|
try {
|
|
5054
5013
|
for await (const chunk of fallbackStreamResult.stream) {
|
|
5055
|
-
if (chunk &&
|
|
5056
|
-
"content" in chunk &&
|
|
5057
|
-
typeof chunk.content === "string") {
|
|
5014
|
+
if (chunk && "content" in chunk && typeof chunk.content === "string") {
|
|
5058
5015
|
fallbackAccumulatedContent += chunk.content;
|
|
5059
5016
|
// Emit chunk event
|
|
5060
5017
|
self.emitter.emit("response:chunk", chunk.content);
|
|
@@ -5073,12 +5030,9 @@ Current user's request: ${currentInput}`;
|
|
|
5073
5030
|
}
|
|
5074
5031
|
// Store memory after fallback stream consumption is complete
|
|
5075
5032
|
// Guard: skip storing if fallback accumulated content is empty
|
|
5076
|
-
if (self.conversationMemory &&
|
|
5077
|
-
enhancedOptions?.context?.sessionId &&
|
|
5078
|
-
fallbackAccumulatedContent.trim()) {
|
|
5033
|
+
if (self.conversationMemory && enhancedOptions?.context?.sessionId && fallbackAccumulatedContent.trim()) {
|
|
5079
5034
|
const sessionId = enhancedOptions?.context?.sessionId;
|
|
5080
|
-
const userId = enhancedOptions?.context
|
|
5081
|
-
?.userId;
|
|
5035
|
+
const userId = enhancedOptions?.context?.userId;
|
|
5082
5036
|
let providerDetails;
|
|
5083
5037
|
if (options.model) {
|
|
5084
5038
|
providerDetails = {
|
|
@@ -5097,8 +5051,7 @@ Current user's request: ${currentInput}`;
|
|
|
5097
5051
|
providerDetails,
|
|
5098
5052
|
enableSummarization: enhancedOptions?.enableSummarization,
|
|
5099
5053
|
requestId: enhancedOptions?.context?.requestId ||
|
|
5100
|
-
options.context
|
|
5101
|
-
?.requestId,
|
|
5054
|
+
options.context?.requestId,
|
|
5102
5055
|
});
|
|
5103
5056
|
self.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": "fallback-stream" }, Date.now() - memStoreStart, SpanStatus.OK);
|
|
5104
5057
|
}
|
|
@@ -5497,7 +5450,8 @@ Current user's request: ${currentInput}`;
|
|
|
5497
5450
|
// (direct executeTool() or AI SDK generateText() tool calling).
|
|
5498
5451
|
if (options?.timeout !== undefined &&
|
|
5499
5452
|
options.timeout > 0 &&
|
|
5500
|
-
Number.isFinite(options.timeout)
|
|
5453
|
+
Number.isFinite(options.timeout) &&
|
|
5454
|
+
typeof convertedTool.execute === "function") {
|
|
5501
5455
|
const originalExecute = convertedTool.execute;
|
|
5502
5456
|
const toolTimeout = options.timeout;
|
|
5503
5457
|
const toolName = name;
|
|
@@ -5506,9 +5460,7 @@ Current user's request: ${currentInput}`;
|
|
|
5506
5460
|
// Compose with any parent abortSignal from ToolExecutionOptions
|
|
5507
5461
|
const execOptions = args[1];
|
|
5508
5462
|
const parentSignal = execOptions?.abortSignal;
|
|
5509
|
-
const composedSignal = parentSignal
|
|
5510
|
-
? AbortSignal.any([parentSignal, timeoutSignal])
|
|
5511
|
-
: timeoutSignal;
|
|
5463
|
+
const composedSignal = parentSignal ? AbortSignal.any([parentSignal, timeoutSignal]) : timeoutSignal;
|
|
5512
5464
|
// Replace the abortSignal in execution options
|
|
5513
5465
|
const augmentedContext = {
|
|
5514
5466
|
...execOptions,
|
|
@@ -5519,7 +5471,7 @@ Current user's request: ${currentInput}`;
|
|
|
5519
5471
|
new Promise((_, reject) => {
|
|
5520
5472
|
composedSignal.addEventListener("abort", () => {
|
|
5521
5473
|
if (timeoutSignal.aborted) {
|
|
5522
|
-
reject(
|
|
5474
|
+
reject(ErrorFactory.toolTimeout(toolName, toolTimeout));
|
|
5523
5475
|
}
|
|
5524
5476
|
else {
|
|
5525
5477
|
reject(new DOMException("The operation was aborted", "AbortError"));
|
|
@@ -5565,9 +5517,7 @@ Current user's request: ${currentInput}`;
|
|
|
5565
5517
|
* @returns Current context or undefined if not set
|
|
5566
5518
|
*/
|
|
5567
5519
|
getToolContext() {
|
|
5568
|
-
return this.toolExecutionContext
|
|
5569
|
-
? { ...this.toolExecutionContext }
|
|
5570
|
-
: undefined;
|
|
5520
|
+
return this.toolExecutionContext ? { ...this.toolExecutionContext } : undefined;
|
|
5571
5521
|
}
|
|
5572
5522
|
/**
|
|
5573
5523
|
* Clear the tool execution context
|
|
@@ -5671,8 +5621,7 @@ Current user's request: ${currentInput}`;
|
|
|
5671
5621
|
typeof this.conversationMemory.updateAgenticLoopReport !== "function") {
|
|
5672
5622
|
throw new ConversationMemoryError("updateAgenticLoopReport is only supported with Redis conversation memory.", "CONFIG_ERROR");
|
|
5673
5623
|
}
|
|
5674
|
-
await withTimeout(this
|
|
5675
|
-
.conversationMemory.updateAgenticLoopReport(sessionId, userId, report), 5000);
|
|
5624
|
+
await withTimeout(this.conversationMemory.updateAgenticLoopReport(sessionId, userId, report), 5000);
|
|
5676
5625
|
}
|
|
5677
5626
|
/**
|
|
5678
5627
|
* Get all registered custom tools
|
|
@@ -5690,14 +5639,10 @@ Current user's request: ${currentInput}`;
|
|
|
5690
5639
|
description: tool.description,
|
|
5691
5640
|
hasParameters: !!tool.parameters,
|
|
5692
5641
|
parametersType: typeof tool.parameters,
|
|
5693
|
-
parametersKeys: tool.parameters && typeof tool.parameters === "object"
|
|
5694
|
-
? Object.keys(tool.parameters)
|
|
5695
|
-
: "NOT_OBJECT",
|
|
5642
|
+
parametersKeys: tool.parameters && typeof tool.parameters === "object" ? Object.keys(tool.parameters) : "NOT_OBJECT",
|
|
5696
5643
|
hasInputSchema: !!tool.inputSchema,
|
|
5697
5644
|
inputSchemaType: typeof tool.inputSchema,
|
|
5698
|
-
inputSchemaKeys: tool.inputSchema && typeof tool.inputSchema === "object"
|
|
5699
|
-
? Object.keys(tool.inputSchema)
|
|
5700
|
-
: "NOT_OBJECT",
|
|
5645
|
+
inputSchemaKeys: tool.inputSchema && typeof tool.inputSchema === "object" ? Object.keys(tool.inputSchema) : "NOT_OBJECT",
|
|
5701
5646
|
hasEffectiveSchema: !!effectiveSchema,
|
|
5702
5647
|
effectiveSchemaType: typeof effectiveSchema,
|
|
5703
5648
|
effectiveSchemaHasProperties: !!effectiveSchema?.properties,
|
|
@@ -5718,18 +5663,14 @@ Current user's request: ${currentInput}`;
|
|
|
5718
5663
|
execute: async (params, context) => {
|
|
5719
5664
|
// CONTEXT MERGING: Combine all available contexts for maximum information
|
|
5720
5665
|
const storedContext = this.toolExecutionContext || {};
|
|
5721
|
-
const runtimeContext = context && isNonNullObject(context)
|
|
5722
|
-
? context
|
|
5723
|
-
: {};
|
|
5666
|
+
const runtimeContext = context && isNonNullObject(context) ? context : {};
|
|
5724
5667
|
// Merge contexts with runtime context taking precedence
|
|
5725
5668
|
// This ensures we have the richest possible context for tool execution
|
|
5726
5669
|
const executionContext = {
|
|
5727
5670
|
...storedContext, // Base context from setToolContext (session, tokens, etc.)
|
|
5728
5671
|
...runtimeContext, // Runtime context from AI model (if any)
|
|
5729
5672
|
// Ensure we always have at least a sessionId for tracing
|
|
5730
|
-
sessionId: runtimeContext.sessionId ||
|
|
5731
|
-
storedContext.sessionId ||
|
|
5732
|
-
`fallback-${Date.now()}`,
|
|
5673
|
+
sessionId: runtimeContext.sessionId || storedContext.sessionId || `fallback-${Date.now()}`,
|
|
5733
5674
|
};
|
|
5734
5675
|
// Enhanced logging for context debugging
|
|
5735
5676
|
logger.debug("Tool execution context merged", {
|
|
@@ -5737,8 +5678,7 @@ Current user's request: ${currentInput}`;
|
|
|
5737
5678
|
storedContextKeys: Object.keys(storedContext),
|
|
5738
5679
|
runtimeContextKeys: Object.keys(runtimeContext),
|
|
5739
5680
|
finalContextKeys: Object.keys(executionContext),
|
|
5740
|
-
hasJuspayToken: !!executionContext
|
|
5741
|
-
.juspayToken,
|
|
5681
|
+
hasJuspayToken: !!executionContext.juspayToken,
|
|
5742
5682
|
hasShopId: !!executionContext.shopId,
|
|
5743
5683
|
sessionId: executionContext.sessionId,
|
|
5744
5684
|
});
|
|
@@ -5766,9 +5706,7 @@ Current user's request: ${currentInput}`;
|
|
|
5766
5706
|
toolMap.set(toolName, {
|
|
5767
5707
|
name: toolName,
|
|
5768
5708
|
description: toolDef.description || `File tool: ${toolName}`,
|
|
5769
|
-
inputSchema: typeof toolParams === "object" && toolParams !== null
|
|
5770
|
-
? toolParams
|
|
5771
|
-
: { type: "object", properties: {} },
|
|
5709
|
+
inputSchema: typeof toolParams === "object" && toolParams !== null ? toolParams : { type: "object", properties: {} },
|
|
5772
5710
|
execute: async (params) => {
|
|
5773
5711
|
return await toolDef.execute(params, {
|
|
5774
5712
|
toolCallId: `file-tool-${Date.now()}`,
|
|
@@ -5879,17 +5817,9 @@ Current user's request: ${currentInput}`;
|
|
|
5879
5817
|
// Determine tool type for span attributes
|
|
5880
5818
|
const externalTools = this.externalServerManager.getAllTools();
|
|
5881
5819
|
const externalTool = externalTools.find((tool) => tool.name === toolName);
|
|
5882
|
-
const toolType = externalTool
|
|
5883
|
-
? "mcp"
|
|
5884
|
-
: this.getCustomTools().has(toolName)
|
|
5885
|
-
? "custom"
|
|
5886
|
-
: "external";
|
|
5820
|
+
const toolType = externalTool ? "mcp" : this.getCustomTools().has(toolName) ? "custom" : "external";
|
|
5887
5821
|
// Compute truncated input size for the span
|
|
5888
|
-
const inputStr = typeof params === "string"
|
|
5889
|
-
? params
|
|
5890
|
-
: params
|
|
5891
|
-
? JSON.stringify(params)
|
|
5892
|
-
: "";
|
|
5822
|
+
const inputStr = typeof params === "string" ? params : params ? JSON.stringify(params) : "";
|
|
5893
5823
|
const inputSize = inputStr.length;
|
|
5894
5824
|
const truncatedInput = inputStr.length > 2048 ? inputStr.substring(0, 2048) : inputStr;
|
|
5895
5825
|
return tracers.mcp.startActiveSpan("neurolink.tool.execute", {
|
|
@@ -5904,9 +5834,7 @@ Current user's request: ${currentInput}`;
|
|
|
5904
5834
|
// Debug: Log tool execution attempt
|
|
5905
5835
|
logger.debug(`[${functionTag}] Tool execution requested:`, {
|
|
5906
5836
|
toolName,
|
|
5907
|
-
params: isNonNullObject(params)
|
|
5908
|
-
? transformParamsForLogging(params)
|
|
5909
|
-
: params,
|
|
5837
|
+
params: isNonNullObject(params) ? transformParamsForLogging(params) : params,
|
|
5910
5838
|
hasExternalManager: !!this.externalServerManager,
|
|
5911
5839
|
});
|
|
5912
5840
|
// 🔧 PARAMETER TRACE: Log tool execution details for debugging
|
|
@@ -5917,15 +5845,9 @@ Current user's request: ${currentInput}`;
|
|
|
5917
5845
|
type: typeof params,
|
|
5918
5846
|
isNull: params === null,
|
|
5919
5847
|
isUndefined: params === undefined,
|
|
5920
|
-
isEmpty: params &&
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
keys: params && typeof params === "object"
|
|
5924
|
-
? Object.keys(params)
|
|
5925
|
-
: "NOT_OBJECT",
|
|
5926
|
-
keysLength: params && typeof params === "object"
|
|
5927
|
-
? Object.keys(params).length
|
|
5928
|
-
: 0,
|
|
5848
|
+
isEmpty: params && typeof params === "object" && Object.keys(params).length === 0,
|
|
5849
|
+
keys: params && typeof params === "object" ? Object.keys(params) : "NOT_OBJECT",
|
|
5850
|
+
keysLength: params && typeof params === "object" ? Object.keys(params).length : 0,
|
|
5929
5851
|
},
|
|
5930
5852
|
isTargetTool: toolName === "juspay-analytics_SuccessRateSRByTime",
|
|
5931
5853
|
options,
|
|
@@ -5945,12 +5867,8 @@ Current user's request: ${currentInput}`;
|
|
|
5945
5867
|
const registeredTimeout = toolInfo?.tool?.timeoutMs;
|
|
5946
5868
|
const registeredMaxRetries = toolInfo?.tool?.maxRetries;
|
|
5947
5869
|
const finalOptions = {
|
|
5948
|
-
timeout: options?.timeout ??
|
|
5949
|
-
|
|
5950
|
-
TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS,
|
|
5951
|
-
maxRetries: options?.maxRetries ??
|
|
5952
|
-
registeredMaxRetries ??
|
|
5953
|
-
RETRY_ATTEMPTS.DEFAULT,
|
|
5870
|
+
timeout: options?.timeout ?? registeredTimeout ?? TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS,
|
|
5871
|
+
maxRetries: options?.maxRetries ?? registeredMaxRetries ?? RETRY_ATTEMPTS.DEFAULT,
|
|
5954
5872
|
retryDelayMs: options?.retryDelayMs || RETRY_DELAYS.BASE_MS,
|
|
5955
5873
|
authContext: options?.authContext,
|
|
5956
5874
|
disableToolCache: options?.disableToolCache,
|
|
@@ -6013,9 +5931,7 @@ Current user's request: ${currentInput}`;
|
|
|
6013
5931
|
metrics.successfulExecutions++;
|
|
6014
5932
|
metrics.lastExecutionTime = executionTime;
|
|
6015
5933
|
metrics.averageExecutionTime =
|
|
6016
|
-
(metrics.averageExecutionTime *
|
|
6017
|
-
(metrics.successfulExecutions - 1) +
|
|
6018
|
-
executionTime) /
|
|
5934
|
+
(metrics.averageExecutionTime * (metrics.successfulExecutions - 1) + executionTime) /
|
|
6019
5935
|
metrics.successfulExecutions;
|
|
6020
5936
|
}
|
|
6021
5937
|
// Track memory usage
|
|
@@ -6037,15 +5953,9 @@ Current user's request: ${currentInput}`;
|
|
|
6037
5953
|
// Set span success attributes
|
|
6038
5954
|
// Check if result has isError flag (MCP tool error result)
|
|
6039
5955
|
// Also detect toolRegistry-wrapped errors that return { success: false }
|
|
6040
|
-
const resultObj = result && typeof result === "object"
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
const isToolError = (resultObj &&
|
|
6044
|
-
"isError" in resultObj &&
|
|
6045
|
-
resultObj.isError === true) ||
|
|
6046
|
-
(resultObj &&
|
|
6047
|
-
"success" in resultObj &&
|
|
6048
|
-
resultObj.success === false);
|
|
5956
|
+
const resultObj = result && typeof result === "object" ? result : undefined;
|
|
5957
|
+
const isToolError = (resultObj && "isError" in resultObj && resultObj.isError === true) ||
|
|
5958
|
+
(resultObj && "success" in resultObj && resultObj.success === false);
|
|
6049
5959
|
// NL-001: Count isError:true results as circuit breaker failures
|
|
6050
5960
|
// This ensures tools that return error results (not just thrown errors) are tracked
|
|
6051
5961
|
// TODO(NL-009): This records a failure AFTER the circuit breaker already recorded
|
|
@@ -6076,10 +5986,7 @@ Current user's request: ${currentInput}`;
|
|
|
6076
5986
|
const errorText = contentArr
|
|
6077
5987
|
?.filter((c) => c.type === "text" && c.text)
|
|
6078
5988
|
.map((c) => c.text)
|
|
6079
|
-
.join(" ") ||
|
|
6080
|
-
(typeof resultObj.error === "string"
|
|
6081
|
-
? resultObj.error
|
|
6082
|
-
: "Unknown error");
|
|
5989
|
+
.join(" ") || (typeof resultObj.error === "string" ? resultObj.error : "Unknown error");
|
|
6083
5990
|
const errorCategory = classifyMcpErrorMessage(errorText);
|
|
6084
5991
|
const prefix = `[TOOL_ERROR: ${toolName} failed (${errorCategory})] `;
|
|
6085
5992
|
// NL-002: Clone content array to avoid mutating shared objects, then prefix error
|
|
@@ -6108,17 +6015,14 @@ Current user's request: ${currentInput}`;
|
|
|
6108
6015
|
// which was incorrectly included as a success
|
|
6109
6016
|
if (prevSuccessful > 1) {
|
|
6110
6017
|
metrics.averageExecutionTime =
|
|
6111
|
-
(metrics.averageExecutionTime * prevSuccessful -
|
|
6112
|
-
executionTime) /
|
|
6113
|
-
(prevSuccessful - 1);
|
|
6018
|
+
(metrics.averageExecutionTime * prevSuccessful - executionTime) / (prevSuccessful - 1);
|
|
6114
6019
|
}
|
|
6115
6020
|
else {
|
|
6116
6021
|
// No remaining successful executions, reset to 0
|
|
6117
6022
|
metrics.averageExecutionTime = 0;
|
|
6118
6023
|
}
|
|
6119
6024
|
const mappedCategory = mcpCategoryToErrorCategory(errorCategory);
|
|
6120
|
-
metrics.errorCategories[mappedCategory] =
|
|
6121
|
-
(metrics.errorCategories[mappedCategory] || 0) + 1;
|
|
6025
|
+
metrics.errorCategories[mappedCategory] = (metrics.errorCategories[mappedCategory] || 0) + 1;
|
|
6122
6026
|
}
|
|
6123
6027
|
}
|
|
6124
6028
|
// Emit tool end event AFTER isError check so success flag is correct
|
|
@@ -6147,8 +6051,7 @@ Current user's request: ${currentInput}`;
|
|
|
6147
6051
|
});
|
|
6148
6052
|
if (metrics) {
|
|
6149
6053
|
const category = ErrorCategory.EXECUTION;
|
|
6150
|
-
metrics.errorCategories[category] =
|
|
6151
|
-
(metrics.errorCategories[category] || 0) + 1;
|
|
6054
|
+
metrics.errorCategories[category] = (metrics.errorCategories[category] || 0) + 1;
|
|
6152
6055
|
}
|
|
6153
6056
|
// Emit tool end event for circuit breaker open
|
|
6154
6057
|
this.emitToolEndEvent(toolName, executionStartTime, false, undefined);
|
|
@@ -6192,12 +6095,10 @@ Current user's request: ${currentInput}`;
|
|
|
6192
6095
|
const availableTools = await this.getAllAvailableTools();
|
|
6193
6096
|
structuredError = ErrorFactory.toolNotFound(toolName, extractToolNames(availableTools.map((t) => ({ name: t.name }))));
|
|
6194
6097
|
}
|
|
6195
|
-
else if (error.message.includes("validation") ||
|
|
6196
|
-
error.message.includes("parameter")) {
|
|
6098
|
+
else if (error.message.includes("validation") || error.message.includes("parameter")) {
|
|
6197
6099
|
structuredError = ErrorFactory.invalidParameters(toolName, error, params);
|
|
6198
6100
|
}
|
|
6199
|
-
else if (error.message.includes("network") ||
|
|
6200
|
-
error.message.includes("connection")) {
|
|
6101
|
+
else if (error.message.includes("network") || error.message.includes("connection")) {
|
|
6201
6102
|
structuredError = ErrorFactory.networkError(toolName, error);
|
|
6202
6103
|
}
|
|
6203
6104
|
else {
|
|
@@ -6209,8 +6110,7 @@ Current user's request: ${currentInput}`;
|
|
|
6209
6110
|
}
|
|
6210
6111
|
if (metrics) {
|
|
6211
6112
|
const category = structuredError.category || ErrorCategory.EXECUTION;
|
|
6212
|
-
metrics.errorCategories[category] =
|
|
6213
|
-
(metrics.errorCategories[category] || 0) + 1;
|
|
6113
|
+
metrics.errorCategories[category] = (metrics.errorCategories[category] || 0) + 1;
|
|
6214
6114
|
}
|
|
6215
6115
|
// Emit tool end event BEFORE the error event.
|
|
6216
6116
|
// Node.js EventEmitter throws on unhandled 'error' events,
|
|
@@ -6247,9 +6147,7 @@ Current user's request: ${currentInput}`;
|
|
|
6247
6147
|
catch (outerError) {
|
|
6248
6148
|
// If the error was not already recorded on the span (from inner catch), record it
|
|
6249
6149
|
if (!(outerError instanceof NeuroLinkError)) {
|
|
6250
|
-
const errMsg = outerError instanceof Error
|
|
6251
|
-
? outerError.message
|
|
6252
|
-
: String(outerError);
|
|
6150
|
+
const errMsg = outerError instanceof Error ? outerError.message : String(outerError);
|
|
6253
6151
|
toolSpan.recordException(outerError instanceof Error ? outerError : new Error(errMsg));
|
|
6254
6152
|
toolSpan.setStatus({ code: SpanStatusCode.ERROR, message: errMsg });
|
|
6255
6153
|
}
|
|
@@ -6275,9 +6173,17 @@ Current user's request: ${currentInput}`;
|
|
|
6275
6173
|
!options.disableToolCache &&
|
|
6276
6174
|
!this._disableToolCacheForCurrentRequest &&
|
|
6277
6175
|
!toolAnnotations?.destructiveHint;
|
|
6176
|
+
const toolResultCache = this.mcpToolResultCache;
|
|
6278
6177
|
// === MCP ENHANCEMENT: Cache check (before execution) ===
|
|
6279
|
-
|
|
6280
|
-
|
|
6178
|
+
// Scope cache key by auth context to prevent cross-user cache leaks
|
|
6179
|
+
const cacheParams = options.authContext || this.toolExecutionContext
|
|
6180
|
+
? {
|
|
6181
|
+
__args: params,
|
|
6182
|
+
__ctx: options.authContext ?? this.toolExecutionContext,
|
|
6183
|
+
}
|
|
6184
|
+
: params;
|
|
6185
|
+
if (isCacheEnabled && toolResultCache) {
|
|
6186
|
+
const cached = toolResultCache.getCachedResult(toolName, cacheParams);
|
|
6281
6187
|
if (cached !== undefined) {
|
|
6282
6188
|
logger.debug(`[${functionTag}] Cache HIT for tool: ${toolName}`);
|
|
6283
6189
|
return cached;
|
|
@@ -6328,9 +6234,7 @@ Current user's request: ${currentInput}`;
|
|
|
6328
6234
|
inputSchema: {},
|
|
6329
6235
|
};
|
|
6330
6236
|
const decision = this.mcpToolRouter.route(mcpTool);
|
|
6331
|
-
externalTool =
|
|
6332
|
-
matchingTools.find((t) => t.serverId === decision.serverId) ||
|
|
6333
|
-
matchingTools[0];
|
|
6237
|
+
externalTool = matchingTools.find((t) => t.serverId === decision.serverId) || matchingTools[0];
|
|
6334
6238
|
logger.debug(`[${functionTag}] Router selected server: ${decision.serverId}`, {
|
|
6335
6239
|
strategy: decision.strategy,
|
|
6336
6240
|
confidence: decision.confidence,
|
|
@@ -6386,10 +6290,7 @@ Current user's request: ${currentInput}`;
|
|
|
6386
6290
|
});
|
|
6387
6291
|
const result = (await this.toolRegistry.executeTool(toolName, params, context));
|
|
6388
6292
|
// Check if result indicates a failure and emit error event
|
|
6389
|
-
if (result &&
|
|
6390
|
-
typeof result === "object" &&
|
|
6391
|
-
"success" in result &&
|
|
6392
|
-
result.success === false) {
|
|
6293
|
+
if (result && typeof result === "object" && "success" in result && result.success === false) {
|
|
6393
6294
|
const errorMessage = result.error || "Tool execution failed";
|
|
6394
6295
|
const errorToEmit = new Error(errorMessage);
|
|
6395
6296
|
this.emitter.emit("error", errorToEmit);
|
|
@@ -6411,8 +6312,8 @@ Current user's request: ${currentInput}`;
|
|
|
6411
6312
|
try {
|
|
6412
6313
|
const result = await executeWithMiddleware(executeCore);
|
|
6413
6314
|
// === MCP ENHANCEMENT: Cache store (after successful execution) ===
|
|
6414
|
-
if (isCacheEnabled && result !== undefined) {
|
|
6415
|
-
|
|
6315
|
+
if (isCacheEnabled && toolResultCache && result !== undefined) {
|
|
6316
|
+
toolResultCache.cacheResult(toolName, cacheParams, result);
|
|
6416
6317
|
logger.debug(`[${functionTag}] Cached result for tool: ${toolName}`);
|
|
6417
6318
|
}
|
|
6418
6319
|
return result;
|
|
@@ -6427,16 +6328,13 @@ Current user's request: ${currentInput}`;
|
|
|
6427
6328
|
execute: async () => ({}),
|
|
6428
6329
|
}
|
|
6429
6330
|
: undefined;
|
|
6430
|
-
if (toolStubForRetry &&
|
|
6431
|
-
isSafeToRetry(toolStubForRetry) &&
|
|
6432
|
-
error instanceof Error &&
|
|
6433
|
-
isRetriableError(error)) {
|
|
6331
|
+
if (toolStubForRetry && isSafeToRetry(toolStubForRetry) && error instanceof Error && isRetriableError(error)) {
|
|
6434
6332
|
logger.debug(`[${functionTag}] Tool ${toolName} is safe to retry, attempting once more`);
|
|
6435
6333
|
try {
|
|
6436
6334
|
const retryResult = await executeWithMiddleware(executeCore);
|
|
6437
6335
|
// Cache the retry result
|
|
6438
|
-
if (isCacheEnabled && retryResult !== undefined) {
|
|
6439
|
-
|
|
6336
|
+
if (isCacheEnabled && toolResultCache && retryResult !== undefined) {
|
|
6337
|
+
toolResultCache.cacheResult(toolName, cacheParams, retryResult);
|
|
6440
6338
|
}
|
|
6441
6339
|
return retryResult;
|
|
6442
6340
|
}
|
|
@@ -6475,8 +6373,7 @@ Current user's request: ${currentInput}`;
|
|
|
6475
6373
|
}
|
|
6476
6374
|
async getAllAvailableTools() {
|
|
6477
6375
|
// Return from cache if available and not stale
|
|
6478
|
-
if (this.toolCache &&
|
|
6479
|
-
Date.now() - this.toolCache.timestamp < this.toolCacheDuration) {
|
|
6376
|
+
if (this.toolCache && Date.now() - this.toolCache.timestamp < this.toolCacheDuration) {
|
|
6480
6377
|
logger.debug("Returning available tools from cache");
|
|
6481
6378
|
return this.toolCache.tools;
|
|
6482
6379
|
}
|
|
@@ -6557,9 +6454,7 @@ Current user's request: ${currentInput}`;
|
|
|
6557
6454
|
if (!allTools.has(tool.name)) {
|
|
6558
6455
|
const optimizedTool = optimizeToolForCollection(tool, {
|
|
6559
6456
|
category: detectCategory({
|
|
6560
|
-
existingCategory: typeof tool.metadata?.category === "string"
|
|
6561
|
-
? tool.metadata.category
|
|
6562
|
-
: undefined,
|
|
6457
|
+
existingCategory: typeof tool.metadata?.category === "string" ? tool.metadata.category : undefined,
|
|
6563
6458
|
isExternal: true,
|
|
6564
6459
|
serverId: tool.serverId,
|
|
6565
6460
|
}),
|
|
@@ -6715,9 +6610,7 @@ Current user's request: ${currentInput}`;
|
|
|
6715
6610
|
status: "failed",
|
|
6716
6611
|
configured: false,
|
|
6717
6612
|
authenticated: false,
|
|
6718
|
-
error: error instanceof Error
|
|
6719
|
-
? error.message
|
|
6720
|
-
: "Ollama service not running",
|
|
6613
|
+
error: error instanceof Error ? error.message : "Ollama service not running",
|
|
6721
6614
|
responseTime: Date.now() - startTime,
|
|
6722
6615
|
};
|
|
6723
6616
|
}
|
|
@@ -6840,9 +6733,7 @@ Current user's request: ${currentInput}`;
|
|
|
6840
6733
|
inMemoryServerInfos.length +
|
|
6841
6734
|
builtInServerInfos.length +
|
|
6842
6735
|
autoDiscoveredServerInfos.length;
|
|
6843
|
-
const availableServers = externalStats.connectedServers +
|
|
6844
|
-
inMemoryServerInfos.length +
|
|
6845
|
-
builtInServerInfos.length; // in-memory and built-in always available
|
|
6736
|
+
const availableServers = externalStats.connectedServers + inMemoryServerInfos.length + builtInServerInfos.length; // in-memory and built-in always available
|
|
6846
6737
|
const totalTools = allTools.length + externalStats.totalTools;
|
|
6847
6738
|
return {
|
|
6848
6739
|
mcpInitialized: this.mcpInitialized,
|
|
@@ -6911,8 +6802,7 @@ Current user's request: ${currentInput}`;
|
|
|
6911
6802
|
// Test external MCP servers
|
|
6912
6803
|
const externalServer = this.externalServerManager.getServer(serverId);
|
|
6913
6804
|
if (externalServer) {
|
|
6914
|
-
return
|
|
6915
|
-
externalServer.client !== null);
|
|
6805
|
+
return externalServer.status === "connected" && externalServer.client !== null;
|
|
6916
6806
|
}
|
|
6917
6807
|
return false;
|
|
6918
6808
|
}
|
|
@@ -7032,9 +6922,7 @@ Current user's request: ${currentInput}`;
|
|
|
7032
6922
|
metrics[toolName] = {
|
|
7033
6923
|
...toolMetrics,
|
|
7034
6924
|
errorCategories: { ...toolMetrics.errorCategories },
|
|
7035
|
-
successRate: toolMetrics.totalExecutions > 0
|
|
7036
|
-
? toolMetrics.successfulExecutions / toolMetrics.totalExecutions
|
|
7037
|
-
: 0,
|
|
6925
|
+
successRate: toolMetrics.totalExecutions > 0 ? toolMetrics.successfulExecutions / toolMetrics.totalExecutions : 0,
|
|
7038
6926
|
};
|
|
7039
6927
|
}
|
|
7040
6928
|
return metrics;
|
|
@@ -7054,7 +6942,7 @@ Current user's request: ${currentInput}`;
|
|
|
7054
6942
|
*/
|
|
7055
6943
|
getToolCircuitBreakerStatus() {
|
|
7056
6944
|
const status = {};
|
|
7057
|
-
for (const [toolName, circuitBreaker
|
|
6945
|
+
for (const [toolName, circuitBreaker] of this.toolCircuitBreakers.entries()) {
|
|
7058
6946
|
status[toolName] = {
|
|
7059
6947
|
state: circuitBreaker.getState(),
|
|
7060
6948
|
failureCount: circuitBreaker.getFailureCount(),
|
|
@@ -7107,8 +6995,7 @@ Current user's request: ${currentInput}`;
|
|
|
7107
6995
|
? metrics.successfulExecutions / metrics.totalExecutions
|
|
7108
6996
|
: 0
|
|
7109
6997
|
: 0;
|
|
7110
|
-
const isHealthy = (!circuitBreaker || circuitBreaker.getState() === "closed") &&
|
|
7111
|
-
successRate >= 0.8;
|
|
6998
|
+
const isHealthy = (!circuitBreaker || circuitBreaker.getState() === "closed") && successRate >= 0.8;
|
|
7112
6999
|
if (isHealthy) {
|
|
7113
7000
|
healthyCount++;
|
|
7114
7001
|
}
|
|
@@ -7149,9 +7036,7 @@ Current user's request: ${currentInput}`;
|
|
|
7149
7036
|
successRate,
|
|
7150
7037
|
averageExecutionTime: metrics?.averageExecutionTime || 0,
|
|
7151
7038
|
lastExecutionTime: metrics?.lastExecutionTime || 0,
|
|
7152
|
-
errorCategories: metrics?.errorCategories
|
|
7153
|
-
? { ...metrics.errorCategories }
|
|
7154
|
-
: {},
|
|
7039
|
+
errorCategories: metrics?.errorCategories ? { ...metrics.errorCategories } : {},
|
|
7155
7040
|
},
|
|
7156
7041
|
circuitBreaker: {
|
|
7157
7042
|
state: circuitBreaker?.getState() || "closed",
|
|
@@ -7303,8 +7188,7 @@ Current user's request: ${currentInput}`;
|
|
|
7303
7188
|
*/
|
|
7304
7189
|
async storeToolExecutions(sessionId, userId, toolCalls, toolResults, currentTime) {
|
|
7305
7190
|
// Check if tools are not empty
|
|
7306
|
-
const hasToolData = (toolCalls && toolCalls.length > 0) ||
|
|
7307
|
-
(toolResults && toolResults.length > 0);
|
|
7191
|
+
const hasToolData = (toolCalls && toolCalls.length > 0) || (toolResults && toolResults.length > 0);
|
|
7308
7192
|
if (!hasToolData) {
|
|
7309
7193
|
logger.debug("Tool execution storage skipped", {
|
|
7310
7194
|
hasToolData,
|
|
@@ -7314,8 +7198,7 @@ Current user's request: ${currentInput}`;
|
|
|
7314
7198
|
return;
|
|
7315
7199
|
}
|
|
7316
7200
|
// Type guard to ensure it's Redis conversation memory manager
|
|
7317
|
-
const redisMemory = this
|
|
7318
|
-
.conversationMemory;
|
|
7201
|
+
const redisMemory = this.conversationMemory;
|
|
7319
7202
|
try {
|
|
7320
7203
|
await redisMemory.storeToolExecution(sessionId, userId, toolCalls, toolResults, currentTime);
|
|
7321
7204
|
}
|
|
@@ -7334,9 +7217,7 @@ Current user's request: ${currentInput}`;
|
|
|
7334
7217
|
*/
|
|
7335
7218
|
isToolExecutionStorageAvailable() {
|
|
7336
7219
|
const isRedisStorage = process.env.STORAGE_TYPE === "redis";
|
|
7337
|
-
const hasRedisConversationMemory = this.conversationMemory &&
|
|
7338
|
-
this.conversationMemory.constructor.name ===
|
|
7339
|
-
"RedisConversationMemoryManager";
|
|
7220
|
+
const hasRedisConversationMemory = this.conversationMemory && this.conversationMemory.constructor.name === "RedisConversationMemoryManager";
|
|
7340
7221
|
return !!(isRedisStorage && hasRedisConversationMemory);
|
|
7341
7222
|
}
|
|
7342
7223
|
/**
|
|
@@ -7855,8 +7736,7 @@ Current user's request: ${currentInput}`;
|
|
|
7855
7736
|
return null;
|
|
7856
7737
|
}
|
|
7857
7738
|
// Check for explicit annotations set on the tool first
|
|
7858
|
-
const explicitAnnotations = toolInfo.tool
|
|
7859
|
-
.annotations;
|
|
7739
|
+
const explicitAnnotations = toolInfo.tool.annotations;
|
|
7860
7740
|
// Infer annotations from the tool name/description as fallback
|
|
7861
7741
|
const inferredAnnotations = inferAnnotations({
|
|
7862
7742
|
name: toolInfo.tool.name,
|
|
@@ -7888,9 +7768,7 @@ Current user's request: ${currentInput}`;
|
|
|
7888
7768
|
const result = await this.externalServerManager.executeTool(tool.serverId, tool.name, params, { timeout: 30000 });
|
|
7889
7769
|
mcpLogger.debug(`[NeuroLink] External MCP tool execution result: ${tool.name}`, {
|
|
7890
7770
|
success: !!result,
|
|
7891
|
-
hasData: !!(result &&
|
|
7892
|
-
typeof result === "object" &&
|
|
7893
|
-
"content" in result),
|
|
7771
|
+
hasData: !!(result && typeof result === "object" && "content" in result),
|
|
7894
7772
|
});
|
|
7895
7773
|
return result;
|
|
7896
7774
|
}
|
|
@@ -8306,9 +8184,7 @@ Current user's request: ${currentInput}`;
|
|
|
8306
8184
|
logger.debug("[NeuroLink] OpenTelemetry shutdown successfully");
|
|
8307
8185
|
}
|
|
8308
8186
|
catch (error) {
|
|
8309
|
-
const err = error instanceof Error
|
|
8310
|
-
? error
|
|
8311
|
-
: new Error(`OpenTelemetry shutdown error: ${String(error)}`);
|
|
8187
|
+
const err = error instanceof Error ? error : new Error(`OpenTelemetry shutdown error: ${String(error)}`);
|
|
8312
8188
|
cleanupErrors.push(err);
|
|
8313
8189
|
logger.warn("[NeuroLink] Error shutting down OpenTelemetry:", error);
|
|
8314
8190
|
}
|
|
@@ -8320,9 +8196,7 @@ Current user's request: ${currentInput}`;
|
|
|
8320
8196
|
logger.debug("[NeuroLink] External MCP servers shutdown successfully");
|
|
8321
8197
|
}
|
|
8322
8198
|
catch (error) {
|
|
8323
|
-
const err = error instanceof Error
|
|
8324
|
-
? error
|
|
8325
|
-
: new Error(`External server shutdown error: ${String(error)}`);
|
|
8199
|
+
const err = error instanceof Error ? error : new Error(`External server shutdown error: ${String(error)}`);
|
|
8326
8200
|
cleanupErrors.push(err);
|
|
8327
8201
|
logger.warn("[NeuroLink] Error shutting down external MCP servers:", error);
|
|
8328
8202
|
}
|
|
@@ -8336,9 +8210,7 @@ Current user's request: ${currentInput}`;
|
|
|
8336
8210
|
logger.debug("[NeuroLink] Event listeners removed successfully");
|
|
8337
8211
|
}
|
|
8338
8212
|
catch (error) {
|
|
8339
|
-
const err = error instanceof Error
|
|
8340
|
-
? error
|
|
8341
|
-
: new Error(`Event emitter cleanup error: ${String(error)}`);
|
|
8213
|
+
const err = error instanceof Error ? error : new Error(`Event emitter cleanup error: ${String(error)}`);
|
|
8342
8214
|
cleanupErrors.push(err);
|
|
8343
8215
|
logger.warn("[NeuroLink] Error removing event listeners:", error);
|
|
8344
8216
|
}
|
|
@@ -8351,9 +8223,7 @@ Current user's request: ${currentInput}`;
|
|
|
8351
8223
|
logger.debug("[NeuroLink] Circuit breakers cleared successfully");
|
|
8352
8224
|
}
|
|
8353
8225
|
catch (error) {
|
|
8354
|
-
const err = error instanceof Error
|
|
8355
|
-
? error
|
|
8356
|
-
: new Error(`Circuit breaker cleanup error: ${String(error)}`);
|
|
8226
|
+
const err = error instanceof Error ? error : new Error(`Circuit breaker cleanup error: ${String(error)}`);
|
|
8357
8227
|
cleanupErrors.push(err);
|
|
8358
8228
|
logger.warn("[NeuroLink] Error clearing circuit breakers:", error);
|
|
8359
8229
|
}
|
|
@@ -8390,12 +8260,23 @@ Current user's request: ${currentInput}`;
|
|
|
8390
8260
|
logger.debug("[NeuroLink] Maps and caches cleared successfully");
|
|
8391
8261
|
}
|
|
8392
8262
|
catch (error) {
|
|
8393
|
-
const err = error instanceof Error
|
|
8394
|
-
? error
|
|
8395
|
-
: new Error(`Cache cleanup error: ${String(error)}`);
|
|
8263
|
+
const err = error instanceof Error ? error : new Error(`Cache cleanup error: ${String(error)}`);
|
|
8396
8264
|
cleanupErrors.push(err);
|
|
8397
8265
|
logger.warn("[NeuroLink] Error clearing caches:", error);
|
|
8398
8266
|
}
|
|
8267
|
+
// 5b. Shutdown TaskManager
|
|
8268
|
+
if (this._taskManager) {
|
|
8269
|
+
try {
|
|
8270
|
+
logger.debug("[NeuroLink] Shutting down TaskManager...");
|
|
8271
|
+
await withTimeout(this._taskManager.shutdown(), 5000, new Error("TaskManager shutdown timed out"));
|
|
8272
|
+
}
|
|
8273
|
+
catch (error) {
|
|
8274
|
+
logger.warn("[NeuroLink] TaskManager shutdown error:", error);
|
|
8275
|
+
}
|
|
8276
|
+
finally {
|
|
8277
|
+
this._taskManager = undefined;
|
|
8278
|
+
}
|
|
8279
|
+
}
|
|
8399
8280
|
// 6. Reset initialization flags
|
|
8400
8281
|
try {
|
|
8401
8282
|
logger.debug("[NeuroLink] Resetting initialization state...");
|
|
@@ -8405,9 +8286,7 @@ Current user's request: ${currentInput}`;
|
|
|
8405
8286
|
logger.debug("[NeuroLink] Initialization state reset successfully");
|
|
8406
8287
|
}
|
|
8407
8288
|
catch (error) {
|
|
8408
|
-
const err = error instanceof Error
|
|
8409
|
-
? error
|
|
8410
|
-
: new Error(`State reset error: ${String(error)}`);
|
|
8289
|
+
const err = error instanceof Error ? error : new Error(`State reset error: ${String(error)}`);
|
|
8411
8290
|
cleanupErrors.push(err);
|
|
8412
8291
|
logger.warn("[NeuroLink] Error resetting state:", error);
|
|
8413
8292
|
}
|
|
@@ -8451,11 +8330,8 @@ Current user's request: ${currentInput}`;
|
|
|
8451
8330
|
}
|
|
8452
8331
|
const compactor = new ContextCompactor({
|
|
8453
8332
|
...config,
|
|
8454
|
-
summarizationProvider: config?.summarizationProvider ??
|
|
8455
|
-
|
|
8456
|
-
?.summarizationProvider,
|
|
8457
|
-
summarizationModel: config?.summarizationModel ??
|
|
8458
|
-
this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
8333
|
+
summarizationProvider: config?.summarizationProvider ?? this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
|
|
8334
|
+
summarizationModel: config?.summarizationModel ?? this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
8459
8335
|
});
|
|
8460
8336
|
// Use actual context window to determine target, not arbitrary heuristic
|
|
8461
8337
|
const budgetInfo = checkContextBudget({
|
|
@@ -8524,28 +8400,32 @@ Current user's request: ${currentInput}`;
|
|
|
8524
8400
|
async setAuthProvider(config) {
|
|
8525
8401
|
// Clear any pending lazy-init promise so it does not race with this call.
|
|
8526
8402
|
this.authInitPromise = undefined;
|
|
8403
|
+
await this.initializeAuthProviderFromConfig(config);
|
|
8404
|
+
}
|
|
8405
|
+
async initializeAuthProviderFromConfig(config) {
|
|
8406
|
+
let provider;
|
|
8407
|
+
let providerType;
|
|
8527
8408
|
// Duck-type check: direct MastraAuthProvider instance
|
|
8528
|
-
if ("authenticateToken" in config &&
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
logger.info(`Auth provider set: ${this.authProvider.type}`);
|
|
8409
|
+
if ("authenticateToken" in config && typeof config.authenticateToken === "function") {
|
|
8410
|
+
provider = config;
|
|
8411
|
+
providerType = provider.type;
|
|
8532
8412
|
}
|
|
8533
8413
|
else if ("provider" in config) {
|
|
8534
|
-
|
|
8535
|
-
|
|
8414
|
+
provider = config.provider;
|
|
8415
|
+
providerType = provider.type;
|
|
8536
8416
|
}
|
|
8537
8417
|
else {
|
|
8538
8418
|
const typedConfig = config;
|
|
8539
8419
|
const { AuthProviderFactory } = await import("./auth/AuthProviderFactory.js");
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
}
|
|
8543
|
-
if (this.authProvider) {
|
|
8544
|
-
this.emitter.emit("auth:provider:set", {
|
|
8545
|
-
type: this.authProvider.type,
|
|
8546
|
-
timestamp: Date.now(),
|
|
8547
|
-
});
|
|
8420
|
+
provider = await AuthProviderFactory.createProvider(typedConfig.type, typedConfig.config);
|
|
8421
|
+
providerType = typedConfig.type;
|
|
8548
8422
|
}
|
|
8423
|
+
this.authProvider = provider;
|
|
8424
|
+
logger.info(`Auth provider set: ${providerType}`);
|
|
8425
|
+
this.emitter.emit("auth:provider:set", {
|
|
8426
|
+
type: provider.type,
|
|
8427
|
+
timestamp: Date.now(),
|
|
8428
|
+
});
|
|
8549
8429
|
}
|
|
8550
8430
|
/**
|
|
8551
8431
|
* Get the currently configured authentication provider
|
|
@@ -8562,14 +8442,17 @@ Current user's request: ${currentInput}`;
|
|
|
8562
8442
|
if (this.authProvider || !this.pendingAuthConfig) {
|
|
8563
8443
|
return;
|
|
8564
8444
|
}
|
|
8445
|
+
const pendingAuthConfig = this.pendingAuthConfig;
|
|
8565
8446
|
this.authInitPromise ??= (async () => {
|
|
8566
8447
|
try {
|
|
8567
|
-
await this.
|
|
8448
|
+
await this.initializeAuthProviderFromConfig(pendingAuthConfig);
|
|
8568
8449
|
this.pendingAuthConfig = undefined;
|
|
8569
8450
|
}
|
|
8570
|
-
|
|
8571
|
-
this.authInitPromise
|
|
8572
|
-
|
|
8451
|
+
finally {
|
|
8452
|
+
if (this.authInitPromise &&
|
|
8453
|
+
(this.pendingAuthConfig === undefined || this.pendingAuthConfig === pendingAuthConfig)) {
|
|
8454
|
+
this.authInitPromise = undefined;
|
|
8455
|
+
}
|
|
8573
8456
|
}
|
|
8574
8457
|
})();
|
|
8575
8458
|
await this.authInitPromise;
|