@juspay/neurolink 9.41.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 +6 -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 +337 -318
- 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.js +3 -0
- package/dist/cli/factories/commandFactory.d.ts +2 -0
- package/dist/cli/factories/commandFactory.js +38 -0
- package/dist/cli/parser.js +4 -3
- 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 +3 -2
- package/dist/lib/neurolink.js +260 -494
- 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 +1 -0
- package/dist/lib/tasks/backends/bullmqBackend.js +14 -7
- package/dist/lib/tasks/store/redisTaskStore.d.ts +1 -0
- package/dist/lib/tasks/store/redisTaskStore.js +34 -26
- package/dist/lib/tasks/taskManager.d.ts +3 -0
- package/dist/lib/tasks/taskManager.js +63 -30
- 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/proxyTypes.d.ts +37 -5
- package/dist/lib/types/streamTypes.d.ts +25 -3
- 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 +3 -2
- package/dist/neurolink.js +260 -494
- 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 +1 -0
- package/dist/tasks/backends/bullmqBackend.js +14 -7
- package/dist/tasks/store/redisTaskStore.d.ts +1 -0
- package/dist/tasks/store/redisTaskStore.js +34 -26
- package/dist/tasks/taskManager.d.ts +3 -0
- package/dist/tasks/taskManager.js +63 -30
- 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/proxyTypes.d.ts +37 -5
- package/dist/types/streamTypes.d.ts +25 -3
- 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 +17 -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/lib/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,11 +75,8 @@ 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
|
-
import { TaskManager } from "./tasks/taskManager.js";
|
|
79
|
-
import { createTaskTools } from "./tasks/tools/taskTools.js";
|
|
80
80
|
/**
|
|
81
81
|
* NL-002: Classify MCP error messages into categories for AI disambiguation.
|
|
82
82
|
* Returns a human-readable error category based on error message content.
|
|
@@ -97,9 +97,7 @@ function classifyMcpErrorMessage(text) {
|
|
|
97
97
|
lower.includes("access denied")) {
|
|
98
98
|
return "permission_denied";
|
|
99
99
|
}
|
|
100
|
-
if (lower.includes("timeout") ||
|
|
101
|
-
lower.includes("timed out") ||
|
|
102
|
-
lower.includes("deadline exceeded")) {
|
|
100
|
+
if (lower.includes("timeout") || lower.includes("timed out") || lower.includes("deadline exceeded")) {
|
|
103
101
|
return "timeout";
|
|
104
102
|
}
|
|
105
103
|
if (lower.includes("rate limit") ||
|
|
@@ -156,11 +154,7 @@ function isNonRetryableProviderError(error) {
|
|
|
156
154
|
// Check for HTTP status codes on error objects (e.g., from Vercel AI SDK)
|
|
157
155
|
if (error && typeof error === "object") {
|
|
158
156
|
const err = error;
|
|
159
|
-
const status = typeof err.status === "number"
|
|
160
|
-
? err.status
|
|
161
|
-
: typeof err.statusCode === "number"
|
|
162
|
-
? err.statusCode
|
|
163
|
-
: undefined;
|
|
157
|
+
const status = typeof err.status === "number" ? err.status : typeof err.statusCode === "number" ? err.statusCode : undefined;
|
|
164
158
|
if (status && NON_RETRYABLE_HTTP_STATUS_CODES.includes(status)) {
|
|
165
159
|
return true;
|
|
166
160
|
}
|
|
@@ -206,8 +200,7 @@ export class NeuroLink {
|
|
|
206
200
|
lastCompactionMessageCount = new Map();
|
|
207
201
|
/** Extract sessionId from options context for compaction watermark keying */
|
|
208
202
|
getCompactionSessionId(options) {
|
|
209
|
-
return
|
|
210
|
-
?.sessionId || "__default__");
|
|
203
|
+
return options.context?.sessionId || "__default__";
|
|
211
204
|
}
|
|
212
205
|
// MCP Enhancement modules - wired into core execution path
|
|
213
206
|
mcpToolResultCache;
|
|
@@ -270,28 +263,19 @@ export class NeuroLink {
|
|
|
270
263
|
* Extract and set Langfuse context from options with proper async scoping
|
|
271
264
|
*/
|
|
272
265
|
async setLangfuseContextFromOptions(options, callback) {
|
|
273
|
-
if (options.context &&
|
|
274
|
-
typeof options.context === "object" &&
|
|
275
|
-
options.context !== null) {
|
|
266
|
+
if (options.context && typeof options.context === "object" && options.context !== null) {
|
|
276
267
|
let callbackExecuted = false;
|
|
277
268
|
try {
|
|
278
269
|
const ctx = options.context;
|
|
279
270
|
// Trigger context scoping if any meaningful Langfuse field is present
|
|
280
|
-
if (ctx.userId ||
|
|
281
|
-
ctx.sessionId ||
|
|
282
|
-
ctx.conversationId ||
|
|
283
|
-
ctx.requestId ||
|
|
284
|
-
ctx.traceName ||
|
|
285
|
-
ctx.metadata) {
|
|
271
|
+
if (ctx.userId || ctx.sessionId || ctx.conversationId || ctx.requestId || ctx.traceName || ctx.metadata) {
|
|
286
272
|
// Build customAttributes from top-level metadata string/number/boolean fields
|
|
287
273
|
let customAttributes;
|
|
288
274
|
if (ctx.metadata && typeof ctx.metadata === "object") {
|
|
289
275
|
const metaObj = ctx.metadata;
|
|
290
276
|
const attrs = {};
|
|
291
277
|
for (const [k, v] of Object.entries(metaObj)) {
|
|
292
|
-
if (typeof v === "string" ||
|
|
293
|
-
typeof v === "number" ||
|
|
294
|
-
typeof v === "boolean") {
|
|
278
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
295
279
|
attrs[k] = v;
|
|
296
280
|
}
|
|
297
281
|
}
|
|
@@ -303,14 +287,10 @@ export class NeuroLink {
|
|
|
303
287
|
setLangfuseContext({
|
|
304
288
|
userId: typeof ctx.userId === "string" ? ctx.userId : null,
|
|
305
289
|
sessionId: typeof ctx.sessionId === "string" ? ctx.sessionId : null,
|
|
306
|
-
conversationId: typeof ctx.conversationId === "string"
|
|
307
|
-
? ctx.conversationId
|
|
308
|
-
: null,
|
|
290
|
+
conversationId: typeof ctx.conversationId === "string" ? ctx.conversationId : null,
|
|
309
291
|
requestId: typeof ctx.requestId === "string" ? ctx.requestId : null,
|
|
310
292
|
traceName: typeof ctx.traceName === "string" ? ctx.traceName : null,
|
|
311
|
-
metadata: ctx.metadata && typeof ctx.metadata === "object"
|
|
312
|
-
? ctx.metadata
|
|
313
|
-
: null,
|
|
293
|
+
metadata: ctx.metadata && typeof ctx.metadata === "object" ? ctx.metadata : null,
|
|
314
294
|
...(customAttributes !== undefined && { customAttributes }),
|
|
315
295
|
}, async () => {
|
|
316
296
|
try {
|
|
@@ -444,9 +424,7 @@ export class NeuroLink {
|
|
|
444
424
|
logger.setEventEmitter(this.emitter);
|
|
445
425
|
// Read tool cache duration from environment variables, with a default
|
|
446
426
|
const cacheDurationEnv = process.env.NEUROLINK_TOOL_CACHE_DURATION;
|
|
447
|
-
this.toolCacheDuration = cacheDurationEnv
|
|
448
|
-
? parseInt(cacheDurationEnv, 10)
|
|
449
|
-
: 20000;
|
|
427
|
+
this.toolCacheDuration = cacheDurationEnv ? parseInt(cacheDurationEnv, 10) : 20000;
|
|
450
428
|
const constructorStartTime = Date.now();
|
|
451
429
|
const constructorHrTimeStart = process.hrtime.bigint();
|
|
452
430
|
const constructorId = `neurolink-constructor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -801,9 +779,7 @@ export class NeuroLink {
|
|
|
801
779
|
// memory manager supports getSessionRaw.
|
|
802
780
|
const memConfig = this.conversationMemoryConfig?.conversationMemory;
|
|
803
781
|
const hasRedisConfig = !!memConfig?.redisConfig ||
|
|
804
|
-
(memConfig &&
|
|
805
|
-
"redis" in memConfig &&
|
|
806
|
-
!!memConfig.redis) ||
|
|
782
|
+
(memConfig && "redis" in memConfig && !!memConfig.redis) ||
|
|
807
783
|
process.env.STORAGE_TYPE === "redis";
|
|
808
784
|
if (!memConfig?.enabled || !hasRedisConfig) {
|
|
809
785
|
logger.debug("[NeuroLink] Skipping memory retrieval tools — requires Redis conversation memory");
|
|
@@ -834,13 +810,8 @@ export class NeuroLink {
|
|
|
834
810
|
messages: [],
|
|
835
811
|
});
|
|
836
812
|
// Check if the tool itself reported an error
|
|
837
|
-
const hasError = result &&
|
|
838
|
-
|
|
839
|
-
"error" in result &&
|
|
840
|
-
!("messages" in result);
|
|
841
|
-
const errorMsg = hasError
|
|
842
|
-
? result.error
|
|
843
|
-
: undefined;
|
|
813
|
+
const hasError = result && typeof result === "object" && "error" in result && !("messages" in result);
|
|
814
|
+
const errorMsg = hasError ? result.error : undefined;
|
|
844
815
|
return {
|
|
845
816
|
success: !hasError,
|
|
846
817
|
data: result,
|
|
@@ -917,8 +888,7 @@ Current user's request: ${currentInput}`;
|
|
|
917
888
|
* Respects both the global memory SDK config and per-call overrides.
|
|
918
889
|
*/
|
|
919
890
|
shouldReadMemory(perCallMemory, userId) {
|
|
920
|
-
if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled ||
|
|
921
|
-
!userId) {
|
|
891
|
+
if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled || !userId) {
|
|
922
892
|
return false;
|
|
923
893
|
}
|
|
924
894
|
if (perCallMemory?.enabled === false) {
|
|
@@ -934,8 +904,7 @@ Current user's request: ${currentInput}`;
|
|
|
934
904
|
* Respects both the global memory SDK config and per-call overrides.
|
|
935
905
|
*/
|
|
936
906
|
shouldWriteMemory(perCallMemory, userId, content) {
|
|
937
|
-
if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled ||
|
|
938
|
-
!userId) {
|
|
907
|
+
if (!this.conversationMemoryConfig?.conversationMemory?.memory?.enabled || !userId) {
|
|
939
908
|
return false;
|
|
940
909
|
}
|
|
941
910
|
if (!content?.trim()) {
|
|
@@ -1009,9 +978,7 @@ Current user's request: ${currentInput}`;
|
|
|
1009
978
|
const writeOps = [client.add(userId, content)];
|
|
1010
979
|
const writableAdditional = (additionalUsers || []).filter((u) => u.write !== false);
|
|
1011
980
|
for (const user of writableAdditional) {
|
|
1012
|
-
const addOptions = user.prompt || user.maxWords
|
|
1013
|
-
? { prompt: user.prompt, maxWords: user.maxWords }
|
|
1014
|
-
: undefined;
|
|
981
|
+
const addOptions = user.prompt || user.maxWords ? { prompt: user.prompt, maxWords: user.maxWords } : undefined;
|
|
1015
982
|
writeOps.push(client.add(user.userId, content, addOptions));
|
|
1016
983
|
}
|
|
1017
984
|
await Promise.all(writeOps);
|
|
@@ -1170,8 +1137,7 @@ Current user's request: ${currentInput}`;
|
|
|
1170
1137
|
try {
|
|
1171
1138
|
const langfuseConfig = this.observabilityConfig?.langfuse;
|
|
1172
1139
|
// Check if we should use external provider mode - bypass enabled check
|
|
1173
|
-
const useExternalProvider = langfuseConfig?.autoDetectExternalProvider === true ||
|
|
1174
|
-
langfuseConfig?.useExternalTracerProvider === true;
|
|
1140
|
+
const useExternalProvider = langfuseConfig?.autoDetectExternalProvider === true || langfuseConfig?.useExternalTracerProvider === true;
|
|
1175
1141
|
if (langfuseConfig?.enabled || useExternalProvider) {
|
|
1176
1142
|
logger.debug(`[NeuroLink] 📊 LOG_POINT_C019_LANGFUSE_INIT_START`, {
|
|
1177
1143
|
logPoint: "C019_LANGFUSE_INIT_START",
|
|
@@ -1186,9 +1152,7 @@ Current user's request: ${currentInput}`;
|
|
|
1186
1152
|
initializeOpenTelemetry(langfuseConfig);
|
|
1187
1153
|
const healthStatus = getLangfuseHealthStatus();
|
|
1188
1154
|
const langfuseInitDurationNs = process.hrtime.bigint() - langfuseInitStartTime;
|
|
1189
|
-
if (healthStatus.initialized &&
|
|
1190
|
-
healthStatus.hasProcessor &&
|
|
1191
|
-
healthStatus.isHealthy) {
|
|
1155
|
+
if (healthStatus.initialized && healthStatus.hasProcessor && healthStatus.isHealthy) {
|
|
1192
1156
|
logger.debug(`[NeuroLink] ✅ LOG_POINT_C020_LANGFUSE_INIT_SUCCESS`, {
|
|
1193
1157
|
logPoint: "C020_LANGFUSE_INIT_SUCCESS",
|
|
1194
1158
|
constructorId,
|
|
@@ -1464,9 +1428,7 @@ Current user's request: ${currentInput}`;
|
|
|
1464
1428
|
}
|
|
1465
1429
|
catch (configError) {
|
|
1466
1430
|
mcpLogger.warn("[NeuroLink] MCP configuration loading failed", {
|
|
1467
|
-
error: configError instanceof Error
|
|
1468
|
-
? configError.message
|
|
1469
|
-
: String(configError),
|
|
1431
|
+
error: configError instanceof Error ? configError.message : String(configError),
|
|
1470
1432
|
});
|
|
1471
1433
|
}
|
|
1472
1434
|
}
|
|
@@ -1591,9 +1553,7 @@ Current user's request: ${currentInput}`;
|
|
|
1591
1553
|
taskType: classification.type,
|
|
1592
1554
|
routedProvider: route.provider,
|
|
1593
1555
|
routedModel: route.model,
|
|
1594
|
-
reason: error instanceof Error
|
|
1595
|
-
? error.message
|
|
1596
|
-
: "Ollama service check failed",
|
|
1556
|
+
reason: error instanceof Error ? error.message : "Ollama service check failed",
|
|
1597
1557
|
orchestrationTime: `${Date.now() - startTime}ms`,
|
|
1598
1558
|
});
|
|
1599
1559
|
return {}; // Return empty object to preserve existing fallback behavior
|
|
@@ -1729,9 +1689,7 @@ Current user's request: ${currentInput}`;
|
|
|
1729
1689
|
taskType: classification.type,
|
|
1730
1690
|
routedProvider: route.provider,
|
|
1731
1691
|
routedModel: route.model,
|
|
1732
|
-
reason: error instanceof Error
|
|
1733
|
-
? error.message
|
|
1734
|
-
: "Ollama service check failed",
|
|
1692
|
+
reason: error instanceof Error ? error.message : "Ollama service check failed",
|
|
1735
1693
|
orchestrationTime: `${Date.now() - startTime}ms`,
|
|
1736
1694
|
});
|
|
1737
1695
|
return {}; // Return empty object to preserve existing fallback behavior
|
|
@@ -1782,9 +1740,7 @@ Current user's request: ${currentInput}`;
|
|
|
1782
1740
|
const anyOptions = optionsOrPrompt;
|
|
1783
1741
|
if (anyOptions.messages && anyOptions.messages.length > 0) {
|
|
1784
1742
|
const lastMessage = anyOptions.messages[anyOptions.messages.length - 1];
|
|
1785
|
-
return typeof lastMessage.content === "string"
|
|
1786
|
-
? lastMessage.content
|
|
1787
|
-
: JSON.stringify(lastMessage.content);
|
|
1743
|
+
return typeof lastMessage.content === "string" ? lastMessage.content : JSON.stringify(lastMessage.content);
|
|
1788
1744
|
}
|
|
1789
1745
|
// Handle input.text format
|
|
1790
1746
|
return optionsOrPrompt.input?.text || "";
|
|
@@ -1876,8 +1832,7 @@ Current user's request: ${currentInput}`;
|
|
|
1876
1832
|
endpoint: otelConfig.endpoint,
|
|
1877
1833
|
serviceName: otelConfig.serviceName,
|
|
1878
1834
|
}
|
|
1879
|
-
: isOpenTelemetryInitialized() ||
|
|
1880
|
-
process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
1835
|
+
: isOpenTelemetryInitialized() || process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
1881
1836
|
? {
|
|
1882
1837
|
enabled: isOpenTelemetryInitialized(),
|
|
1883
1838
|
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
@@ -2019,9 +1974,7 @@ Current user's request: ${currentInput}`;
|
|
|
2019
1974
|
const result = data.result;
|
|
2020
1975
|
const usage = result?.usage;
|
|
2021
1976
|
const analytics = result?.analytics;
|
|
2022
|
-
const provider = data.provider ||
|
|
2023
|
-
result?.provider ||
|
|
2024
|
-
"unknown";
|
|
1977
|
+
const provider = data.provider || result?.provider || "unknown";
|
|
2025
1978
|
const model = result?.model || "unknown";
|
|
2026
1979
|
const responseTime = data.responseTime || 0;
|
|
2027
1980
|
const traceCtx = this._metricsTraceContext;
|
|
@@ -2040,9 +1993,7 @@ Current user's request: ${currentInput}`;
|
|
|
2040
1993
|
span.parentSpanId = undefined;
|
|
2041
1994
|
}
|
|
2042
1995
|
// Mark failed generations with ERROR status so metrics count them correctly
|
|
2043
|
-
const spanStatus = data.success === false || data.error
|
|
2044
|
-
? SpanStatus.ERROR
|
|
2045
|
-
: SpanStatus.OK;
|
|
1996
|
+
const spanStatus = data.success === false || data.error ? SpanStatus.ERROR : SpanStatus.OK;
|
|
2046
1997
|
span = SpanSerializer.endSpan(span, spanStatus, data.error ? String(data.error) : undefined);
|
|
2047
1998
|
span.durationMs = responseTime;
|
|
2048
1999
|
if (usage) {
|
|
@@ -2078,9 +2029,7 @@ Current user's request: ${currentInput}`;
|
|
|
2078
2029
|
const content = result?.content || result?.text;
|
|
2079
2030
|
if (content) {
|
|
2080
2031
|
span = SpanSerializer.updateAttributes(span, {
|
|
2081
|
-
output: content.length > 5000
|
|
2082
|
-
? content.substring(0, 5000) + "...[truncated]"
|
|
2083
|
-
: content,
|
|
2032
|
+
output: content.length > 5000 ? content.substring(0, 5000) + "...[truncated]" : content,
|
|
2084
2033
|
});
|
|
2085
2034
|
}
|
|
2086
2035
|
this.metricsAggregator.recordSpan(span);
|
|
@@ -2119,18 +2068,14 @@ Current user's request: ${currentInput}`;
|
|
|
2119
2068
|
if (data.prompt) {
|
|
2120
2069
|
const promptStr = String(data.prompt);
|
|
2121
2070
|
span = SpanSerializer.updateAttributes(span, {
|
|
2122
|
-
input: promptStr.length > 5000
|
|
2123
|
-
? promptStr.substring(0, 5000) + "...[truncated]"
|
|
2124
|
-
: promptStr,
|
|
2071
|
+
input: promptStr.length > 5000 ? promptStr.substring(0, 5000) + "...[truncated]" : promptStr,
|
|
2125
2072
|
});
|
|
2126
2073
|
}
|
|
2127
2074
|
// Record streamed output (truncated for safety)
|
|
2128
2075
|
const streamContent = data.content;
|
|
2129
2076
|
if (streamContent) {
|
|
2130
2077
|
span = SpanSerializer.updateAttributes(span, {
|
|
2131
|
-
output: streamContent.length > 5000
|
|
2132
|
-
? streamContent.substring(0, 5000) + "...[truncated]"
|
|
2133
|
-
: streamContent,
|
|
2078
|
+
output: streamContent.length > 5000 ? streamContent.substring(0, 5000) + "...[truncated]" : streamContent,
|
|
2134
2079
|
});
|
|
2135
2080
|
}
|
|
2136
2081
|
// Enrich stream span with token usage if available
|
|
@@ -2147,8 +2092,7 @@ Current user's request: ${currentInput}`;
|
|
|
2147
2092
|
const pricing = tokenTracker.getModelPricing(model);
|
|
2148
2093
|
if (pricing) {
|
|
2149
2094
|
const inputCost = ((usage.input || 0) / 1_000_000) * pricing.inputPricePerMillion;
|
|
2150
|
-
const outputCost = ((usage.output || 0) / 1_000_000) *
|
|
2151
|
-
pricing.outputPricePerMillion;
|
|
2095
|
+
const outputCost = ((usage.output || 0) / 1_000_000) * pricing.outputPricePerMillion;
|
|
2152
2096
|
const totalCost = inputCost + outputCost;
|
|
2153
2097
|
if (totalCost > 0) {
|
|
2154
2098
|
span = SpanSerializer.enrichWithCost(span, {
|
|
@@ -2183,8 +2127,7 @@ Current user's request: ${currentInput}`;
|
|
|
2183
2127
|
span = SpanSerializer.endSpan(span, success ? SpanStatus.OK : SpanStatus.ERROR);
|
|
2184
2128
|
span.durationMs = responseTime;
|
|
2185
2129
|
if (!success && data.error) {
|
|
2186
|
-
span.statusMessage =
|
|
2187
|
-
data.error.message || String(data.error);
|
|
2130
|
+
span.statusMessage = data.error.message || String(data.error);
|
|
2188
2131
|
}
|
|
2189
2132
|
if (data.result) {
|
|
2190
2133
|
try {
|
|
@@ -2341,10 +2284,7 @@ Current user's request: ${currentInput}`;
|
|
|
2341
2284
|
// The generation span will be the root (no parentSpanId).
|
|
2342
2285
|
// Tool spans will be children of the root span via rootSpanId.
|
|
2343
2286
|
const metricsTraceId = crypto.randomUUID().replace(/-/g, "");
|
|
2344
|
-
const metricsRootSpanId = crypto
|
|
2345
|
-
.randomUUID()
|
|
2346
|
-
.replace(/-/g, "")
|
|
2347
|
-
.substring(0, 16);
|
|
2287
|
+
const metricsRootSpanId = crypto.randomUUID().replace(/-/g, "").substring(0, 16);
|
|
2348
2288
|
// Scope trace context to this request via AsyncLocalStorage
|
|
2349
2289
|
// so concurrent generate/stream calls don't race.
|
|
2350
2290
|
return metricsTraceContextStorage.run({ traceId: metricsTraceId, parentSpanId: metricsRootSpanId }, async () => {
|
|
@@ -2352,24 +2292,18 @@ Current user's request: ${currentInput}`;
|
|
|
2352
2292
|
const originalPrompt = this._extractOriginalPrompt(optionsOrPrompt);
|
|
2353
2293
|
// Convert string prompt to full options
|
|
2354
2294
|
// Shallow-copy caller's object to avoid mutating their original reference
|
|
2355
|
-
const options = typeof optionsOrPrompt === "string"
|
|
2356
|
-
? { input: { text: optionsOrPrompt } }
|
|
2357
|
-
: { ...optionsOrPrompt };
|
|
2295
|
+
const options = typeof optionsOrPrompt === "string" ? { input: { text: optionsOrPrompt } } : { ...optionsOrPrompt };
|
|
2358
2296
|
// NL-004: Resolve model aliases/deprecations before processing
|
|
2359
2297
|
options.model = resolveModel(options.model, this.modelAliasConfig);
|
|
2360
2298
|
// MCP Enhancement: propagate disableToolCache to tool execution
|
|
2361
|
-
this._disableToolCacheForCurrentRequest =
|
|
2362
|
-
!!options.disableToolCache;
|
|
2299
|
+
this._disableToolCacheForCurrentRequest = !!options.disableToolCache;
|
|
2363
2300
|
// Set span attributes for observability
|
|
2364
2301
|
generateSpan.setAttribute("neurolink.provider", options.provider || "default");
|
|
2365
2302
|
generateSpan.setAttribute("neurolink.model", options.model || "default");
|
|
2366
|
-
generateSpan.setAttribute("neurolink.input_length", typeof optionsOrPrompt === "string"
|
|
2367
|
-
? optionsOrPrompt.length
|
|
2368
|
-
: options.input?.text?.length || 0);
|
|
2303
|
+
generateSpan.setAttribute("neurolink.input_length", typeof optionsOrPrompt === "string" ? optionsOrPrompt.length : options.input?.text?.length || 0);
|
|
2369
2304
|
generateSpan.setAttribute("neurolink.has_tools", !!(options.tools && Object.keys(options.tools).length > 0));
|
|
2370
2305
|
// Validate prompt
|
|
2371
|
-
if (!options.input?.text ||
|
|
2372
|
-
typeof options.input.text !== "string") {
|
|
2306
|
+
if (!options.input?.text || typeof options.input.text !== "string") {
|
|
2373
2307
|
throw new Error("Input text is required and must be a non-empty string");
|
|
2374
2308
|
}
|
|
2375
2309
|
// Check budget limit before making API call
|
|
@@ -2399,10 +2333,9 @@ Current user's request: ${currentInput}`;
|
|
|
2399
2333
|
...options.middleware?.middlewareConfig?.lifecycle,
|
|
2400
2334
|
enabled: true,
|
|
2401
2335
|
config: {
|
|
2402
|
-
...options.middleware?.middlewareConfig?.lifecycle
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
onError: options.onError,
|
|
2336
|
+
...options.middleware?.middlewareConfig?.lifecycle?.config,
|
|
2337
|
+
...(options.onFinish !== undefined ? { onFinish: options.onFinish } : {}),
|
|
2338
|
+
...(options.onError !== undefined ? { onError: options.onError } : {}),
|
|
2406
2339
|
},
|
|
2407
2340
|
},
|
|
2408
2341
|
},
|
|
@@ -2421,9 +2354,7 @@ Current user's request: ${currentInput}`;
|
|
|
2421
2354
|
}
|
|
2422
2355
|
catch (err) {
|
|
2423
2356
|
// Rethrow auth errors as-is; wrap anything else
|
|
2424
|
-
if (err instanceof Error &&
|
|
2425
|
-
"feature" in err &&
|
|
2426
|
-
err.feature === "Auth") {
|
|
2357
|
+
if (err instanceof Error && "feature" in err && err.feature === "Auth") {
|
|
2427
2358
|
throw err;
|
|
2428
2359
|
}
|
|
2429
2360
|
throw AuthError.create("PROVIDER_ERROR", `Auth token validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -2483,9 +2414,7 @@ Current user's request: ${currentInput}`;
|
|
|
2483
2414
|
return await this.setLangfuseContextFromOptions(options, async () => {
|
|
2484
2415
|
const startTime = Date.now();
|
|
2485
2416
|
// Apply orchestration if enabled and no specific provider/model requested
|
|
2486
|
-
if (this.enableOrchestration &&
|
|
2487
|
-
!options.provider &&
|
|
2488
|
-
!options.model) {
|
|
2417
|
+
if (this.enableOrchestration && !options.provider && !options.model) {
|
|
2489
2418
|
try {
|
|
2490
2419
|
const orchestratedOptions = await this.applyOrchestration(options);
|
|
2491
2420
|
logger.debug("Orchestration applied", {
|
|
@@ -2503,9 +2432,7 @@ Current user's request: ${currentInput}`;
|
|
|
2503
2432
|
}
|
|
2504
2433
|
catch (error) {
|
|
2505
2434
|
logger.warn("Orchestration failed, continuing with original options", {
|
|
2506
|
-
error: error instanceof Error
|
|
2507
|
-
? error.message
|
|
2508
|
-
: String(error),
|
|
2435
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2509
2436
|
originalProvider: options.provider || "auto",
|
|
2510
2437
|
});
|
|
2511
2438
|
// Continue with original options if orchestration fails
|
|
@@ -2550,8 +2477,7 @@ Current user's request: ${currentInput}`;
|
|
|
2550
2477
|
`This tool searches your local knowledge base of pre-loaded documents and is the primary source of truth.`,
|
|
2551
2478
|
`Do NOT use websearchGrounding or any web search tools when the answer can be found in the loaded documents.`,
|
|
2552
2479
|
].join(" ");
|
|
2553
|
-
options.systemPrompt =
|
|
2554
|
-
(options.systemPrompt || "") + ragSystemInstruction;
|
|
2480
|
+
options.systemPrompt = (options.systemPrompt || "") + ragSystemInstruction;
|
|
2555
2481
|
logger.info("[RAG] Tool injected into generate()", {
|
|
2556
2482
|
toolName: ragResult.toolName,
|
|
2557
2483
|
filesLoaded: ragResult.filesLoaded,
|
|
@@ -2560,15 +2486,12 @@ Current user's request: ${currentInput}`;
|
|
|
2560
2486
|
}
|
|
2561
2487
|
catch (error) {
|
|
2562
2488
|
logger.warn("[RAG] Failed to prepare RAG tool, continuing without RAG", {
|
|
2563
|
-
error: error instanceof Error
|
|
2564
|
-
? error.message
|
|
2565
|
-
: String(error),
|
|
2489
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2566
2490
|
});
|
|
2567
2491
|
}
|
|
2568
2492
|
}
|
|
2569
2493
|
// Memory retrieval for generate path
|
|
2570
|
-
if (this.shouldReadMemory(options.memory, options.context?.userId) &&
|
|
2571
|
-
options.context?.userId) {
|
|
2494
|
+
if (this.shouldReadMemory(options.memory, options.context?.userId) && options.context?.userId) {
|
|
2572
2495
|
try {
|
|
2573
2496
|
options.input.text = await this.retrieveMemory(options.input.text, options.context.userId, options.memory?.additionalUsers);
|
|
2574
2497
|
logger.debug("Memory retrieval successful (generate)");
|
|
@@ -2615,8 +2538,7 @@ Current user's request: ${currentInput}`;
|
|
|
2615
2538
|
if (extraContext.sessionId || extraContext.userId) {
|
|
2616
2539
|
baseOptions.context = {
|
|
2617
2540
|
...baseOptions.context,
|
|
2618
|
-
...(extraContext.sessionId &&
|
|
2619
|
-
!baseOptions.context?.sessionId
|
|
2541
|
+
...(extraContext.sessionId && !baseOptions.context?.sessionId
|
|
2620
2542
|
? { sessionId: extraContext.sessionId }
|
|
2621
2543
|
: {}),
|
|
2622
2544
|
...(extraContext.userId && !baseOptions.context?.userId
|
|
@@ -2628,8 +2550,7 @@ Current user's request: ${currentInput}`;
|
|
|
2628
2550
|
const textOptions = enhanceTextGenerationOptions(baseOptions, factoryResult);
|
|
2629
2551
|
// Pass conversation memory config if available
|
|
2630
2552
|
if (this.conversationMemory) {
|
|
2631
|
-
textOptions.conversationMemoryConfig =
|
|
2632
|
-
this.conversationMemory.config;
|
|
2553
|
+
textOptions.conversationMemoryConfig = this.conversationMemory.config;
|
|
2633
2554
|
// Include original prompt for context summarization
|
|
2634
2555
|
textOptions.originalPrompt = originalPrompt;
|
|
2635
2556
|
}
|
|
@@ -2652,8 +2573,7 @@ Current user's request: ${currentInput}`;
|
|
|
2652
2573
|
toolsUsed: textResult.toolsUsed,
|
|
2653
2574
|
timestamp: Date.now(),
|
|
2654
2575
|
result: textResult, // Enhanced: include full result
|
|
2655
|
-
prompt: options.input?.text ||
|
|
2656
|
-
options.prompt,
|
|
2576
|
+
prompt: options.input?.text || options.prompt,
|
|
2657
2577
|
temperature: textOptions.temperature,
|
|
2658
2578
|
maxTokens: textOptions.maxTokens,
|
|
2659
2579
|
});
|
|
@@ -2686,10 +2606,8 @@ Current user's request: ${currentInput}`;
|
|
|
2686
2606
|
? {
|
|
2687
2607
|
...textResult.evaluation,
|
|
2688
2608
|
isOffTopic: textResult.evaluation.isOffTopic ?? false,
|
|
2689
|
-
alertSeverity: textResult.evaluation.alertSeverity ??
|
|
2690
|
-
|
|
2691
|
-
reasoning: textResult.evaluation.reasoning ??
|
|
2692
|
-
"No evaluation provided",
|
|
2609
|
+
alertSeverity: textResult.evaluation.alertSeverity ?? "none",
|
|
2610
|
+
reasoning: textResult.evaluation.reasoning ?? "No evaluation provided",
|
|
2693
2611
|
evaluationModel: textResult.evaluation.evaluationModel ?? "unknown",
|
|
2694
2612
|
evaluationTime: textResult.evaluation.evaluationTime ?? Date.now(),
|
|
2695
2613
|
evaluationDomain: textResult.evaluation.evaluationDomain ??
|
|
@@ -2704,8 +2622,7 @@ Current user's request: ${currentInput}`;
|
|
|
2704
2622
|
...(textResult.retries && { retries: textResult.retries }),
|
|
2705
2623
|
};
|
|
2706
2624
|
// Accumulate session cost for budget tracking
|
|
2707
|
-
if (generateResult.analytics?.cost &&
|
|
2708
|
-
generateResult.analytics.cost > 0) {
|
|
2625
|
+
if (generateResult.analytics?.cost && generateResult.analytics.cost > 0) {
|
|
2709
2626
|
this._sessionCostUsd += generateResult.analytics.cost;
|
|
2710
2627
|
}
|
|
2711
2628
|
this.scheduleGenerateMemoryStorage(options, originalPrompt, generateResult);
|
|
@@ -2733,9 +2650,7 @@ Current user's request: ${currentInput}`;
|
|
|
2733
2650
|
const errProvider = typeof optionsOrPrompt === "object"
|
|
2734
2651
|
? optionsOrPrompt.provider || "unknown"
|
|
2735
2652
|
: "unknown";
|
|
2736
|
-
const errModel = typeof optionsOrPrompt === "object"
|
|
2737
|
-
? optionsOrPrompt.model || "unknown"
|
|
2738
|
-
: "unknown";
|
|
2653
|
+
const errModel = typeof optionsOrPrompt === "object" ? optionsOrPrompt.model || "unknown" : "unknown";
|
|
2739
2654
|
try {
|
|
2740
2655
|
this.emitter.emit("generation:end", {
|
|
2741
2656
|
provider: errProvider,
|
|
@@ -2836,11 +2751,8 @@ Current user's request: ${currentInput}`;
|
|
|
2836
2751
|
?.filter((m) => m.role === "user" || m.role === "assistant")
|
|
2837
2752
|
.map((m) => ({
|
|
2838
2753
|
role: m.role,
|
|
2839
|
-
content: typeof m.content === "string"
|
|
2840
|
-
|
|
2841
|
-
: JSON.stringify(m.content),
|
|
2842
|
-
})) ??
|
|
2843
|
-
options.conversationHistory,
|
|
2754
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
|
|
2755
|
+
})) ?? options.conversationHistory,
|
|
2844
2756
|
timeout: options.timeout,
|
|
2845
2757
|
verbose: false,
|
|
2846
2758
|
metadata: options.context,
|
|
@@ -2850,10 +2762,8 @@ Current user's request: ${currentInput}`;
|
|
|
2850
2762
|
// Primary output (backward compatible) - use the original best response
|
|
2851
2763
|
content: workflowResult.content,
|
|
2852
2764
|
// Provider info from selected response
|
|
2853
|
-
provider: workflowResult.selectedResponse?.provider ||
|
|
2854
|
-
|
|
2855
|
-
model: workflowResult.selectedResponse?.model ||
|
|
2856
|
-
workflowConfig.models[0]?.model,
|
|
2765
|
+
provider: workflowResult.selectedResponse?.provider || workflowConfig.models[0]?.provider,
|
|
2766
|
+
model: workflowResult.selectedResponse?.model || workflowConfig.models[0]?.model,
|
|
2857
2767
|
// Basic usage info
|
|
2858
2768
|
usage: workflowResult.usage
|
|
2859
2769
|
? {
|
|
@@ -2935,11 +2845,8 @@ Current user's request: ${currentInput}`;
|
|
|
2935
2845
|
?.filter((m) => m.role === "user" || m.role === "assistant")
|
|
2936
2846
|
.map((m) => ({
|
|
2937
2847
|
role: m.role,
|
|
2938
|
-
content: typeof m.content === "string"
|
|
2939
|
-
|
|
2940
|
-
: JSON.stringify(m.content),
|
|
2941
|
-
})) ??
|
|
2942
|
-
options.conversationHistory,
|
|
2848
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
|
|
2849
|
+
})) ?? options.conversationHistory,
|
|
2943
2850
|
timeout: options.timeout,
|
|
2944
2851
|
verbose: false,
|
|
2945
2852
|
metadata: options.context,
|
|
@@ -3063,9 +2970,7 @@ Current user's request: ${currentInput}`;
|
|
|
3063
2970
|
*/
|
|
3064
2971
|
async generateText(options) {
|
|
3065
2972
|
// Validate required parameters for backward compatibility
|
|
3066
|
-
if (!options.prompt ||
|
|
3067
|
-
typeof options.prompt !== "string" ||
|
|
3068
|
-
options.prompt.trim() === "") {
|
|
2973
|
+
if (!options.prompt || typeof options.prompt !== "string" || options.prompt.trim() === "") {
|
|
3069
2974
|
throw new Error("GenerateText options must include prompt as a non-empty string");
|
|
3070
2975
|
}
|
|
3071
2976
|
// NL-004: Resolve model aliases/deprecations before processing
|
|
@@ -3148,9 +3053,7 @@ Current user's request: ${currentInput}`;
|
|
|
3148
3053
|
// the catch block needs the originals for a more effective retry
|
|
3149
3054
|
if (this.conversationMemory) {
|
|
3150
3055
|
const originalMessages = await getConversationMessages(this.conversationMemory, options);
|
|
3151
|
-
options._originalConversationMessages = originalMessages
|
|
3152
|
-
? [...originalMessages]
|
|
3153
|
-
: undefined;
|
|
3056
|
+
options._originalConversationMessages = originalMessages ? [...originalMessages] : undefined;
|
|
3154
3057
|
}
|
|
3155
3058
|
const directResult = await this.directProviderGeneration(options);
|
|
3156
3059
|
logger.debug(`[${functionTag}] Direct generation successful`);
|
|
@@ -3200,8 +3103,7 @@ Current user's request: ${currentInput}`;
|
|
|
3200
3103
|
// IMPROVEMENT 1: Extract actual token count from provider error if available
|
|
3201
3104
|
const actualOverflow = parseProviderOverflowDetails(error);
|
|
3202
3105
|
// IMPROVEMENT 2: Use ORIGINAL messages (not already-compacted ones)
|
|
3203
|
-
const originalMessages = options._originalConversationMessages ??
|
|
3204
|
-
(await getConversationMessages(this.conversationMemory, options));
|
|
3106
|
+
const originalMessages = options._originalConversationMessages ?? (await getConversationMessages(this.conversationMemory, options));
|
|
3205
3107
|
// IMPROVEMENT 3: Calculate precise reduction target
|
|
3206
3108
|
const recoveryBudget = checkContextBudget({
|
|
3207
3109
|
provider: options.provider || "openai",
|
|
@@ -3211,16 +3113,12 @@ Current user's request: ${currentInput}`;
|
|
|
3211
3113
|
systemPrompt: options.systemPrompt,
|
|
3212
3114
|
});
|
|
3213
3115
|
// Use provider's reported token count if available (more accurate than our estimate)
|
|
3214
|
-
const actualTokens = actualOverflow?.actualTokens ??
|
|
3215
|
-
|
|
3216
|
-
const budgetTokens = actualOverflow?.budgetTokens ??
|
|
3217
|
-
recoveryBudget.availableInputTokens;
|
|
3116
|
+
const actualTokens = actualOverflow?.actualTokens ?? recoveryBudget.estimatedInputTokens;
|
|
3117
|
+
const budgetTokens = actualOverflow?.budgetTokens ?? recoveryBudget.availableInputTokens;
|
|
3218
3118
|
// Target = 70% of budget (aggressive safety margin for recovery)
|
|
3219
3119
|
const compactionTarget = Math.floor(budgetTokens * 0.7);
|
|
3220
3120
|
// IMPROVEMENT 4: Calculate adaptive truncation fraction from actual numbers
|
|
3221
|
-
const requiredReduction = actualTokens > 0
|
|
3222
|
-
? (actualTokens - compactionTarget) / actualTokens
|
|
3223
|
-
: 0.5;
|
|
3121
|
+
const requiredReduction = actualTokens > 0 ? (actualTokens - compactionTarget) / actualTokens : 0.5;
|
|
3224
3122
|
const compactor = new ContextCompactor({
|
|
3225
3123
|
enableSummarize: false, // Skip LLM call for recovery (speed)
|
|
3226
3124
|
enablePrune: true,
|
|
@@ -3273,9 +3171,7 @@ Current user's request: ${currentInput}`;
|
|
|
3273
3171
|
throw retryError;
|
|
3274
3172
|
}
|
|
3275
3173
|
logger.error(`[${functionTag}] Recovery attempt failed`, {
|
|
3276
|
-
error: retryError instanceof Error
|
|
3277
|
-
? retryError.message
|
|
3278
|
-
: String(retryError),
|
|
3174
|
+
error: retryError instanceof Error ? retryError.message : String(retryError),
|
|
3279
3175
|
});
|
|
3280
3176
|
}
|
|
3281
3177
|
}
|
|
@@ -3288,8 +3184,7 @@ Current user's request: ${currentInput}`;
|
|
|
3288
3184
|
logger.info(`[${functionTag}] Generation aborted — storing conversation turn for title generation`, {
|
|
3289
3185
|
hasMemory: !!this.conversationMemory,
|
|
3290
3186
|
memoryType: this.conversationMemory?.constructor?.name || "NONE",
|
|
3291
|
-
sessionId: options.context?.sessionId ||
|
|
3292
|
-
"unknown",
|
|
3187
|
+
sessionId: options.context?.sessionId || "unknown",
|
|
3293
3188
|
});
|
|
3294
3189
|
try {
|
|
3295
3190
|
const abortedResult = {
|
|
@@ -3302,9 +3197,7 @@ Current user's request: ${currentInput}`;
|
|
|
3302
3197
|
}
|
|
3303
3198
|
catch (storeError) {
|
|
3304
3199
|
logger.warn(`[${functionTag}] Failed to store conversation turn after abort`, {
|
|
3305
|
-
error: storeError instanceof Error
|
|
3306
|
-
? storeError.message
|
|
3307
|
-
: String(storeError),
|
|
3200
|
+
error: storeError instanceof Error ? storeError.message : String(storeError),
|
|
3308
3201
|
});
|
|
3309
3202
|
}
|
|
3310
3203
|
}
|
|
@@ -3321,9 +3214,7 @@ Current user's request: ${currentInput}`;
|
|
|
3321
3214
|
catch (spanError) {
|
|
3322
3215
|
internalSpan.setStatus({
|
|
3323
3216
|
code: SpanStatusCode.ERROR,
|
|
3324
|
-
message: spanError instanceof Error
|
|
3325
|
-
? spanError.message
|
|
3326
|
-
: String(spanError),
|
|
3217
|
+
message: spanError instanceof Error ? spanError.message : String(spanError),
|
|
3327
3218
|
});
|
|
3328
3219
|
throw spanError;
|
|
3329
3220
|
}
|
|
@@ -3403,8 +3294,7 @@ Current user's request: ${currentInput}`;
|
|
|
3403
3294
|
* Attempt MCP generation with retry logic
|
|
3404
3295
|
*/
|
|
3405
3296
|
async attemptMCPGeneration(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag) {
|
|
3406
|
-
if (!options.disableTools &&
|
|
3407
|
-
!(options.tts?.enabled && !options.tts?.useAiResponse)) {
|
|
3297
|
+
if (!options.disableTools && !(options.tts?.enabled && !options.tts?.useAiResponse)) {
|
|
3408
3298
|
return await this.performMCPGenerationRetries(options, generateInternalId, generateInternalStartTime, generateInternalHrTimeStart, functionTag);
|
|
3409
3299
|
}
|
|
3410
3300
|
return null;
|
|
@@ -3426,9 +3316,7 @@ Current user's request: ${currentInput}`;
|
|
|
3426
3316
|
try {
|
|
3427
3317
|
logger.debug(`[${functionTag}] Attempting MCP generation (attempt ${attempt}/${maxAttempts})...`);
|
|
3428
3318
|
const mcpResult = await this.tryMCPGeneration(options);
|
|
3429
|
-
if (mcpResult &&
|
|
3430
|
-
(mcpResult.content ||
|
|
3431
|
-
(mcpResult.toolExecutions && mcpResult.toolExecutions.length > 0))) {
|
|
3319
|
+
if (mcpResult && (mcpResult.content || (mcpResult.toolExecutions && mcpResult.toolExecutions.length > 0))) {
|
|
3432
3320
|
logger.debug(`[${functionTag}] MCP generation successful on attempt ${attempt}`, {
|
|
3433
3321
|
contentLength: mcpResult.content?.length || 0,
|
|
3434
3322
|
toolsUsed: mcpResult.toolsUsed?.length || 0,
|
|
@@ -3459,11 +3347,7 @@ Current user's request: ${currentInput}`;
|
|
|
3459
3347
|
// NL-007: Record retry error for observability
|
|
3460
3348
|
retryCount++;
|
|
3461
3349
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
3462
|
-
const errCode = error instanceof NeuroLinkError
|
|
3463
|
-
? error.code
|
|
3464
|
-
: error instanceof Error
|
|
3465
|
-
? error.name
|
|
3466
|
-
: "UNKNOWN";
|
|
3350
|
+
const errCode = error instanceof NeuroLinkError ? error.code : error instanceof Error ? error.name : "UNKNOWN";
|
|
3467
3351
|
retryErrors.push({ code: errCode, message: errMsg.substring(0, 500) });
|
|
3468
3352
|
logger.debug(`[${functionTag}] MCP generation failed on attempt ${attempt}/${maxAttempts}`, {
|
|
3469
3353
|
error: errMsg,
|
|
@@ -3482,11 +3366,8 @@ Current user's request: ${currentInput}`;
|
|
|
3482
3366
|
const isNonRetryable = isContextOverflowError(error) ||
|
|
3483
3367
|
isToolError ||
|
|
3484
3368
|
isNonRetryableProviderError(error) ||
|
|
3485
|
-
(error instanceof Error &&
|
|
3486
|
-
|
|
3487
|
-
false) ||
|
|
3488
|
-
(error instanceof Error &&
|
|
3489
|
-
error.statusCode === 400);
|
|
3369
|
+
(error instanceof Error && error.isRetryable === false) ||
|
|
3370
|
+
(error instanceof Error && error.statusCode === 400);
|
|
3490
3371
|
if (isNonRetryable) {
|
|
3491
3372
|
logger.debug(`[${functionTag}] Non-retryable error detected, skipping remaining retries`);
|
|
3492
3373
|
break;
|
|
@@ -3522,8 +3403,7 @@ Current user's request: ${currentInput}`;
|
|
|
3522
3403
|
throw new DOMException("The operation was aborted", "AbortError");
|
|
3523
3404
|
}
|
|
3524
3405
|
// 🚀 EXHAUSTIVE LOGGING POINT T001: TRY MCP GENERATION ENTRY
|
|
3525
|
-
const requestId = options.context?.requestId ||
|
|
3526
|
-
"unknown";
|
|
3406
|
+
const requestId = options.context?.requestId || "unknown";
|
|
3527
3407
|
const tryMCPId = `try-mcp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3528
3408
|
const tryMCPStartTime = Date.now();
|
|
3529
3409
|
const tryMCPHrTimeStart = process.hrtime.bigint();
|
|
@@ -3551,9 +3431,7 @@ Current user's request: ${currentInput}`;
|
|
|
3551
3431
|
}
|
|
3552
3432
|
// Context creation removed - was never used
|
|
3553
3433
|
// Determine provider
|
|
3554
|
-
const providerName = options.provider === "auto" || !options.provider
|
|
3555
|
-
? await getBestProvider()
|
|
3556
|
-
: options.provider;
|
|
3434
|
+
const providerName = options.provider === "auto" || !options.provider ? await getBestProvider() : options.provider;
|
|
3557
3435
|
// Get available tools
|
|
3558
3436
|
let availableTools = await this.getAllAvailableTools();
|
|
3559
3437
|
// NL-001: Filter out tools with OPEN circuit breakers
|
|
@@ -3563,8 +3441,7 @@ Current user's request: ${currentInput}`;
|
|
|
3563
3441
|
availableTools = availableTools.filter((t) => cbFilteredNames.has(t.name));
|
|
3564
3442
|
// Apply per-call tool filtering for system prompt tool descriptions
|
|
3565
3443
|
availableTools = this.applyToolInfoFiltering(availableTools, options);
|
|
3566
|
-
const targetTool = availableTools.find((t) => t.name.includes("SuccessRateSRByTime") ||
|
|
3567
|
-
t.name.includes("juspay-analytics"));
|
|
3444
|
+
const targetTool = availableTools.find((t) => t.name.includes("SuccessRateSRByTime") || t.name.includes("juspay-analytics"));
|
|
3568
3445
|
logger.debug("Available tools for AI prompt generation", {
|
|
3569
3446
|
toolsCount: availableTools.length,
|
|
3570
3447
|
toolNames: availableTools.map((t) => t.name),
|
|
@@ -3598,9 +3475,7 @@ Current user's request: ${currentInput}`;
|
|
|
3598
3475
|
logger.debug("[Observability] System prompt metadata", {
|
|
3599
3476
|
requestId,
|
|
3600
3477
|
systemPromptLength: enhancedSystemPrompt.length,
|
|
3601
|
-
systemPromptHash: enhancedSystemPrompt.length > 0
|
|
3602
|
-
? `sha256:${enhancedSystemPrompt.slice(0, 8)}...`
|
|
3603
|
-
: "empty",
|
|
3478
|
+
systemPromptHash: enhancedSystemPrompt.length > 0 ? `sha256:${enhancedSystemPrompt.slice(0, 8)}...` : "empty",
|
|
3604
3479
|
hasCustomSystemPrompt: !!options.systemPrompt,
|
|
3605
3480
|
});
|
|
3606
3481
|
// Get conversation messages for context
|
|
@@ -3627,9 +3502,7 @@ Current user's request: ${currentInput}`;
|
|
|
3627
3502
|
index: i,
|
|
3628
3503
|
role: msg.role,
|
|
3629
3504
|
contentLength,
|
|
3630
|
-
contentPreview: typeof msg.content === "string"
|
|
3631
|
-
? msg.content.substring(0, 200)
|
|
3632
|
-
: "[multimodal]",
|
|
3505
|
+
contentPreview: typeof msg.content === "string" ? msg.content.substring(0, 200) : "[multimodal]",
|
|
3633
3506
|
};
|
|
3634
3507
|
}),
|
|
3635
3508
|
});
|
|
@@ -3670,8 +3543,7 @@ Current user's request: ${currentInput}`;
|
|
|
3670
3543
|
const compactionSessionId = this.getCompactionSessionId(options);
|
|
3671
3544
|
if (budgetResult.shouldCompact &&
|
|
3672
3545
|
this.conversationMemory &&
|
|
3673
|
-
messageCount >
|
|
3674
|
-
(this.lastCompactionMessageCount.get(compactionSessionId) ?? 0)) {
|
|
3546
|
+
messageCount > (this.lastCompactionMessageCount.get(compactionSessionId) ?? 0)) {
|
|
3675
3547
|
logger.info("[NeuroLink] Context budget exceeded, triggering auto-compaction", {
|
|
3676
3548
|
usageRatio: budgetResult.usageRatio,
|
|
3677
3549
|
estimatedTokens: budgetResult.estimatedInputTokens,
|
|
@@ -3679,10 +3551,8 @@ Current user's request: ${currentInput}`;
|
|
|
3679
3551
|
});
|
|
3680
3552
|
const compactor = new ContextCompactor({
|
|
3681
3553
|
provider: providerName,
|
|
3682
|
-
summarizationProvider: this.conversationMemoryConfig?.conversationMemory
|
|
3683
|
-
|
|
3684
|
-
summarizationModel: this.conversationMemoryConfig?.conversationMemory
|
|
3685
|
-
?.summarizationModel,
|
|
3554
|
+
summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
|
|
3555
|
+
summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
3686
3556
|
});
|
|
3687
3557
|
const compactionResult = await compactor.compact(conversationMessages, budgetResult.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, requestId);
|
|
3688
3558
|
if (compactionResult.compacted) {
|
|
@@ -3862,18 +3732,12 @@ Current user's request: ${currentInput}`;
|
|
|
3862
3732
|
];
|
|
3863
3733
|
const requestedProvider = options.provider === "auto" ? undefined : options.provider;
|
|
3864
3734
|
// Check for orchestrated preferred provider in context
|
|
3865
|
-
const preferredOrchestrated = options.context &&
|
|
3866
|
-
|
|
3867
|
-
"__orchestratedPreferredProvider" in options.context
|
|
3868
|
-
? options.context
|
|
3869
|
-
.__orchestratedPreferredProvider
|
|
3735
|
+
const preferredOrchestrated = options.context && typeof options.context === "object" && "__orchestratedPreferredProvider" in options.context
|
|
3736
|
+
? options.context.__orchestratedPreferredProvider
|
|
3870
3737
|
: undefined;
|
|
3871
3738
|
// Build provider list with orchestrated preference first, then fallback to full list
|
|
3872
3739
|
const tryProviders = preferredOrchestrated
|
|
3873
|
-
? [
|
|
3874
|
-
preferredOrchestrated,
|
|
3875
|
-
...providerPriority.filter((p) => p !== preferredOrchestrated),
|
|
3876
|
-
]
|
|
3740
|
+
? [preferredOrchestrated, ...providerPriority.filter((p) => p !== preferredOrchestrated)]
|
|
3877
3741
|
: requestedProvider
|
|
3878
3742
|
? [requestedProvider]
|
|
3879
3743
|
: providerPriority;
|
|
@@ -3893,8 +3757,7 @@ Current user's request: ${currentInput}`;
|
|
|
3893
3757
|
logger.debug(`[${functionTag}] Attempting provider: ${providerName}`);
|
|
3894
3758
|
// Get conversation messages for context (use pre-compacted if provided)
|
|
3895
3759
|
const optionsWithMessages = options;
|
|
3896
|
-
let conversationMessages = optionsWithMessages.conversationMessages
|
|
3897
|
-
?.length
|
|
3760
|
+
let conversationMessages = optionsWithMessages.conversationMessages?.length
|
|
3898
3761
|
? optionsWithMessages.conversationMessages
|
|
3899
3762
|
: await getConversationMessages(this.conversationMemory, options);
|
|
3900
3763
|
// Pre-generation budget check
|
|
@@ -3905,22 +3768,17 @@ Current user's request: ${currentInput}`;
|
|
|
3905
3768
|
systemPrompt: options.systemPrompt,
|
|
3906
3769
|
conversationMessages: conversationMessages,
|
|
3907
3770
|
currentPrompt: options.prompt,
|
|
3908
|
-
toolDefinitions: options.tools
|
|
3909
|
-
? Object.values(options.tools)
|
|
3910
|
-
: undefined,
|
|
3771
|
+
toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
|
|
3911
3772
|
});
|
|
3912
3773
|
const dpgMessageCount = conversationMessages?.length || 0;
|
|
3913
3774
|
const dpgCompactionSessionId = this.getCompactionSessionId(options);
|
|
3914
3775
|
if (budgetCheck.shouldCompact &&
|
|
3915
3776
|
this.conversationMemory &&
|
|
3916
|
-
dpgMessageCount >
|
|
3917
|
-
(this.lastCompactionMessageCount.get(dpgCompactionSessionId) ?? 0)) {
|
|
3777
|
+
dpgMessageCount > (this.lastCompactionMessageCount.get(dpgCompactionSessionId) ?? 0)) {
|
|
3918
3778
|
const compactor = new ContextCompactor({
|
|
3919
3779
|
provider: providerName,
|
|
3920
|
-
summarizationProvider: this.conversationMemoryConfig?.conversationMemory
|
|
3921
|
-
|
|
3922
|
-
summarizationModel: this.conversationMemoryConfig?.conversationMemory
|
|
3923
|
-
?.summarizationModel,
|
|
3780
|
+
summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
|
|
3781
|
+
summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
3924
3782
|
});
|
|
3925
3783
|
const compactionResult = await compactor.compact(conversationMessages, budgetCheck.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, options.context?.requestId);
|
|
3926
3784
|
if (compactionResult.compacted) {
|
|
@@ -3936,9 +3794,7 @@ Current user's request: ${currentInput}`;
|
|
|
3936
3794
|
systemPrompt: options.systemPrompt,
|
|
3937
3795
|
conversationMessages: conversationMessages,
|
|
3938
3796
|
currentPrompt: options.prompt,
|
|
3939
|
-
toolDefinitions: options.tools
|
|
3940
|
-
? Object.values(options.tools)
|
|
3941
|
-
: undefined,
|
|
3797
|
+
toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
|
|
3942
3798
|
});
|
|
3943
3799
|
if (!postCompactBudget.withinBudget) {
|
|
3944
3800
|
logger.warn("[NeuroLink] directProviderGeneration: post-compaction still over budget, emergency truncation", {
|
|
@@ -3954,9 +3810,7 @@ Current user's request: ${currentInput}`;
|
|
|
3954
3810
|
systemPrompt: options.systemPrompt,
|
|
3955
3811
|
conversationMessages: conversationMessages,
|
|
3956
3812
|
currentPrompt: options.prompt,
|
|
3957
|
-
toolDefinitions: options.tools
|
|
3958
|
-
? Object.values(options.tools)
|
|
3959
|
-
: undefined,
|
|
3813
|
+
toolDefinitions: options.tools ? Object.values(options.tools) : undefined,
|
|
3960
3814
|
});
|
|
3961
3815
|
if (!finalBudget.withinBudget) {
|
|
3962
3816
|
throw new ContextBudgetExceededError(`Context exceeds model budget after all compaction stages. ` +
|
|
@@ -4214,10 +4068,7 @@ Current user's request: ${currentInput}`;
|
|
|
4214
4068
|
options = { ...options };
|
|
4215
4069
|
// Set metrics trace context for parent-child span linking
|
|
4216
4070
|
const metricsTraceId = crypto.randomUUID().replace(/-/g, "");
|
|
4217
|
-
const metricsParentSpanId = crypto
|
|
4218
|
-
.randomUUID()
|
|
4219
|
-
.replace(/-/g, "")
|
|
4220
|
-
.substring(0, 16);
|
|
4071
|
+
const metricsParentSpanId = crypto.randomUUID().replace(/-/g, "").substring(0, 16);
|
|
4221
4072
|
// Scope trace context to this request via AsyncLocalStorage
|
|
4222
4073
|
// so concurrent generate/stream calls don't race.
|
|
4223
4074
|
return metricsTraceContextStorage.run({ traceId: metricsTraceId, parentSpanId: metricsParentSpanId }, async () => {
|
|
@@ -4276,9 +4127,7 @@ Current user's request: ${currentInput}`;
|
|
|
4276
4127
|
}
|
|
4277
4128
|
catch (err) {
|
|
4278
4129
|
// Rethrow auth errors as-is; wrap anything else
|
|
4279
|
-
if (err instanceof Error &&
|
|
4280
|
-
"feature" in err &&
|
|
4281
|
-
err.feature === "Auth") {
|
|
4130
|
+
if (err instanceof Error && "feature" in err && err.feature === "Auth") {
|
|
4282
4131
|
throw err;
|
|
4283
4132
|
}
|
|
4284
4133
|
throw AuthError.create("PROVIDER_ERROR", `Auth token validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4331,9 +4180,9 @@ Current user's request: ${currentInput}`;
|
|
|
4331
4180
|
enabled: true,
|
|
4332
4181
|
config: {
|
|
4333
4182
|
...options.middleware?.middlewareConfig?.lifecycle?.config,
|
|
4334
|
-
onFinish: options.onFinish,
|
|
4335
|
-
onError: options.onError,
|
|
4336
|
-
onChunk: options.onChunk,
|
|
4183
|
+
...(options.onFinish !== undefined ? { onFinish: options.onFinish } : {}),
|
|
4184
|
+
...(options.onError !== undefined ? { onError: options.onError } : {}),
|
|
4185
|
+
...(options.onChunk !== undefined ? { onChunk: options.onChunk } : {}),
|
|
4337
4186
|
},
|
|
4338
4187
|
},
|
|
4339
4188
|
},
|
|
@@ -4372,7 +4221,12 @@ Current user's request: ${currentInput}`;
|
|
|
4372
4221
|
try {
|
|
4373
4222
|
// Prepare options: init memory, MCP, orchestration, Ollama auto-disable, tool detection
|
|
4374
4223
|
const { enhancedOptions, factoryResult } = await this.prepareStreamOptions(options, streamId, startTime, hrTimeStart);
|
|
4375
|
-
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
|
+
};
|
|
4376
4230
|
// Update span with resolved provider name
|
|
4377
4231
|
streamSpan.setAttribute(ATTR.NL_PROVIDER, providerName || "unknown");
|
|
4378
4232
|
let accumulatedContent = "";
|
|
@@ -4394,9 +4248,7 @@ Current user's request: ${currentInput}`;
|
|
|
4394
4248
|
try {
|
|
4395
4249
|
for await (const chunk of mcpStream) {
|
|
4396
4250
|
chunkCount++;
|
|
4397
|
-
if (chunk &&
|
|
4398
|
-
"content" in chunk &&
|
|
4399
|
-
typeof chunk.content === "string") {
|
|
4251
|
+
if (chunk && "content" in chunk && typeof chunk.content === "string") {
|
|
4400
4252
|
accumulatedContent += chunk.content;
|
|
4401
4253
|
self.emitter.emit("response:chunk", chunk.content);
|
|
4402
4254
|
// Emit stream:chunk event (Observability Solution 8)
|
|
@@ -4412,8 +4264,12 @@ Current user's request: ${currentInput}`;
|
|
|
4412
4264
|
}
|
|
4413
4265
|
yield chunk;
|
|
4414
4266
|
}
|
|
4415
|
-
if (chunkCount === 0 &&
|
|
4416
|
-
|
|
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) => {
|
|
4417
4273
|
accumulatedContent += content;
|
|
4418
4274
|
});
|
|
4419
4275
|
}
|
|
@@ -4421,9 +4277,7 @@ Current user's request: ${currentInput}`;
|
|
|
4421
4277
|
// When fallback took over, attribute the completion to the
|
|
4422
4278
|
// fallback provider so downstream telemetry reflects reality.
|
|
4423
4279
|
const effectiveProvider = metadata.fallbackProvider ?? providerName;
|
|
4424
|
-
const effectiveModel = metadata.fallbackModel ??
|
|
4425
|
-
streamModel ??
|
|
4426
|
-
enhancedOptions.model;
|
|
4280
|
+
const effectiveModel = metadata.fallbackModel ?? streamModel ?? enhancedOptions.model;
|
|
4427
4281
|
// Resolve analytics promise to get final token usage
|
|
4428
4282
|
let resolvedUsage = streamUsage;
|
|
4429
4283
|
if (!resolvedUsage && streamAnalytics) {
|
|
@@ -4442,8 +4296,7 @@ Current user's request: ${currentInput}`;
|
|
|
4442
4296
|
content: accumulatedContent,
|
|
4443
4297
|
provider: effectiveProvider,
|
|
4444
4298
|
model: effectiveModel,
|
|
4445
|
-
prompt: enhancedOptions.input?.text ||
|
|
4446
|
-
enhancedOptions.prompt,
|
|
4299
|
+
prompt: enhancedOptions.input?.text || enhancedOptions.prompt,
|
|
4447
4300
|
metadata: {
|
|
4448
4301
|
chunkCount,
|
|
4449
4302
|
totalLength: accumulatedContent.length,
|
|
@@ -4497,10 +4350,7 @@ Current user's request: ${currentInput}`;
|
|
|
4497
4350
|
if (primaryFailed) {
|
|
4498
4351
|
streamSpan.setStatus({
|
|
4499
4352
|
code: SpanStatusCode.ERROR,
|
|
4500
|
-
message: metadata.error ||
|
|
4501
|
-
(streamError instanceof Error
|
|
4502
|
-
? streamError.message
|
|
4503
|
-
: String(streamError)),
|
|
4353
|
+
message: metadata.error || (streamError instanceof Error ? streamError.message : String(streamError)),
|
|
4504
4354
|
});
|
|
4505
4355
|
}
|
|
4506
4356
|
else {
|
|
@@ -4527,10 +4377,18 @@ Current user's request: ${currentInput}`;
|
|
|
4527
4377
|
}
|
|
4528
4378
|
})();
|
|
4529
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
|
+
}
|
|
4530
4389
|
const responseTime = Date.now() - startTime;
|
|
4531
4390
|
// Accumulate session cost for budget tracking
|
|
4532
|
-
if (streamResult.analytics?.cost &&
|
|
4533
|
-
streamResult.analytics.cost > 0) {
|
|
4391
|
+
if (streamResult.analytics?.cost && streamResult.analytics.cost > 0) {
|
|
4534
4392
|
this._sessionCostUsd += streamResult.analytics.cost;
|
|
4535
4393
|
}
|
|
4536
4394
|
this.emitStreamEndEvents(streamResult);
|
|
@@ -4547,6 +4405,9 @@ Current user's request: ${currentInput}`;
|
|
|
4547
4405
|
});
|
|
4548
4406
|
}
|
|
4549
4407
|
catch (error) {
|
|
4408
|
+
if (options.disableInternalFallback) {
|
|
4409
|
+
throw error;
|
|
4410
|
+
}
|
|
4550
4411
|
return this.handleStreamError(error, options, startTime, streamId, undefined, undefined);
|
|
4551
4412
|
}
|
|
4552
4413
|
});
|
|
@@ -4575,8 +4436,7 @@ Current user's request: ${currentInput}`;
|
|
|
4575
4436
|
// Initialize MCP
|
|
4576
4437
|
await this.initializeMCP();
|
|
4577
4438
|
// Memory retrieval
|
|
4578
|
-
if (this.shouldReadMemory(options.memory, options.context?.userId) &&
|
|
4579
|
-
options.context?.userId) {
|
|
4439
|
+
if (this.shouldReadMemory(options.memory, options.context?.userId) && options.context?.userId) {
|
|
4580
4440
|
try {
|
|
4581
4441
|
options.input.text = await this.retrieveMemory(options.input.text, options.context.userId, options.memory?.additionalUsers);
|
|
4582
4442
|
logger.debug("Memory retrieval successful");
|
|
@@ -4621,8 +4481,7 @@ Current user's request: ${currentInput}`;
|
|
|
4621
4481
|
if (!options.tools) {
|
|
4622
4482
|
options.tools = {};
|
|
4623
4483
|
}
|
|
4624
|
-
options.tools[ragResult.toolName] =
|
|
4625
|
-
ragResult.tool;
|
|
4484
|
+
options.tools[ragResult.toolName] = ragResult.tool;
|
|
4626
4485
|
// Inject RAG-aware system prompt so the AI uses the RAG tool first
|
|
4627
4486
|
const ragSystemInstruction = [
|
|
4628
4487
|
`\n\nIMPORTANT: You have a tool called "${ragResult.toolName}" that searches through`,
|
|
@@ -4631,8 +4490,7 @@ Current user's request: ${currentInput}`;
|
|
|
4631
4490
|
`This tool searches your local knowledge base of pre-loaded documents and is the primary source of truth.`,
|
|
4632
4491
|
`Do NOT use websearchGrounding or any web search tools when the answer can be found in the loaded documents.`,
|
|
4633
4492
|
].join(" ");
|
|
4634
|
-
options.systemPrompt =
|
|
4635
|
-
(options.systemPrompt || "") + ragSystemInstruction;
|
|
4493
|
+
options.systemPrompt = (options.systemPrompt || "") + ragSystemInstruction;
|
|
4636
4494
|
logger.info("[RAG] Tool injected into stream()", {
|
|
4637
4495
|
toolName: ragResult.toolName,
|
|
4638
4496
|
filesLoaded: ragResult.filesLoaded,
|
|
@@ -4660,8 +4518,7 @@ Current user's request: ${currentInput}`;
|
|
|
4660
4518
|
* Prevents overwhelming smaller models with massive tool descriptions in the system message.
|
|
4661
4519
|
*/
|
|
4662
4520
|
async autoDisableOllamaStreamTools(options) {
|
|
4663
|
-
if ((options.provider === "ollama" ||
|
|
4664
|
-
options.provider?.toLowerCase().includes("ollama")) &&
|
|
4521
|
+
if ((options.provider === "ollama" || options.provider?.toLowerCase().includes("ollama")) &&
|
|
4665
4522
|
!options.disableTools) {
|
|
4666
4523
|
const { ModelConfigurationManager } = await import("./core/modelConfiguration.js");
|
|
4667
4524
|
const modelConfig = ModelConfigurationManager.getInstance();
|
|
@@ -4745,7 +4602,7 @@ Current user's request: ${currentInput}`;
|
|
|
4745
4602
|
* Handle fallback when the primary stream returns 0 chunks.
|
|
4746
4603
|
* Yields chunks from a fallback provider and updates metadata accordingly.
|
|
4747
4604
|
*/
|
|
4748
|
-
async *handleStreamFallback(metadata, originalPrompt, enhancedOptions, providerName, _accumulatedContent, appendContent) {
|
|
4605
|
+
async *handleStreamFallback(metadata, streamState, originalPrompt, enhancedOptions, providerName, _accumulatedContent, appendContent) {
|
|
4749
4606
|
metadata.fallbackAttempted = true;
|
|
4750
4607
|
const errorMsg = "Stream completed with 0 chunks (possible guardrails block)";
|
|
4751
4608
|
metadata.error = errorMsg;
|
|
@@ -4803,18 +4660,23 @@ Current user's request: ${currentInput}`;
|
|
|
4803
4660
|
model: fallbackRoute.model,
|
|
4804
4661
|
conversationMessages,
|
|
4805
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
|
+
}
|
|
4806
4670
|
let fallbackChunkCount = 0;
|
|
4807
4671
|
for await (const fallbackChunk of fallbackResult.stream) {
|
|
4808
4672
|
fallbackChunkCount++;
|
|
4809
|
-
if (fallbackChunk &&
|
|
4810
|
-
"content" in fallbackChunk &&
|
|
4811
|
-
typeof fallbackChunk.content === "string") {
|
|
4673
|
+
if (fallbackChunk && "content" in fallbackChunk && typeof fallbackChunk.content === "string") {
|
|
4812
4674
|
appendContent(fallbackChunk.content);
|
|
4813
4675
|
this.emitter.emit("response:chunk", fallbackChunk.content);
|
|
4814
4676
|
}
|
|
4815
4677
|
yield fallbackChunk;
|
|
4816
4678
|
}
|
|
4817
|
-
if (fallbackChunkCount === 0) {
|
|
4679
|
+
if (fallbackChunkCount === 0 && fallbackToolCalls.length === 0 && fallbackToolResults.length === 0) {
|
|
4818
4680
|
throw new Error(`Fallback provider ${fallbackRoute.provider} also returned 0 chunks`);
|
|
4819
4681
|
}
|
|
4820
4682
|
// Fallback succeeded - likely guardrails blocked primary
|
|
@@ -4823,9 +4685,7 @@ Current user's request: ${currentInput}`;
|
|
|
4823
4685
|
metadata.guardrailsBlocked = true;
|
|
4824
4686
|
}
|
|
4825
4687
|
catch (fallbackError) {
|
|
4826
|
-
const fallbackErrorMsg = fallbackError instanceof Error
|
|
4827
|
-
? fallbackError.message
|
|
4828
|
-
: String(fallbackError);
|
|
4688
|
+
const fallbackErrorMsg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
4829
4689
|
metadata.error = `${errorMsg}; Fallback failed: ${fallbackErrorMsg}`;
|
|
4830
4690
|
logger.error("Fallback provider failed", {
|
|
4831
4691
|
fallbackProvider: fallbackRoute.provider,
|
|
@@ -4839,22 +4699,19 @@ Current user's request: ${currentInput}`;
|
|
|
4839
4699
|
* Handles conversation memory storage in the background.
|
|
4840
4700
|
*/
|
|
4841
4701
|
async storeStreamConversationMemory(params) {
|
|
4842
|
-
const { enhancedOptions, providerName, originalPrompt, accumulatedContent, startTime, eventSequence
|
|
4702
|
+
const { enhancedOptions, providerName, originalPrompt, accumulatedContent, startTime, eventSequence } = params;
|
|
4843
4703
|
// Guard: skip storing if no meaningful content was produced (no text AND no tool activity)
|
|
4844
4704
|
const hasToolEvents = eventSequence.some((e) => e.type === "tool:start" || e.type === "tool:end");
|
|
4845
4705
|
if (!accumulatedContent.trim() && !hasToolEvents) {
|
|
4846
4706
|
logger.warn("[NeuroLink.stream] Skipping conversation turn storage — no text content or tool activity", {
|
|
4847
|
-
sessionId: enhancedOptions.context
|
|
4848
|
-
?.sessionId,
|
|
4707
|
+
sessionId: enhancedOptions.context?.sessionId,
|
|
4849
4708
|
});
|
|
4850
4709
|
return;
|
|
4851
4710
|
}
|
|
4852
4711
|
// Store memory after stream consumption is complete
|
|
4853
4712
|
if (this.conversationMemory && enhancedOptions.context?.sessionId) {
|
|
4854
|
-
const sessionId = enhancedOptions.context
|
|
4855
|
-
|
|
4856
|
-
const userId = enhancedOptions.context
|
|
4857
|
-
?.userId;
|
|
4713
|
+
const sessionId = enhancedOptions.context?.sessionId;
|
|
4714
|
+
const userId = enhancedOptions.context?.userId;
|
|
4858
4715
|
let providerDetails;
|
|
4859
4716
|
if (enhancedOptions.model) {
|
|
4860
4717
|
providerDetails = {
|
|
@@ -4873,8 +4730,7 @@ Current user's request: ${currentInput}`;
|
|
|
4873
4730
|
providerDetails,
|
|
4874
4731
|
enableSummarization: enhancedOptions.enableSummarization,
|
|
4875
4732
|
events: eventSequence.length > 0 ? eventSequence : undefined,
|
|
4876
|
-
requestId: enhancedOptions.context
|
|
4877
|
-
?.requestId,
|
|
4733
|
+
requestId: enhancedOptions.context?.requestId,
|
|
4878
4734
|
});
|
|
4879
4735
|
this.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": "stream" }, Date.now() - memStoreStart, SpanStatus.OK);
|
|
4880
4736
|
logger.debug("[NeuroLink.stream] Stored conversation turn with events", {
|
|
@@ -4904,8 +4760,7 @@ Current user's request: ${currentInput}`;
|
|
|
4904
4760
|
validationStartTimeNs: validationStartTime.toString(),
|
|
4905
4761
|
message: "Starting comprehensive input validation process",
|
|
4906
4762
|
});
|
|
4907
|
-
const hasText = typeof options?.input?.text === "string" &&
|
|
4908
|
-
options.input.text.trim().length > 0;
|
|
4763
|
+
const hasText = typeof options?.input?.text === "string" && options.input.text.trim().length > 0;
|
|
4909
4764
|
// Accept audio when frames are present; sampleRateHz is optional (defaults applied later)
|
|
4910
4765
|
const hasAudio = !!(options?.input?.audio &&
|
|
4911
4766
|
options.input.audio.frames &&
|
|
@@ -4984,12 +4839,10 @@ Current user's request: ${currentInput}`;
|
|
|
4984
4839
|
const streamCompactionSessionId = this.getCompactionSessionId(options);
|
|
4985
4840
|
if (streamBudget.shouldCompact &&
|
|
4986
4841
|
(hasCallerConversationHistory || this.conversationMemory) &&
|
|
4987
|
-
streamMessageCount >
|
|
4988
|
-
(this.lastCompactionMessageCount.get(streamCompactionSessionId) ?? 0)) {
|
|
4842
|
+
streamMessageCount > (this.lastCompactionMessageCount.get(streamCompactionSessionId) ?? 0)) {
|
|
4989
4843
|
const compactor = new ContextCompactor({
|
|
4990
4844
|
provider: providerName,
|
|
4991
|
-
summarizationProvider: this.conversationMemoryConfig?.conversationMemory
|
|
4992
|
-
?.summarizationProvider,
|
|
4845
|
+
summarizationProvider: this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
|
|
4993
4846
|
summarizationModel: this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
4994
4847
|
});
|
|
4995
4848
|
const compactionResult = await compactor.compact(conversationMessages, streamBudget.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, options.context?.requestId);
|
|
@@ -5059,6 +4912,9 @@ Current user's request: ${currentInput}`;
|
|
|
5059
4912
|
provider: providerName,
|
|
5060
4913
|
usage: streamResult.usage,
|
|
5061
4914
|
model: streamResult.model || options.model,
|
|
4915
|
+
finishReason: streamResult.finishReason,
|
|
4916
|
+
toolCalls: streamResult.toolCalls ?? [],
|
|
4917
|
+
toolResults: streamResult.toolResults ?? [],
|
|
5062
4918
|
analytics: streamResult.analytics,
|
|
5063
4919
|
};
|
|
5064
4920
|
}
|
|
@@ -5131,8 +4987,7 @@ Current user's request: ${currentInput}`;
|
|
|
5131
4987
|
parentSpanId: traceCtx?.parentSpanId,
|
|
5132
4988
|
});
|
|
5133
4989
|
failedSpan = SpanSerializer.endSpan(failedSpan, SpanStatus.ERROR);
|
|
5134
|
-
failedSpan.statusMessage =
|
|
5135
|
-
error instanceof Error ? error.message : String(error);
|
|
4990
|
+
failedSpan.statusMessage = error instanceof Error ? error.message : String(error);
|
|
5136
4991
|
failedSpan.durationMs = Date.now() - startTime;
|
|
5137
4992
|
this.metricsAggregator.recordSpan(failedSpan);
|
|
5138
4993
|
getMetricsAggregator().recordSpan(failedSpan);
|
|
@@ -5156,9 +5011,7 @@ Current user's request: ${currentInput}`;
|
|
|
5156
5011
|
const fallbackProcessedStream = (async function* (self) {
|
|
5157
5012
|
try {
|
|
5158
5013
|
for await (const chunk of fallbackStreamResult.stream) {
|
|
5159
|
-
if (chunk &&
|
|
5160
|
-
"content" in chunk &&
|
|
5161
|
-
typeof chunk.content === "string") {
|
|
5014
|
+
if (chunk && "content" in chunk && typeof chunk.content === "string") {
|
|
5162
5015
|
fallbackAccumulatedContent += chunk.content;
|
|
5163
5016
|
// Emit chunk event
|
|
5164
5017
|
self.emitter.emit("response:chunk", chunk.content);
|
|
@@ -5177,12 +5030,9 @@ Current user's request: ${currentInput}`;
|
|
|
5177
5030
|
}
|
|
5178
5031
|
// Store memory after fallback stream consumption is complete
|
|
5179
5032
|
// Guard: skip storing if fallback accumulated content is empty
|
|
5180
|
-
if (self.conversationMemory &&
|
|
5181
|
-
enhancedOptions?.context?.sessionId &&
|
|
5182
|
-
fallbackAccumulatedContent.trim()) {
|
|
5033
|
+
if (self.conversationMemory && enhancedOptions?.context?.sessionId && fallbackAccumulatedContent.trim()) {
|
|
5183
5034
|
const sessionId = enhancedOptions?.context?.sessionId;
|
|
5184
|
-
const userId = enhancedOptions?.context
|
|
5185
|
-
?.userId;
|
|
5035
|
+
const userId = enhancedOptions?.context?.userId;
|
|
5186
5036
|
let providerDetails;
|
|
5187
5037
|
if (options.model) {
|
|
5188
5038
|
providerDetails = {
|
|
@@ -5201,8 +5051,7 @@ Current user's request: ${currentInput}`;
|
|
|
5201
5051
|
providerDetails,
|
|
5202
5052
|
enableSummarization: enhancedOptions?.enableSummarization,
|
|
5203
5053
|
requestId: enhancedOptions?.context?.requestId ||
|
|
5204
|
-
options.context
|
|
5205
|
-
?.requestId,
|
|
5054
|
+
options.context?.requestId,
|
|
5206
5055
|
});
|
|
5207
5056
|
self.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": "fallback-stream" }, Date.now() - memStoreStart, SpanStatus.OK);
|
|
5208
5057
|
}
|
|
@@ -5601,7 +5450,8 @@ Current user's request: ${currentInput}`;
|
|
|
5601
5450
|
// (direct executeTool() or AI SDK generateText() tool calling).
|
|
5602
5451
|
if (options?.timeout !== undefined &&
|
|
5603
5452
|
options.timeout > 0 &&
|
|
5604
|
-
Number.isFinite(options.timeout)
|
|
5453
|
+
Number.isFinite(options.timeout) &&
|
|
5454
|
+
typeof convertedTool.execute === "function") {
|
|
5605
5455
|
const originalExecute = convertedTool.execute;
|
|
5606
5456
|
const toolTimeout = options.timeout;
|
|
5607
5457
|
const toolName = name;
|
|
@@ -5610,9 +5460,7 @@ Current user's request: ${currentInput}`;
|
|
|
5610
5460
|
// Compose with any parent abortSignal from ToolExecutionOptions
|
|
5611
5461
|
const execOptions = args[1];
|
|
5612
5462
|
const parentSignal = execOptions?.abortSignal;
|
|
5613
|
-
const composedSignal = parentSignal
|
|
5614
|
-
? AbortSignal.any([parentSignal, timeoutSignal])
|
|
5615
|
-
: timeoutSignal;
|
|
5463
|
+
const composedSignal = parentSignal ? AbortSignal.any([parentSignal, timeoutSignal]) : timeoutSignal;
|
|
5616
5464
|
// Replace the abortSignal in execution options
|
|
5617
5465
|
const augmentedContext = {
|
|
5618
5466
|
...execOptions,
|
|
@@ -5623,7 +5471,7 @@ Current user's request: ${currentInput}`;
|
|
|
5623
5471
|
new Promise((_, reject) => {
|
|
5624
5472
|
composedSignal.addEventListener("abort", () => {
|
|
5625
5473
|
if (timeoutSignal.aborted) {
|
|
5626
|
-
reject(
|
|
5474
|
+
reject(ErrorFactory.toolTimeout(toolName, toolTimeout));
|
|
5627
5475
|
}
|
|
5628
5476
|
else {
|
|
5629
5477
|
reject(new DOMException("The operation was aborted", "AbortError"));
|
|
@@ -5669,9 +5517,7 @@ Current user's request: ${currentInput}`;
|
|
|
5669
5517
|
* @returns Current context or undefined if not set
|
|
5670
5518
|
*/
|
|
5671
5519
|
getToolContext() {
|
|
5672
|
-
return this.toolExecutionContext
|
|
5673
|
-
? { ...this.toolExecutionContext }
|
|
5674
|
-
: undefined;
|
|
5520
|
+
return this.toolExecutionContext ? { ...this.toolExecutionContext } : undefined;
|
|
5675
5521
|
}
|
|
5676
5522
|
/**
|
|
5677
5523
|
* Clear the tool execution context
|
|
@@ -5775,8 +5621,7 @@ Current user's request: ${currentInput}`;
|
|
|
5775
5621
|
typeof this.conversationMemory.updateAgenticLoopReport !== "function") {
|
|
5776
5622
|
throw new ConversationMemoryError("updateAgenticLoopReport is only supported with Redis conversation memory.", "CONFIG_ERROR");
|
|
5777
5623
|
}
|
|
5778
|
-
await withTimeout(this
|
|
5779
|
-
.conversationMemory.updateAgenticLoopReport(sessionId, userId, report), 5000);
|
|
5624
|
+
await withTimeout(this.conversationMemory.updateAgenticLoopReport(sessionId, userId, report), 5000);
|
|
5780
5625
|
}
|
|
5781
5626
|
/**
|
|
5782
5627
|
* Get all registered custom tools
|
|
@@ -5794,14 +5639,10 @@ Current user's request: ${currentInput}`;
|
|
|
5794
5639
|
description: tool.description,
|
|
5795
5640
|
hasParameters: !!tool.parameters,
|
|
5796
5641
|
parametersType: typeof tool.parameters,
|
|
5797
|
-
parametersKeys: tool.parameters && typeof tool.parameters === "object"
|
|
5798
|
-
? Object.keys(tool.parameters)
|
|
5799
|
-
: "NOT_OBJECT",
|
|
5642
|
+
parametersKeys: tool.parameters && typeof tool.parameters === "object" ? Object.keys(tool.parameters) : "NOT_OBJECT",
|
|
5800
5643
|
hasInputSchema: !!tool.inputSchema,
|
|
5801
5644
|
inputSchemaType: typeof tool.inputSchema,
|
|
5802
|
-
inputSchemaKeys: tool.inputSchema && typeof tool.inputSchema === "object"
|
|
5803
|
-
? Object.keys(tool.inputSchema)
|
|
5804
|
-
: "NOT_OBJECT",
|
|
5645
|
+
inputSchemaKeys: tool.inputSchema && typeof tool.inputSchema === "object" ? Object.keys(tool.inputSchema) : "NOT_OBJECT",
|
|
5805
5646
|
hasEffectiveSchema: !!effectiveSchema,
|
|
5806
5647
|
effectiveSchemaType: typeof effectiveSchema,
|
|
5807
5648
|
effectiveSchemaHasProperties: !!effectiveSchema?.properties,
|
|
@@ -5822,18 +5663,14 @@ Current user's request: ${currentInput}`;
|
|
|
5822
5663
|
execute: async (params, context) => {
|
|
5823
5664
|
// CONTEXT MERGING: Combine all available contexts for maximum information
|
|
5824
5665
|
const storedContext = this.toolExecutionContext || {};
|
|
5825
|
-
const runtimeContext = context && isNonNullObject(context)
|
|
5826
|
-
? context
|
|
5827
|
-
: {};
|
|
5666
|
+
const runtimeContext = context && isNonNullObject(context) ? context : {};
|
|
5828
5667
|
// Merge contexts with runtime context taking precedence
|
|
5829
5668
|
// This ensures we have the richest possible context for tool execution
|
|
5830
5669
|
const executionContext = {
|
|
5831
5670
|
...storedContext, // Base context from setToolContext (session, tokens, etc.)
|
|
5832
5671
|
...runtimeContext, // Runtime context from AI model (if any)
|
|
5833
5672
|
// Ensure we always have at least a sessionId for tracing
|
|
5834
|
-
sessionId: runtimeContext.sessionId ||
|
|
5835
|
-
storedContext.sessionId ||
|
|
5836
|
-
`fallback-${Date.now()}`,
|
|
5673
|
+
sessionId: runtimeContext.sessionId || storedContext.sessionId || `fallback-${Date.now()}`,
|
|
5837
5674
|
};
|
|
5838
5675
|
// Enhanced logging for context debugging
|
|
5839
5676
|
logger.debug("Tool execution context merged", {
|
|
@@ -5841,8 +5678,7 @@ Current user's request: ${currentInput}`;
|
|
|
5841
5678
|
storedContextKeys: Object.keys(storedContext),
|
|
5842
5679
|
runtimeContextKeys: Object.keys(runtimeContext),
|
|
5843
5680
|
finalContextKeys: Object.keys(executionContext),
|
|
5844
|
-
hasJuspayToken: !!executionContext
|
|
5845
|
-
.juspayToken,
|
|
5681
|
+
hasJuspayToken: !!executionContext.juspayToken,
|
|
5846
5682
|
hasShopId: !!executionContext.shopId,
|
|
5847
5683
|
sessionId: executionContext.sessionId,
|
|
5848
5684
|
});
|
|
@@ -5870,9 +5706,7 @@ Current user's request: ${currentInput}`;
|
|
|
5870
5706
|
toolMap.set(toolName, {
|
|
5871
5707
|
name: toolName,
|
|
5872
5708
|
description: toolDef.description || `File tool: ${toolName}`,
|
|
5873
|
-
inputSchema: typeof toolParams === "object" && toolParams !== null
|
|
5874
|
-
? toolParams
|
|
5875
|
-
: { type: "object", properties: {} },
|
|
5709
|
+
inputSchema: typeof toolParams === "object" && toolParams !== null ? toolParams : { type: "object", properties: {} },
|
|
5876
5710
|
execute: async (params) => {
|
|
5877
5711
|
return await toolDef.execute(params, {
|
|
5878
5712
|
toolCallId: `file-tool-${Date.now()}`,
|
|
@@ -5983,17 +5817,9 @@ Current user's request: ${currentInput}`;
|
|
|
5983
5817
|
// Determine tool type for span attributes
|
|
5984
5818
|
const externalTools = this.externalServerManager.getAllTools();
|
|
5985
5819
|
const externalTool = externalTools.find((tool) => tool.name === toolName);
|
|
5986
|
-
const toolType = externalTool
|
|
5987
|
-
? "mcp"
|
|
5988
|
-
: this.getCustomTools().has(toolName)
|
|
5989
|
-
? "custom"
|
|
5990
|
-
: "external";
|
|
5820
|
+
const toolType = externalTool ? "mcp" : this.getCustomTools().has(toolName) ? "custom" : "external";
|
|
5991
5821
|
// Compute truncated input size for the span
|
|
5992
|
-
const inputStr = typeof params === "string"
|
|
5993
|
-
? params
|
|
5994
|
-
: params
|
|
5995
|
-
? JSON.stringify(params)
|
|
5996
|
-
: "";
|
|
5822
|
+
const inputStr = typeof params === "string" ? params : params ? JSON.stringify(params) : "";
|
|
5997
5823
|
const inputSize = inputStr.length;
|
|
5998
5824
|
const truncatedInput = inputStr.length > 2048 ? inputStr.substring(0, 2048) : inputStr;
|
|
5999
5825
|
return tracers.mcp.startActiveSpan("neurolink.tool.execute", {
|
|
@@ -6008,9 +5834,7 @@ Current user's request: ${currentInput}`;
|
|
|
6008
5834
|
// Debug: Log tool execution attempt
|
|
6009
5835
|
logger.debug(`[${functionTag}] Tool execution requested:`, {
|
|
6010
5836
|
toolName,
|
|
6011
|
-
params: isNonNullObject(params)
|
|
6012
|
-
? transformParamsForLogging(params)
|
|
6013
|
-
: params,
|
|
5837
|
+
params: isNonNullObject(params) ? transformParamsForLogging(params) : params,
|
|
6014
5838
|
hasExternalManager: !!this.externalServerManager,
|
|
6015
5839
|
});
|
|
6016
5840
|
// 🔧 PARAMETER TRACE: Log tool execution details for debugging
|
|
@@ -6021,15 +5845,9 @@ Current user's request: ${currentInput}`;
|
|
|
6021
5845
|
type: typeof params,
|
|
6022
5846
|
isNull: params === null,
|
|
6023
5847
|
isUndefined: params === undefined,
|
|
6024
|
-
isEmpty: params &&
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
keys: params && typeof params === "object"
|
|
6028
|
-
? Object.keys(params)
|
|
6029
|
-
: "NOT_OBJECT",
|
|
6030
|
-
keysLength: params && typeof params === "object"
|
|
6031
|
-
? Object.keys(params).length
|
|
6032
|
-
: 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,
|
|
6033
5851
|
},
|
|
6034
5852
|
isTargetTool: toolName === "juspay-analytics_SuccessRateSRByTime",
|
|
6035
5853
|
options,
|
|
@@ -6049,12 +5867,8 @@ Current user's request: ${currentInput}`;
|
|
|
6049
5867
|
const registeredTimeout = toolInfo?.tool?.timeoutMs;
|
|
6050
5868
|
const registeredMaxRetries = toolInfo?.tool?.maxRetries;
|
|
6051
5869
|
const finalOptions = {
|
|
6052
|
-
timeout: options?.timeout ??
|
|
6053
|
-
|
|
6054
|
-
TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS,
|
|
6055
|
-
maxRetries: options?.maxRetries ??
|
|
6056
|
-
registeredMaxRetries ??
|
|
6057
|
-
RETRY_ATTEMPTS.DEFAULT,
|
|
5870
|
+
timeout: options?.timeout ?? registeredTimeout ?? TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS,
|
|
5871
|
+
maxRetries: options?.maxRetries ?? registeredMaxRetries ?? RETRY_ATTEMPTS.DEFAULT,
|
|
6058
5872
|
retryDelayMs: options?.retryDelayMs || RETRY_DELAYS.BASE_MS,
|
|
6059
5873
|
authContext: options?.authContext,
|
|
6060
5874
|
disableToolCache: options?.disableToolCache,
|
|
@@ -6117,9 +5931,7 @@ Current user's request: ${currentInput}`;
|
|
|
6117
5931
|
metrics.successfulExecutions++;
|
|
6118
5932
|
metrics.lastExecutionTime = executionTime;
|
|
6119
5933
|
metrics.averageExecutionTime =
|
|
6120
|
-
(metrics.averageExecutionTime *
|
|
6121
|
-
(metrics.successfulExecutions - 1) +
|
|
6122
|
-
executionTime) /
|
|
5934
|
+
(metrics.averageExecutionTime * (metrics.successfulExecutions - 1) + executionTime) /
|
|
6123
5935
|
metrics.successfulExecutions;
|
|
6124
5936
|
}
|
|
6125
5937
|
// Track memory usage
|
|
@@ -6141,15 +5953,9 @@ Current user's request: ${currentInput}`;
|
|
|
6141
5953
|
// Set span success attributes
|
|
6142
5954
|
// Check if result has isError flag (MCP tool error result)
|
|
6143
5955
|
// Also detect toolRegistry-wrapped errors that return { success: false }
|
|
6144
|
-
const resultObj = result && typeof result === "object"
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
const isToolError = (resultObj &&
|
|
6148
|
-
"isError" in resultObj &&
|
|
6149
|
-
resultObj.isError === true) ||
|
|
6150
|
-
(resultObj &&
|
|
6151
|
-
"success" in resultObj &&
|
|
6152
|
-
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);
|
|
6153
5959
|
// NL-001: Count isError:true results as circuit breaker failures
|
|
6154
5960
|
// This ensures tools that return error results (not just thrown errors) are tracked
|
|
6155
5961
|
// TODO(NL-009): This records a failure AFTER the circuit breaker already recorded
|
|
@@ -6180,10 +5986,7 @@ Current user's request: ${currentInput}`;
|
|
|
6180
5986
|
const errorText = contentArr
|
|
6181
5987
|
?.filter((c) => c.type === "text" && c.text)
|
|
6182
5988
|
.map((c) => c.text)
|
|
6183
|
-
.join(" ") ||
|
|
6184
|
-
(typeof resultObj.error === "string"
|
|
6185
|
-
? resultObj.error
|
|
6186
|
-
: "Unknown error");
|
|
5989
|
+
.join(" ") || (typeof resultObj.error === "string" ? resultObj.error : "Unknown error");
|
|
6187
5990
|
const errorCategory = classifyMcpErrorMessage(errorText);
|
|
6188
5991
|
const prefix = `[TOOL_ERROR: ${toolName} failed (${errorCategory})] `;
|
|
6189
5992
|
// NL-002: Clone content array to avoid mutating shared objects, then prefix error
|
|
@@ -6212,17 +6015,14 @@ Current user's request: ${currentInput}`;
|
|
|
6212
6015
|
// which was incorrectly included as a success
|
|
6213
6016
|
if (prevSuccessful > 1) {
|
|
6214
6017
|
metrics.averageExecutionTime =
|
|
6215
|
-
(metrics.averageExecutionTime * prevSuccessful -
|
|
6216
|
-
executionTime) /
|
|
6217
|
-
(prevSuccessful - 1);
|
|
6018
|
+
(metrics.averageExecutionTime * prevSuccessful - executionTime) / (prevSuccessful - 1);
|
|
6218
6019
|
}
|
|
6219
6020
|
else {
|
|
6220
6021
|
// No remaining successful executions, reset to 0
|
|
6221
6022
|
metrics.averageExecutionTime = 0;
|
|
6222
6023
|
}
|
|
6223
6024
|
const mappedCategory = mcpCategoryToErrorCategory(errorCategory);
|
|
6224
|
-
metrics.errorCategories[mappedCategory] =
|
|
6225
|
-
(metrics.errorCategories[mappedCategory] || 0) + 1;
|
|
6025
|
+
metrics.errorCategories[mappedCategory] = (metrics.errorCategories[mappedCategory] || 0) + 1;
|
|
6226
6026
|
}
|
|
6227
6027
|
}
|
|
6228
6028
|
// Emit tool end event AFTER isError check so success flag is correct
|
|
@@ -6251,8 +6051,7 @@ Current user's request: ${currentInput}`;
|
|
|
6251
6051
|
});
|
|
6252
6052
|
if (metrics) {
|
|
6253
6053
|
const category = ErrorCategory.EXECUTION;
|
|
6254
|
-
metrics.errorCategories[category] =
|
|
6255
|
-
(metrics.errorCategories[category] || 0) + 1;
|
|
6054
|
+
metrics.errorCategories[category] = (metrics.errorCategories[category] || 0) + 1;
|
|
6256
6055
|
}
|
|
6257
6056
|
// Emit tool end event for circuit breaker open
|
|
6258
6057
|
this.emitToolEndEvent(toolName, executionStartTime, false, undefined);
|
|
@@ -6296,12 +6095,10 @@ Current user's request: ${currentInput}`;
|
|
|
6296
6095
|
const availableTools = await this.getAllAvailableTools();
|
|
6297
6096
|
structuredError = ErrorFactory.toolNotFound(toolName, extractToolNames(availableTools.map((t) => ({ name: t.name }))));
|
|
6298
6097
|
}
|
|
6299
|
-
else if (error.message.includes("validation") ||
|
|
6300
|
-
error.message.includes("parameter")) {
|
|
6098
|
+
else if (error.message.includes("validation") || error.message.includes("parameter")) {
|
|
6301
6099
|
structuredError = ErrorFactory.invalidParameters(toolName, error, params);
|
|
6302
6100
|
}
|
|
6303
|
-
else if (error.message.includes("network") ||
|
|
6304
|
-
error.message.includes("connection")) {
|
|
6101
|
+
else if (error.message.includes("network") || error.message.includes("connection")) {
|
|
6305
6102
|
structuredError = ErrorFactory.networkError(toolName, error);
|
|
6306
6103
|
}
|
|
6307
6104
|
else {
|
|
@@ -6313,8 +6110,7 @@ Current user's request: ${currentInput}`;
|
|
|
6313
6110
|
}
|
|
6314
6111
|
if (metrics) {
|
|
6315
6112
|
const category = structuredError.category || ErrorCategory.EXECUTION;
|
|
6316
|
-
metrics.errorCategories[category] =
|
|
6317
|
-
(metrics.errorCategories[category] || 0) + 1;
|
|
6113
|
+
metrics.errorCategories[category] = (metrics.errorCategories[category] || 0) + 1;
|
|
6318
6114
|
}
|
|
6319
6115
|
// Emit tool end event BEFORE the error event.
|
|
6320
6116
|
// Node.js EventEmitter throws on unhandled 'error' events,
|
|
@@ -6351,9 +6147,7 @@ Current user's request: ${currentInput}`;
|
|
|
6351
6147
|
catch (outerError) {
|
|
6352
6148
|
// If the error was not already recorded on the span (from inner catch), record it
|
|
6353
6149
|
if (!(outerError instanceof NeuroLinkError)) {
|
|
6354
|
-
const errMsg = outerError instanceof Error
|
|
6355
|
-
? outerError.message
|
|
6356
|
-
: String(outerError);
|
|
6150
|
+
const errMsg = outerError instanceof Error ? outerError.message : String(outerError);
|
|
6357
6151
|
toolSpan.recordException(outerError instanceof Error ? outerError : new Error(errMsg));
|
|
6358
6152
|
toolSpan.setStatus({ code: SpanStatusCode.ERROR, message: errMsg });
|
|
6359
6153
|
}
|
|
@@ -6379,9 +6173,17 @@ Current user's request: ${currentInput}`;
|
|
|
6379
6173
|
!options.disableToolCache &&
|
|
6380
6174
|
!this._disableToolCacheForCurrentRequest &&
|
|
6381
6175
|
!toolAnnotations?.destructiveHint;
|
|
6176
|
+
const toolResultCache = this.mcpToolResultCache;
|
|
6382
6177
|
// === MCP ENHANCEMENT: Cache check (before execution) ===
|
|
6383
|
-
|
|
6384
|
-
|
|
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);
|
|
6385
6187
|
if (cached !== undefined) {
|
|
6386
6188
|
logger.debug(`[${functionTag}] Cache HIT for tool: ${toolName}`);
|
|
6387
6189
|
return cached;
|
|
@@ -6432,9 +6234,7 @@ Current user's request: ${currentInput}`;
|
|
|
6432
6234
|
inputSchema: {},
|
|
6433
6235
|
};
|
|
6434
6236
|
const decision = this.mcpToolRouter.route(mcpTool);
|
|
6435
|
-
externalTool =
|
|
6436
|
-
matchingTools.find((t) => t.serverId === decision.serverId) ||
|
|
6437
|
-
matchingTools[0];
|
|
6237
|
+
externalTool = matchingTools.find((t) => t.serverId === decision.serverId) || matchingTools[0];
|
|
6438
6238
|
logger.debug(`[${functionTag}] Router selected server: ${decision.serverId}`, {
|
|
6439
6239
|
strategy: decision.strategy,
|
|
6440
6240
|
confidence: decision.confidence,
|
|
@@ -6490,10 +6290,7 @@ Current user's request: ${currentInput}`;
|
|
|
6490
6290
|
});
|
|
6491
6291
|
const result = (await this.toolRegistry.executeTool(toolName, params, context));
|
|
6492
6292
|
// Check if result indicates a failure and emit error event
|
|
6493
|
-
if (result &&
|
|
6494
|
-
typeof result === "object" &&
|
|
6495
|
-
"success" in result &&
|
|
6496
|
-
result.success === false) {
|
|
6293
|
+
if (result && typeof result === "object" && "success" in result && result.success === false) {
|
|
6497
6294
|
const errorMessage = result.error || "Tool execution failed";
|
|
6498
6295
|
const errorToEmit = new Error(errorMessage);
|
|
6499
6296
|
this.emitter.emit("error", errorToEmit);
|
|
@@ -6515,8 +6312,8 @@ Current user's request: ${currentInput}`;
|
|
|
6515
6312
|
try {
|
|
6516
6313
|
const result = await executeWithMiddleware(executeCore);
|
|
6517
6314
|
// === MCP ENHANCEMENT: Cache store (after successful execution) ===
|
|
6518
|
-
if (isCacheEnabled && result !== undefined) {
|
|
6519
|
-
|
|
6315
|
+
if (isCacheEnabled && toolResultCache && result !== undefined) {
|
|
6316
|
+
toolResultCache.cacheResult(toolName, cacheParams, result);
|
|
6520
6317
|
logger.debug(`[${functionTag}] Cached result for tool: ${toolName}`);
|
|
6521
6318
|
}
|
|
6522
6319
|
return result;
|
|
@@ -6531,16 +6328,13 @@ Current user's request: ${currentInput}`;
|
|
|
6531
6328
|
execute: async () => ({}),
|
|
6532
6329
|
}
|
|
6533
6330
|
: undefined;
|
|
6534
|
-
if (toolStubForRetry &&
|
|
6535
|
-
isSafeToRetry(toolStubForRetry) &&
|
|
6536
|
-
error instanceof Error &&
|
|
6537
|
-
isRetriableError(error)) {
|
|
6331
|
+
if (toolStubForRetry && isSafeToRetry(toolStubForRetry) && error instanceof Error && isRetriableError(error)) {
|
|
6538
6332
|
logger.debug(`[${functionTag}] Tool ${toolName} is safe to retry, attempting once more`);
|
|
6539
6333
|
try {
|
|
6540
6334
|
const retryResult = await executeWithMiddleware(executeCore);
|
|
6541
6335
|
// Cache the retry result
|
|
6542
|
-
if (isCacheEnabled && retryResult !== undefined) {
|
|
6543
|
-
|
|
6336
|
+
if (isCacheEnabled && toolResultCache && retryResult !== undefined) {
|
|
6337
|
+
toolResultCache.cacheResult(toolName, cacheParams, retryResult);
|
|
6544
6338
|
}
|
|
6545
6339
|
return retryResult;
|
|
6546
6340
|
}
|
|
@@ -6579,8 +6373,7 @@ Current user's request: ${currentInput}`;
|
|
|
6579
6373
|
}
|
|
6580
6374
|
async getAllAvailableTools() {
|
|
6581
6375
|
// Return from cache if available and not stale
|
|
6582
|
-
if (this.toolCache &&
|
|
6583
|
-
Date.now() - this.toolCache.timestamp < this.toolCacheDuration) {
|
|
6376
|
+
if (this.toolCache && Date.now() - this.toolCache.timestamp < this.toolCacheDuration) {
|
|
6584
6377
|
logger.debug("Returning available tools from cache");
|
|
6585
6378
|
return this.toolCache.tools;
|
|
6586
6379
|
}
|
|
@@ -6661,9 +6454,7 @@ Current user's request: ${currentInput}`;
|
|
|
6661
6454
|
if (!allTools.has(tool.name)) {
|
|
6662
6455
|
const optimizedTool = optimizeToolForCollection(tool, {
|
|
6663
6456
|
category: detectCategory({
|
|
6664
|
-
existingCategory: typeof tool.metadata?.category === "string"
|
|
6665
|
-
? tool.metadata.category
|
|
6666
|
-
: undefined,
|
|
6457
|
+
existingCategory: typeof tool.metadata?.category === "string" ? tool.metadata.category : undefined,
|
|
6667
6458
|
isExternal: true,
|
|
6668
6459
|
serverId: tool.serverId,
|
|
6669
6460
|
}),
|
|
@@ -6819,9 +6610,7 @@ Current user's request: ${currentInput}`;
|
|
|
6819
6610
|
status: "failed",
|
|
6820
6611
|
configured: false,
|
|
6821
6612
|
authenticated: false,
|
|
6822
|
-
error: error instanceof Error
|
|
6823
|
-
? error.message
|
|
6824
|
-
: "Ollama service not running",
|
|
6613
|
+
error: error instanceof Error ? error.message : "Ollama service not running",
|
|
6825
6614
|
responseTime: Date.now() - startTime,
|
|
6826
6615
|
};
|
|
6827
6616
|
}
|
|
@@ -6944,9 +6733,7 @@ Current user's request: ${currentInput}`;
|
|
|
6944
6733
|
inMemoryServerInfos.length +
|
|
6945
6734
|
builtInServerInfos.length +
|
|
6946
6735
|
autoDiscoveredServerInfos.length;
|
|
6947
|
-
const availableServers = externalStats.connectedServers +
|
|
6948
|
-
inMemoryServerInfos.length +
|
|
6949
|
-
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
|
|
6950
6737
|
const totalTools = allTools.length + externalStats.totalTools;
|
|
6951
6738
|
return {
|
|
6952
6739
|
mcpInitialized: this.mcpInitialized,
|
|
@@ -7015,8 +6802,7 @@ Current user's request: ${currentInput}`;
|
|
|
7015
6802
|
// Test external MCP servers
|
|
7016
6803
|
const externalServer = this.externalServerManager.getServer(serverId);
|
|
7017
6804
|
if (externalServer) {
|
|
7018
|
-
return
|
|
7019
|
-
externalServer.client !== null);
|
|
6805
|
+
return externalServer.status === "connected" && externalServer.client !== null;
|
|
7020
6806
|
}
|
|
7021
6807
|
return false;
|
|
7022
6808
|
}
|
|
@@ -7136,9 +6922,7 @@ Current user's request: ${currentInput}`;
|
|
|
7136
6922
|
metrics[toolName] = {
|
|
7137
6923
|
...toolMetrics,
|
|
7138
6924
|
errorCategories: { ...toolMetrics.errorCategories },
|
|
7139
|
-
successRate: toolMetrics.totalExecutions > 0
|
|
7140
|
-
? toolMetrics.successfulExecutions / toolMetrics.totalExecutions
|
|
7141
|
-
: 0,
|
|
6925
|
+
successRate: toolMetrics.totalExecutions > 0 ? toolMetrics.successfulExecutions / toolMetrics.totalExecutions : 0,
|
|
7142
6926
|
};
|
|
7143
6927
|
}
|
|
7144
6928
|
return metrics;
|
|
@@ -7158,7 +6942,7 @@ Current user's request: ${currentInput}`;
|
|
|
7158
6942
|
*/
|
|
7159
6943
|
getToolCircuitBreakerStatus() {
|
|
7160
6944
|
const status = {};
|
|
7161
|
-
for (const [toolName, circuitBreaker
|
|
6945
|
+
for (const [toolName, circuitBreaker] of this.toolCircuitBreakers.entries()) {
|
|
7162
6946
|
status[toolName] = {
|
|
7163
6947
|
state: circuitBreaker.getState(),
|
|
7164
6948
|
failureCount: circuitBreaker.getFailureCount(),
|
|
@@ -7211,8 +6995,7 @@ Current user's request: ${currentInput}`;
|
|
|
7211
6995
|
? metrics.successfulExecutions / metrics.totalExecutions
|
|
7212
6996
|
: 0
|
|
7213
6997
|
: 0;
|
|
7214
|
-
const isHealthy = (!circuitBreaker || circuitBreaker.getState() === "closed") &&
|
|
7215
|
-
successRate >= 0.8;
|
|
6998
|
+
const isHealthy = (!circuitBreaker || circuitBreaker.getState() === "closed") && successRate >= 0.8;
|
|
7216
6999
|
if (isHealthy) {
|
|
7217
7000
|
healthyCount++;
|
|
7218
7001
|
}
|
|
@@ -7253,9 +7036,7 @@ Current user's request: ${currentInput}`;
|
|
|
7253
7036
|
successRate,
|
|
7254
7037
|
averageExecutionTime: metrics?.averageExecutionTime || 0,
|
|
7255
7038
|
lastExecutionTime: metrics?.lastExecutionTime || 0,
|
|
7256
|
-
errorCategories: metrics?.errorCategories
|
|
7257
|
-
? { ...metrics.errorCategories }
|
|
7258
|
-
: {},
|
|
7039
|
+
errorCategories: metrics?.errorCategories ? { ...metrics.errorCategories } : {},
|
|
7259
7040
|
},
|
|
7260
7041
|
circuitBreaker: {
|
|
7261
7042
|
state: circuitBreaker?.getState() || "closed",
|
|
@@ -7407,8 +7188,7 @@ Current user's request: ${currentInput}`;
|
|
|
7407
7188
|
*/
|
|
7408
7189
|
async storeToolExecutions(sessionId, userId, toolCalls, toolResults, currentTime) {
|
|
7409
7190
|
// Check if tools are not empty
|
|
7410
|
-
const hasToolData = (toolCalls && toolCalls.length > 0) ||
|
|
7411
|
-
(toolResults && toolResults.length > 0);
|
|
7191
|
+
const hasToolData = (toolCalls && toolCalls.length > 0) || (toolResults && toolResults.length > 0);
|
|
7412
7192
|
if (!hasToolData) {
|
|
7413
7193
|
logger.debug("Tool execution storage skipped", {
|
|
7414
7194
|
hasToolData,
|
|
@@ -7418,8 +7198,7 @@ Current user's request: ${currentInput}`;
|
|
|
7418
7198
|
return;
|
|
7419
7199
|
}
|
|
7420
7200
|
// Type guard to ensure it's Redis conversation memory manager
|
|
7421
|
-
const redisMemory = this
|
|
7422
|
-
.conversationMemory;
|
|
7201
|
+
const redisMemory = this.conversationMemory;
|
|
7423
7202
|
try {
|
|
7424
7203
|
await redisMemory.storeToolExecution(sessionId, userId, toolCalls, toolResults, currentTime);
|
|
7425
7204
|
}
|
|
@@ -7438,9 +7217,7 @@ Current user's request: ${currentInput}`;
|
|
|
7438
7217
|
*/
|
|
7439
7218
|
isToolExecutionStorageAvailable() {
|
|
7440
7219
|
const isRedisStorage = process.env.STORAGE_TYPE === "redis";
|
|
7441
|
-
const hasRedisConversationMemory = this.conversationMemory &&
|
|
7442
|
-
this.conversationMemory.constructor.name ===
|
|
7443
|
-
"RedisConversationMemoryManager";
|
|
7220
|
+
const hasRedisConversationMemory = this.conversationMemory && this.conversationMemory.constructor.name === "RedisConversationMemoryManager";
|
|
7444
7221
|
return !!(isRedisStorage && hasRedisConversationMemory);
|
|
7445
7222
|
}
|
|
7446
7223
|
/**
|
|
@@ -7959,8 +7736,7 @@ Current user's request: ${currentInput}`;
|
|
|
7959
7736
|
return null;
|
|
7960
7737
|
}
|
|
7961
7738
|
// Check for explicit annotations set on the tool first
|
|
7962
|
-
const explicitAnnotations = toolInfo.tool
|
|
7963
|
-
.annotations;
|
|
7739
|
+
const explicitAnnotations = toolInfo.tool.annotations;
|
|
7964
7740
|
// Infer annotations from the tool name/description as fallback
|
|
7965
7741
|
const inferredAnnotations = inferAnnotations({
|
|
7966
7742
|
name: toolInfo.tool.name,
|
|
@@ -7992,9 +7768,7 @@ Current user's request: ${currentInput}`;
|
|
|
7992
7768
|
const result = await this.externalServerManager.executeTool(tool.serverId, tool.name, params, { timeout: 30000 });
|
|
7993
7769
|
mcpLogger.debug(`[NeuroLink] External MCP tool execution result: ${tool.name}`, {
|
|
7994
7770
|
success: !!result,
|
|
7995
|
-
hasData: !!(result &&
|
|
7996
|
-
typeof result === "object" &&
|
|
7997
|
-
"content" in result),
|
|
7771
|
+
hasData: !!(result && typeof result === "object" && "content" in result),
|
|
7998
7772
|
});
|
|
7999
7773
|
return result;
|
|
8000
7774
|
}
|
|
@@ -8410,9 +8184,7 @@ Current user's request: ${currentInput}`;
|
|
|
8410
8184
|
logger.debug("[NeuroLink] OpenTelemetry shutdown successfully");
|
|
8411
8185
|
}
|
|
8412
8186
|
catch (error) {
|
|
8413
|
-
const err = error instanceof Error
|
|
8414
|
-
? error
|
|
8415
|
-
: new Error(`OpenTelemetry shutdown error: ${String(error)}`);
|
|
8187
|
+
const err = error instanceof Error ? error : new Error(`OpenTelemetry shutdown error: ${String(error)}`);
|
|
8416
8188
|
cleanupErrors.push(err);
|
|
8417
8189
|
logger.warn("[NeuroLink] Error shutting down OpenTelemetry:", error);
|
|
8418
8190
|
}
|
|
@@ -8424,9 +8196,7 @@ Current user's request: ${currentInput}`;
|
|
|
8424
8196
|
logger.debug("[NeuroLink] External MCP servers shutdown successfully");
|
|
8425
8197
|
}
|
|
8426
8198
|
catch (error) {
|
|
8427
|
-
const err = error instanceof Error
|
|
8428
|
-
? error
|
|
8429
|
-
: new Error(`External server shutdown error: ${String(error)}`);
|
|
8199
|
+
const err = error instanceof Error ? error : new Error(`External server shutdown error: ${String(error)}`);
|
|
8430
8200
|
cleanupErrors.push(err);
|
|
8431
8201
|
logger.warn("[NeuroLink] Error shutting down external MCP servers:", error);
|
|
8432
8202
|
}
|
|
@@ -8440,9 +8210,7 @@ Current user's request: ${currentInput}`;
|
|
|
8440
8210
|
logger.debug("[NeuroLink] Event listeners removed successfully");
|
|
8441
8211
|
}
|
|
8442
8212
|
catch (error) {
|
|
8443
|
-
const err = error instanceof Error
|
|
8444
|
-
? error
|
|
8445
|
-
: new Error(`Event emitter cleanup error: ${String(error)}`);
|
|
8213
|
+
const err = error instanceof Error ? error : new Error(`Event emitter cleanup error: ${String(error)}`);
|
|
8446
8214
|
cleanupErrors.push(err);
|
|
8447
8215
|
logger.warn("[NeuroLink] Error removing event listeners:", error);
|
|
8448
8216
|
}
|
|
@@ -8455,9 +8223,7 @@ Current user's request: ${currentInput}`;
|
|
|
8455
8223
|
logger.debug("[NeuroLink] Circuit breakers cleared successfully");
|
|
8456
8224
|
}
|
|
8457
8225
|
catch (error) {
|
|
8458
|
-
const err = error instanceof Error
|
|
8459
|
-
? error
|
|
8460
|
-
: new Error(`Circuit breaker cleanup error: ${String(error)}`);
|
|
8226
|
+
const err = error instanceof Error ? error : new Error(`Circuit breaker cleanup error: ${String(error)}`);
|
|
8461
8227
|
cleanupErrors.push(err);
|
|
8462
8228
|
logger.warn("[NeuroLink] Error clearing circuit breakers:", error);
|
|
8463
8229
|
}
|
|
@@ -8494,9 +8260,7 @@ Current user's request: ${currentInput}`;
|
|
|
8494
8260
|
logger.debug("[NeuroLink] Maps and caches cleared successfully");
|
|
8495
8261
|
}
|
|
8496
8262
|
catch (error) {
|
|
8497
|
-
const err = error instanceof Error
|
|
8498
|
-
? error
|
|
8499
|
-
: new Error(`Cache cleanup error: ${String(error)}`);
|
|
8263
|
+
const err = error instanceof Error ? error : new Error(`Cache cleanup error: ${String(error)}`);
|
|
8500
8264
|
cleanupErrors.push(err);
|
|
8501
8265
|
logger.warn("[NeuroLink] Error clearing caches:", error);
|
|
8502
8266
|
}
|
|
@@ -8522,9 +8286,7 @@ Current user's request: ${currentInput}`;
|
|
|
8522
8286
|
logger.debug("[NeuroLink] Initialization state reset successfully");
|
|
8523
8287
|
}
|
|
8524
8288
|
catch (error) {
|
|
8525
|
-
const err = error instanceof Error
|
|
8526
|
-
? error
|
|
8527
|
-
: new Error(`State reset error: ${String(error)}`);
|
|
8289
|
+
const err = error instanceof Error ? error : new Error(`State reset error: ${String(error)}`);
|
|
8528
8290
|
cleanupErrors.push(err);
|
|
8529
8291
|
logger.warn("[NeuroLink] Error resetting state:", error);
|
|
8530
8292
|
}
|
|
@@ -8568,11 +8330,8 @@ Current user's request: ${currentInput}`;
|
|
|
8568
8330
|
}
|
|
8569
8331
|
const compactor = new ContextCompactor({
|
|
8570
8332
|
...config,
|
|
8571
|
-
summarizationProvider: config?.summarizationProvider ??
|
|
8572
|
-
|
|
8573
|
-
?.summarizationProvider,
|
|
8574
|
-
summarizationModel: config?.summarizationModel ??
|
|
8575
|
-
this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
8333
|
+
summarizationProvider: config?.summarizationProvider ?? this.conversationMemoryConfig?.conversationMemory?.summarizationProvider,
|
|
8334
|
+
summarizationModel: config?.summarizationModel ?? this.conversationMemoryConfig?.conversationMemory?.summarizationModel,
|
|
8576
8335
|
});
|
|
8577
8336
|
// Use actual context window to determine target, not arbitrary heuristic
|
|
8578
8337
|
const budgetInfo = checkContextBudget({
|
|
@@ -8641,28 +8400,32 @@ Current user's request: ${currentInput}`;
|
|
|
8641
8400
|
async setAuthProvider(config) {
|
|
8642
8401
|
// Clear any pending lazy-init promise so it does not race with this call.
|
|
8643
8402
|
this.authInitPromise = undefined;
|
|
8403
|
+
await this.initializeAuthProviderFromConfig(config);
|
|
8404
|
+
}
|
|
8405
|
+
async initializeAuthProviderFromConfig(config) {
|
|
8406
|
+
let provider;
|
|
8407
|
+
let providerType;
|
|
8644
8408
|
// Duck-type check: direct MastraAuthProvider instance
|
|
8645
|
-
if ("authenticateToken" in config &&
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
logger.info(`Auth provider set: ${this.authProvider.type}`);
|
|
8409
|
+
if ("authenticateToken" in config && typeof config.authenticateToken === "function") {
|
|
8410
|
+
provider = config;
|
|
8411
|
+
providerType = provider.type;
|
|
8649
8412
|
}
|
|
8650
8413
|
else if ("provider" in config) {
|
|
8651
|
-
|
|
8652
|
-
|
|
8414
|
+
provider = config.provider;
|
|
8415
|
+
providerType = provider.type;
|
|
8653
8416
|
}
|
|
8654
8417
|
else {
|
|
8655
8418
|
const typedConfig = config;
|
|
8656
8419
|
const { AuthProviderFactory } = await import("./auth/AuthProviderFactory.js");
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
}
|
|
8660
|
-
if (this.authProvider) {
|
|
8661
|
-
this.emitter.emit("auth:provider:set", {
|
|
8662
|
-
type: this.authProvider.type,
|
|
8663
|
-
timestamp: Date.now(),
|
|
8664
|
-
});
|
|
8420
|
+
provider = await AuthProviderFactory.createProvider(typedConfig.type, typedConfig.config);
|
|
8421
|
+
providerType = typedConfig.type;
|
|
8665
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
|
+
});
|
|
8666
8429
|
}
|
|
8667
8430
|
/**
|
|
8668
8431
|
* Get the currently configured authentication provider
|
|
@@ -8679,14 +8442,17 @@ Current user's request: ${currentInput}`;
|
|
|
8679
8442
|
if (this.authProvider || !this.pendingAuthConfig) {
|
|
8680
8443
|
return;
|
|
8681
8444
|
}
|
|
8445
|
+
const pendingAuthConfig = this.pendingAuthConfig;
|
|
8682
8446
|
this.authInitPromise ??= (async () => {
|
|
8683
8447
|
try {
|
|
8684
|
-
await this.
|
|
8448
|
+
await this.initializeAuthProviderFromConfig(pendingAuthConfig);
|
|
8685
8449
|
this.pendingAuthConfig = undefined;
|
|
8686
8450
|
}
|
|
8687
|
-
|
|
8688
|
-
this.authInitPromise
|
|
8689
|
-
|
|
8451
|
+
finally {
|
|
8452
|
+
if (this.authInitPromise &&
|
|
8453
|
+
(this.pendingAuthConfig === undefined || this.pendingAuthConfig === pendingAuthConfig)) {
|
|
8454
|
+
this.authInitPromise = undefined;
|
|
8455
|
+
}
|
|
8690
8456
|
}
|
|
8691
8457
|
})();
|
|
8692
8458
|
await this.authInitPromise;
|