@juspay/neurolink 9.10.0 → 9.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/adapters/video/videoAnalyzer.d.ts +3 -3
- package/dist/adapters/video/videoAnalyzer.js +39 -25
- package/dist/agent/directTools.d.ts +3 -3
- package/dist/cli/commands/config.d.ts +9 -9
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/constants/contextWindows.d.ts +6 -3
- package/dist/constants/contextWindows.js +30 -3
- package/dist/constants/index.d.ts +3 -3
- package/dist/constants/retry.d.ts +4 -4
- package/dist/constants/retry.js +1 -1
- package/dist/context/contextCompactor.d.ts +1 -1
- package/dist/context/contextCompactor.js +59 -1
- package/dist/context/summarizationEngine.d.ts +2 -2
- package/dist/context/summarizationEngine.js +44 -18
- package/dist/context/toolOutputLimits.d.ts +22 -13
- package/dist/context/toolOutputLimits.js +58 -64
- package/dist/core/baseProvider.d.ts +11 -2
- package/dist/core/baseProvider.js +30 -1
- package/dist/core/conversationMemoryManager.d.ts +13 -1
- package/dist/core/conversationMemoryManager.js +36 -5
- package/dist/core/modules/GenerationHandler.d.ts +6 -0
- package/dist/core/modules/GenerationHandler.js +192 -7
- package/dist/core/modules/MessageBuilder.js +42 -4
- package/dist/core/modules/TelemetryHandler.js +4 -1
- package/dist/core/redisConversationMemoryManager.d.ts +19 -3
- package/dist/core/redisConversationMemoryManager.js +253 -58
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/lib/adapters/video/videoAnalyzer.d.ts +3 -3
- package/dist/lib/adapters/video/videoAnalyzer.js +39 -25
- package/dist/lib/agent/directTools.d.ts +7 -7
- package/dist/lib/constants/contextWindows.d.ts +6 -3
- package/dist/lib/constants/contextWindows.js +30 -3
- package/dist/lib/constants/index.d.ts +3 -3
- package/dist/lib/constants/retry.d.ts +4 -4
- package/dist/lib/constants/retry.js +1 -1
- package/dist/lib/context/contextCompactor.d.ts +1 -1
- package/dist/lib/context/contextCompactor.js +59 -1
- package/dist/lib/context/summarizationEngine.d.ts +2 -2
- package/dist/lib/context/summarizationEngine.js +44 -18
- package/dist/lib/context/toolOutputLimits.d.ts +22 -13
- package/dist/lib/context/toolOutputLimits.js +58 -64
- package/dist/lib/core/baseProvider.d.ts +11 -2
- package/dist/lib/core/baseProvider.js +30 -1
- package/dist/lib/core/conversationMemoryManager.d.ts +13 -1
- package/dist/lib/core/conversationMemoryManager.js +36 -5
- package/dist/lib/core/modules/GenerationHandler.d.ts +6 -0
- package/dist/lib/core/modules/GenerationHandler.js +192 -7
- package/dist/lib/core/modules/MessageBuilder.js +42 -4
- package/dist/lib/core/modules/TelemetryHandler.js +4 -1
- package/dist/lib/core/redisConversationMemoryManager.d.ts +19 -3
- package/dist/lib/core/redisConversationMemoryManager.js +253 -58
- package/dist/lib/files/fileTools.d.ts +3 -3
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/mcp/externalServerManager.js +36 -1
- package/dist/lib/memory/memoryRetrievalTools.d.ts +166 -0
- package/dist/lib/memory/memoryRetrievalTools.js +145 -0
- package/dist/lib/neurolink.d.ts +35 -1
- package/dist/lib/neurolink.js +471 -16
- package/dist/lib/providers/amazonBedrock.d.ts +1 -1
- package/dist/lib/providers/amazonBedrock.js +78 -45
- package/dist/lib/providers/amazonSagemaker.d.ts +1 -1
- package/dist/lib/providers/amazonSagemaker.js +1 -1
- package/dist/lib/providers/anthropic.d.ts +1 -1
- package/dist/lib/providers/anthropic.js +7 -7
- package/dist/lib/providers/anthropicBaseProvider.d.ts +1 -1
- package/dist/lib/providers/anthropicBaseProvider.js +7 -6
- package/dist/lib/providers/azureOpenai.d.ts +1 -1
- package/dist/lib/providers/azureOpenai.js +1 -1
- package/dist/lib/providers/googleAiStudio.d.ts +1 -1
- package/dist/lib/providers/googleAiStudio.js +5 -5
- package/dist/lib/providers/googleVertex.d.ts +1 -1
- package/dist/lib/providers/googleVertex.js +74 -17
- package/dist/lib/providers/huggingFace.d.ts +1 -1
- package/dist/lib/providers/huggingFace.js +1 -1
- package/dist/lib/providers/litellm.d.ts +1 -1
- package/dist/lib/providers/litellm.js +18 -16
- package/dist/lib/providers/mistral.d.ts +1 -1
- package/dist/lib/providers/mistral.js +1 -1
- package/dist/lib/providers/ollama.d.ts +1 -1
- package/dist/lib/providers/ollama.js +8 -7
- package/dist/lib/providers/openAI.d.ts +1 -1
- package/dist/lib/providers/openAI.js +6 -6
- package/dist/lib/providers/openRouter.d.ts +1 -1
- package/dist/lib/providers/openRouter.js +6 -2
- package/dist/lib/providers/openaiCompatible.d.ts +1 -1
- package/dist/lib/providers/openaiCompatible.js +1 -1
- package/dist/lib/proxy/proxyFetch.js +291 -65
- package/dist/lib/server/utils/validation.d.ts +4 -4
- package/dist/lib/services/server/ai/observability/instrumentation.js +12 -3
- package/dist/lib/telemetry/telemetryService.d.ts +2 -1
- package/dist/lib/telemetry/telemetryService.js +8 -1
- package/dist/lib/types/contextTypes.d.ts +26 -2
- package/dist/lib/types/conversation.d.ts +72 -40
- package/dist/lib/types/conversationMemoryInterface.d.ts +5 -1
- package/dist/lib/types/generateTypes.d.ts +26 -0
- package/dist/lib/types/modelTypes.d.ts +2 -2
- package/dist/lib/types/multimodal.d.ts +2 -0
- package/dist/lib/types/observability.d.ts +10 -0
- package/dist/lib/types/sdkTypes.d.ts +1 -1
- package/dist/lib/utils/conversationMemory.d.ts +4 -3
- package/dist/lib/utils/conversationMemory.js +44 -6
- package/dist/lib/utils/errorHandling.d.ts +5 -0
- package/dist/lib/utils/errorHandling.js +7 -2
- package/dist/lib/utils/logger.d.ts +8 -0
- package/dist/lib/utils/logger.js +56 -1
- package/dist/lib/utils/messageBuilder.js +74 -4
- package/dist/lib/utils/redis.js +6 -1
- package/dist/lib/utils/tokenEstimation.d.ts +2 -2
- package/dist/lib/utils/tokenEstimation.js +16 -1
- package/dist/lib/utils/videoAnalysisProcessor.d.ts +2 -1
- package/dist/lib/utils/videoAnalysisProcessor.js +7 -2
- package/dist/lib/workflow/config.d.ts +110 -110
- package/dist/mcp/externalServerManager.js +36 -1
- package/dist/memory/memoryRetrievalTools.d.ts +166 -0
- package/dist/memory/memoryRetrievalTools.js +144 -0
- package/dist/neurolink.d.ts +35 -1
- package/dist/neurolink.js +471 -16
- package/dist/providers/amazonBedrock.d.ts +1 -1
- package/dist/providers/amazonBedrock.js +78 -45
- package/dist/providers/amazonSagemaker.d.ts +1 -1
- package/dist/providers/amazonSagemaker.js +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +7 -7
- package/dist/providers/anthropicBaseProvider.d.ts +1 -1
- package/dist/providers/anthropicBaseProvider.js +7 -6
- package/dist/providers/azureOpenai.d.ts +1 -1
- package/dist/providers/azureOpenai.js +1 -1
- package/dist/providers/googleAiStudio.d.ts +1 -1
- package/dist/providers/googleAiStudio.js +5 -5
- package/dist/providers/googleVertex.d.ts +1 -1
- package/dist/providers/googleVertex.js +74 -17
- package/dist/providers/huggingFace.d.ts +1 -1
- package/dist/providers/huggingFace.js +1 -1
- package/dist/providers/litellm.d.ts +1 -1
- package/dist/providers/litellm.js +18 -16
- package/dist/providers/mistral.d.ts +1 -1
- package/dist/providers/mistral.js +1 -1
- package/dist/providers/ollama.d.ts +1 -1
- package/dist/providers/ollama.js +8 -7
- package/dist/providers/openAI.d.ts +1 -1
- package/dist/providers/openAI.js +6 -6
- package/dist/providers/openRouter.d.ts +1 -1
- package/dist/providers/openRouter.js +6 -2
- package/dist/providers/openaiCompatible.d.ts +1 -1
- package/dist/providers/openaiCompatible.js +1 -1
- package/dist/proxy/proxyFetch.js +291 -65
- package/dist/services/server/ai/observability/instrumentation.js +12 -3
- package/dist/telemetry/telemetryService.d.ts +2 -1
- package/dist/telemetry/telemetryService.js +8 -1
- package/dist/types/contextTypes.d.ts +26 -2
- package/dist/types/conversation.d.ts +72 -40
- package/dist/types/conversationMemoryInterface.d.ts +5 -1
- package/dist/types/generateTypes.d.ts +26 -0
- package/dist/types/modelTypes.d.ts +10 -10
- package/dist/types/multimodal.d.ts +2 -0
- package/dist/types/observability.d.ts +10 -0
- package/dist/types/sdkTypes.d.ts +1 -1
- package/dist/utils/conversationMemory.d.ts +4 -3
- package/dist/utils/conversationMemory.js +44 -6
- package/dist/utils/errorHandling.d.ts +5 -0
- package/dist/utils/errorHandling.js +7 -2
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.js +56 -1
- package/dist/utils/messageBuilder.js +74 -4
- package/dist/utils/redis.js +6 -1
- package/dist/utils/tokenEstimation.d.ts +2 -2
- package/dist/utils/tokenEstimation.js +16 -1
- package/dist/utils/videoAnalysisProcessor.d.ts +2 -1
- package/dist/utils/videoAnalysisProcessor.js +7 -2
- package/dist/workflow/config.d.ts +12 -12
- package/package.json +1 -1
package/dist/lib/neurolink.js
CHANGED
|
@@ -15,12 +15,14 @@ catch {
|
|
|
15
15
|
}
|
|
16
16
|
import { EventEmitter } from "events";
|
|
17
17
|
import pLimit from "p-limit";
|
|
18
|
+
import { ErrorCategory, ErrorSeverity } from "./constants/enums.js";
|
|
18
19
|
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";
|
|
19
20
|
import { checkContextBudget } from "./context/budgetChecker.js";
|
|
20
21
|
import { ContextCompactor, } from "./context/contextCompactor.js";
|
|
21
22
|
import { isContextOverflowError } from "./context/errorDetection.js";
|
|
22
23
|
import { repairToolPairs } from "./context/toolPairRepair.js";
|
|
23
24
|
import { SYSTEM_LIMITS } from "./core/constants.js";
|
|
25
|
+
import { ConversationMemoryManager } from "./core/conversationMemoryManager.js";
|
|
24
26
|
import { AIProviderFactory } from "./core/factory.js";
|
|
25
27
|
import { ProviderRegistry } from "./factories/providerRegistry.js";
|
|
26
28
|
import { FileReferenceRegistry } from "./files/fileReferenceRegistry.js";
|
|
@@ -31,10 +33,11 @@ import { ExternalServerManager } from "./mcp/externalServerManager.js";
|
|
|
31
33
|
import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
|
|
32
34
|
import { MCPToolRegistry } from "./mcp/toolRegistry.js";
|
|
33
35
|
import { initializeMem0 } from "./memory/mem0Initializer.js";
|
|
36
|
+
import { createMemoryRetrievalTools } from "./memory/memoryRetrievalTools.js";
|
|
34
37
|
import { flushOpenTelemetry, getLangfuseHealthStatus, initializeOpenTelemetry, isOpenTelemetryInitialized, setLangfuseContext, shutdownOpenTelemetry, } from "./services/server/ai/observability/instrumentation.js";
|
|
35
38
|
import { getConversationMessages, storeConversationTurn, } from "./utils/conversationMemory.js";
|
|
36
39
|
// Enhanced error handling imports
|
|
37
|
-
import { CircuitBreaker, ErrorFactory, isAbortError, isRetriableError, logStructuredError, NeuroLinkError, withRetry, withTimeout, } from "./utils/errorHandling.js";
|
|
40
|
+
import { CircuitBreaker, ERROR_CODES, ErrorFactory, isAbortError, isRetriableError, logStructuredError, NeuroLinkError, withRetry, withTimeout, } from "./utils/errorHandling.js";
|
|
38
41
|
// Factory processing imports
|
|
39
42
|
import { createCleanStreamOptions, enhanceTextGenerationOptions, processFactoryOptions, processStreamingFactoryOptions, validateFactoryConfig, } from "./utils/factoryProcessing.js";
|
|
40
43
|
import { logger, mcpLogger } from "./utils/logger.js";
|
|
@@ -171,6 +174,8 @@ export class NeuroLink {
|
|
|
171
174
|
// Mem0 memory instance and config for conversation context
|
|
172
175
|
mem0Instance;
|
|
173
176
|
mem0Config;
|
|
177
|
+
// Accumulated cost in USD across all generate() calls on this instance
|
|
178
|
+
_sessionCostUsd = 0;
|
|
174
179
|
// File Reference Registry for lazy on-demand file processing
|
|
175
180
|
fileRegistry;
|
|
176
181
|
// Cached file tools to avoid redundant createFileTools() calls per generate/stream
|
|
@@ -184,11 +189,42 @@ export class NeuroLink {
|
|
|
184
189
|
options.context !== null) {
|
|
185
190
|
try {
|
|
186
191
|
const ctx = options.context;
|
|
187
|
-
if
|
|
192
|
+
// Trigger context scoping if any meaningful Langfuse field is present
|
|
193
|
+
if (ctx.userId ||
|
|
194
|
+
ctx.sessionId ||
|
|
195
|
+
ctx.conversationId ||
|
|
196
|
+
ctx.requestId ||
|
|
197
|
+
ctx.traceName ||
|
|
198
|
+
ctx.metadata) {
|
|
199
|
+
// Build customAttributes from top-level metadata string/number/boolean fields
|
|
200
|
+
let customAttributes;
|
|
201
|
+
if (ctx.metadata && typeof ctx.metadata === "object") {
|
|
202
|
+
const metaObj = ctx.metadata;
|
|
203
|
+
const attrs = {};
|
|
204
|
+
for (const [k, v] of Object.entries(metaObj)) {
|
|
205
|
+
if (typeof v === "string" ||
|
|
206
|
+
typeof v === "number" ||
|
|
207
|
+
typeof v === "boolean") {
|
|
208
|
+
attrs[k] = v;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (Object.keys(attrs).length > 0) {
|
|
212
|
+
customAttributes = attrs;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
188
215
|
return await new Promise((resolve, reject) => {
|
|
189
216
|
setLangfuseContext({
|
|
190
217
|
userId: typeof ctx.userId === "string" ? ctx.userId : null,
|
|
191
218
|
sessionId: typeof ctx.sessionId === "string" ? ctx.sessionId : null,
|
|
219
|
+
conversationId: typeof ctx.conversationId === "string"
|
|
220
|
+
? ctx.conversationId
|
|
221
|
+
: null,
|
|
222
|
+
requestId: typeof ctx.requestId === "string" ? ctx.requestId : null,
|
|
223
|
+
traceName: typeof ctx.traceName === "string" ? ctx.traceName : null,
|
|
224
|
+
metadata: ctx.metadata && typeof ctx.metadata === "object"
|
|
225
|
+
? ctx.metadata
|
|
226
|
+
: null,
|
|
227
|
+
...(customAttributes !== undefined && { customAttributes }),
|
|
192
228
|
}, async () => {
|
|
193
229
|
try {
|
|
194
230
|
const result = await callback();
|
|
@@ -315,6 +351,7 @@ export class NeuroLink {
|
|
|
315
351
|
this.initializeExternalServerManager(constructorId, constructorStartTime, constructorHrTimeStart);
|
|
316
352
|
this.initializeHITL(config, constructorId, constructorStartTime, constructorHrTimeStart);
|
|
317
353
|
this.registerFileTools();
|
|
354
|
+
this.registerMemoryRetrievalTools();
|
|
318
355
|
this.initializeLangfuse(constructorId, constructorStartTime, constructorHrTimeStart);
|
|
319
356
|
this.logConstructorComplete(constructorId, constructorStartTime, constructorHrTimeStart);
|
|
320
357
|
}
|
|
@@ -518,6 +555,104 @@ export class NeuroLink {
|
|
|
518
555
|
logger.debug(`[NeuroLink] Registered ${Object.keys(fileTools).length} file reference tools`);
|
|
519
556
|
});
|
|
520
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Register memory retrieval tools that allow the AI to access
|
|
560
|
+
* conversation history, including full tool outputs.
|
|
561
|
+
* Only registered when Redis conversation memory is active.
|
|
562
|
+
*/
|
|
563
|
+
registerMemoryRetrievalTools() {
|
|
564
|
+
// Check if conversation memory is configured
|
|
565
|
+
// Memory retrieval tool requires Redis (getSessionRaw) but registration
|
|
566
|
+
// is deferred — the execute handler checks at runtime whether the actual
|
|
567
|
+
// memory manager supports getSessionRaw.
|
|
568
|
+
const memConfig = this.conversationMemoryConfig?.conversationMemory;
|
|
569
|
+
const hasRedisConfig = !!memConfig?.redisConfig ||
|
|
570
|
+
(memConfig &&
|
|
571
|
+
"redis" in memConfig &&
|
|
572
|
+
!!memConfig.redis) ||
|
|
573
|
+
process.env.STORAGE_TYPE === "redis";
|
|
574
|
+
if (!memConfig?.enabled || !hasRedisConfig) {
|
|
575
|
+
logger.debug("[NeuroLink] Skipping memory retrieval tools — requires Redis conversation memory");
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
// Defer registration until conversation memory is actually initialized
|
|
579
|
+
// We register a placeholder that will use the lazy-initialized memory manager
|
|
580
|
+
const self = this;
|
|
581
|
+
const tools = {
|
|
582
|
+
retrieve_context: {
|
|
583
|
+
description: "Retrieve messages from conversation memory. Use this to access full tool " +
|
|
584
|
+
"outputs when a result was truncated, review previous assistant responses, " +
|
|
585
|
+
"or search through conversation history.",
|
|
586
|
+
execute: async (params) => {
|
|
587
|
+
// Lazy access: conversationMemory is initialized on first generate() call
|
|
588
|
+
const memoryManager = self.conversationMemory;
|
|
589
|
+
if (!memoryManager || !("getSessionRaw" in memoryManager)) {
|
|
590
|
+
return {
|
|
591
|
+
success: false,
|
|
592
|
+
error: "Memory retrieval not available — Redis memory manager not initialized",
|
|
593
|
+
metadata: {
|
|
594
|
+
toolName: "retrieve_context",
|
|
595
|
+
serverId: "direct",
|
|
596
|
+
executionTime: 0,
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const actualTools = createMemoryRetrievalTools(memoryManager);
|
|
601
|
+
const result = await actualTools.retrieve_context.execute(params, {
|
|
602
|
+
toolCallId: "memory-retrieval",
|
|
603
|
+
messages: [],
|
|
604
|
+
});
|
|
605
|
+
// Check if the tool itself reported an error
|
|
606
|
+
const hasError = result &&
|
|
607
|
+
typeof result === "object" &&
|
|
608
|
+
"error" in result &&
|
|
609
|
+
!("messages" in result);
|
|
610
|
+
const errorMsg = hasError
|
|
611
|
+
? result.error
|
|
612
|
+
: undefined;
|
|
613
|
+
return {
|
|
614
|
+
success: !hasError,
|
|
615
|
+
data: result,
|
|
616
|
+
...(errorMsg ? { error: errorMsg } : {}),
|
|
617
|
+
metadata: {
|
|
618
|
+
toolName: "retrieve_context",
|
|
619
|
+
serverId: "direct",
|
|
620
|
+
executionTime: 0,
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
};
|
|
626
|
+
const registrations = Object.entries(tools).map(async ([toolName, toolDef]) => {
|
|
627
|
+
const toolId = `direct.${toolName}`;
|
|
628
|
+
const toolInfo = {
|
|
629
|
+
name: toolName,
|
|
630
|
+
description: toolDef.description,
|
|
631
|
+
inputSchema: {},
|
|
632
|
+
serverId: "direct",
|
|
633
|
+
category: "built-in",
|
|
634
|
+
};
|
|
635
|
+
await this.toolRegistry.registerTool(toolId, toolInfo, {
|
|
636
|
+
execute: async (params) => {
|
|
637
|
+
try {
|
|
638
|
+
return await toolDef.execute(params);
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
return {
|
|
642
|
+
success: false,
|
|
643
|
+
error: error instanceof Error ? error.message : String(error),
|
|
644
|
+
metadata: { toolName, serverId: "direct", executionTime: 0 },
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
description: toolDef.description,
|
|
649
|
+
inputSchema: {},
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
void Promise.all(registrations).then(() => {
|
|
653
|
+
logger.info("[NeuroLink] Memory retrieval tools registered");
|
|
654
|
+
});
|
|
655
|
+
}
|
|
521
656
|
/** Format memory context for prompt inclusion */
|
|
522
657
|
formatMemoryContext(memoryContext, currentInput) {
|
|
523
658
|
return `Context from previous conversations:
|
|
@@ -1451,10 +1586,30 @@ Current user's request: ${currentInput}`;
|
|
|
1451
1586
|
if (!options.input?.text || typeof options.input.text !== "string") {
|
|
1452
1587
|
throw new Error("Input text is required and must be a non-empty string");
|
|
1453
1588
|
}
|
|
1589
|
+
// Check budget limit before making API call
|
|
1590
|
+
if (options.maxBudgetUsd !== undefined &&
|
|
1591
|
+
options.maxBudgetUsd > 0 &&
|
|
1592
|
+
this._sessionCostUsd >= options.maxBudgetUsd) {
|
|
1593
|
+
throw new NeuroLinkError({
|
|
1594
|
+
code: "SESSION_BUDGET_EXCEEDED",
|
|
1595
|
+
message: `Session budget exceeded: spent $${this._sessionCostUsd.toFixed(4)} of $${options.maxBudgetUsd.toFixed(4)} limit`,
|
|
1596
|
+
category: ErrorCategory.VALIDATION,
|
|
1597
|
+
severity: ErrorSeverity.HIGH,
|
|
1598
|
+
retriable: false,
|
|
1599
|
+
context: {
|
|
1600
|
+
spent: this._sessionCostUsd,
|
|
1601
|
+
limit: options.maxBudgetUsd,
|
|
1602
|
+
},
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1454
1605
|
// Check if workflow is requested
|
|
1455
1606
|
if (options.workflow || options.workflowConfig) {
|
|
1456
1607
|
return await this.generateWithWorkflow(options);
|
|
1457
1608
|
}
|
|
1609
|
+
// Check if PPT output mode is requested
|
|
1610
|
+
if (options.output?.mode === "ppt") {
|
|
1611
|
+
return await this.generateWithPPT(options);
|
|
1612
|
+
}
|
|
1458
1613
|
// Set session and user IDs from context for Langfuse spans and execute with proper async scoping
|
|
1459
1614
|
return await this.setLangfuseContextFromOptions(options, async () => {
|
|
1460
1615
|
if (this.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
|
|
@@ -1585,6 +1740,20 @@ Current user's request: ${currentInput}`;
|
|
|
1585
1740
|
abortSignal: options.abortSignal,
|
|
1586
1741
|
skipToolPromptInjection: options.skipToolPromptInjection,
|
|
1587
1742
|
};
|
|
1743
|
+
// Auto-map top-level sessionId/userId to context for convenience
|
|
1744
|
+
// Tests and users may pass sessionId/userId as top-level options
|
|
1745
|
+
const extraContext = options;
|
|
1746
|
+
if (extraContext.sessionId || extraContext.userId) {
|
|
1747
|
+
baseOptions.context = {
|
|
1748
|
+
...baseOptions.context,
|
|
1749
|
+
...(extraContext.sessionId && !baseOptions.context?.sessionId
|
|
1750
|
+
? { sessionId: extraContext.sessionId }
|
|
1751
|
+
: {}),
|
|
1752
|
+
...(extraContext.userId && !baseOptions.context?.userId
|
|
1753
|
+
? { userId: extraContext.userId }
|
|
1754
|
+
: {}),
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1588
1757
|
// Apply factory enhancement using centralized utilities
|
|
1589
1758
|
const textOptions = enhanceTextGenerationOptions(baseOptions, factoryResult);
|
|
1590
1759
|
// Pass conversation memory config if available
|
|
@@ -1664,6 +1833,10 @@ Current user's request: ${currentInput}`;
|
|
|
1664
1833
|
video: textResult.video,
|
|
1665
1834
|
ppt: textResult.ppt,
|
|
1666
1835
|
};
|
|
1836
|
+
// Accumulate session cost for budget tracking
|
|
1837
|
+
if (generateResult.analytics?.cost && generateResult.analytics.cost > 0) {
|
|
1838
|
+
this._sessionCostUsd += generateResult.analytics.cost;
|
|
1839
|
+
}
|
|
1667
1840
|
this.scheduleGenerateMem0Storage(options, originalPrompt, generateResult);
|
|
1668
1841
|
return generateResult;
|
|
1669
1842
|
});
|
|
@@ -1691,6 +1864,40 @@ Current user's request: ${currentInput}`;
|
|
|
1691
1864
|
});
|
|
1692
1865
|
}
|
|
1693
1866
|
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Handle PPT generation mode
|
|
1869
|
+
*/
|
|
1870
|
+
async generateWithPPT(options) {
|
|
1871
|
+
const startTime = Date.now();
|
|
1872
|
+
// Dynamic import to avoid circular deps (same pattern as RAG)
|
|
1873
|
+
const { generatePresentation } = await import("./features/ppt/presentationOrchestrator.js");
|
|
1874
|
+
const { extractPPTContext, getEffectivePPTProvider } = await import("./features/ppt/utils.js");
|
|
1875
|
+
// Get provider instance for content planning
|
|
1876
|
+
const requestedProvider = (options.provider || "vertex");
|
|
1877
|
+
const provider = await AIProviderFactory.createProvider(requestedProvider, options.model, true, this);
|
|
1878
|
+
// Resolve effective PPT provider (may auto-select if current is not PPT-compatible)
|
|
1879
|
+
const effectiveProvider = await getEffectivePPTProvider(provider, requestedProvider, options.model || "default", this);
|
|
1880
|
+
// Extract PPT context from options
|
|
1881
|
+
const pptContext = extractPPTContext(options);
|
|
1882
|
+
// Generate the presentation
|
|
1883
|
+
const pptResult = await generatePresentation({
|
|
1884
|
+
context: pptContext,
|
|
1885
|
+
provider: effectiveProvider.provider,
|
|
1886
|
+
providerName: effectiveProvider.providerName,
|
|
1887
|
+
modelName: effectiveProvider.modelName,
|
|
1888
|
+
neurolink: this,
|
|
1889
|
+
});
|
|
1890
|
+
// Map PPT result to GenerateResult
|
|
1891
|
+
return {
|
|
1892
|
+
content: `Presentation generated: ${pptResult.filePath} (${pptResult.totalSlides} slides)`,
|
|
1893
|
+
finishReason: "stop",
|
|
1894
|
+
provider: pptResult.provider || requestedProvider,
|
|
1895
|
+
model: pptResult.model || options.model || "default",
|
|
1896
|
+
usage: undefined,
|
|
1897
|
+
responseTime: Date.now() - startTime,
|
|
1898
|
+
ppt: pptResult,
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1694
1901
|
/**
|
|
1695
1902
|
* Generate with workflow engine integration
|
|
1696
1903
|
* Returns both original and processed responses for AB testing
|
|
@@ -1957,6 +2164,11 @@ Current user's request: ${currentInput}`;
|
|
|
1957
2164
|
*/
|
|
1958
2165
|
async generateTextInternal(options) {
|
|
1959
2166
|
const generateInternalId = `generate-internal-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
2167
|
+
const existingRequestId = options.context?.requestId;
|
|
2168
|
+
const requestId = typeof existingRequestId === "string" && existingRequestId
|
|
2169
|
+
? existingRequestId
|
|
2170
|
+
: `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
2171
|
+
options.context = { ...options.context, requestId };
|
|
1960
2172
|
const generateInternalStartTime = Date.now();
|
|
1961
2173
|
const generateInternalHrTimeStart = process.hrtime.bigint();
|
|
1962
2174
|
const functionTag = "NeuroLink.generateTextInternal";
|
|
@@ -1972,11 +2184,23 @@ Current user's request: ${currentInput}`;
|
|
|
1972
2184
|
responseTimeMs: Date.now() - generateInternalStartTime,
|
|
1973
2185
|
tokensUsed: mcpResult.usage?.total || 0,
|
|
1974
2186
|
toolsUsed: mcpResult.toolsUsed?.length || 0,
|
|
2187
|
+
...(mcpResult.usage?.cacheCreationTokens !== undefined && {
|
|
2188
|
+
cacheCreationTokens: mcpResult.usage.cacheCreationTokens,
|
|
2189
|
+
}),
|
|
2190
|
+
...(mcpResult.usage?.cacheReadTokens !== undefined && {
|
|
2191
|
+
cacheReadTokens: mcpResult.usage.cacheReadTokens,
|
|
2192
|
+
}),
|
|
2193
|
+
...(mcpResult.usage?.cacheSavingsPercent !== undefined && {
|
|
2194
|
+
cacheSavingsPercent: mcpResult.usage.cacheSavingsPercent,
|
|
2195
|
+
}),
|
|
1975
2196
|
});
|
|
1976
|
-
await storeConversationTurn(this.conversationMemory, options, mcpResult, new Date(generateInternalStartTime));
|
|
2197
|
+
await storeConversationTurn(this.conversationMemory, options, mcpResult, new Date(generateInternalStartTime), requestId);
|
|
1977
2198
|
this.emitter.emit("response:end", mcpResult.content || "");
|
|
1978
2199
|
return mcpResult;
|
|
1979
2200
|
}
|
|
2201
|
+
if (options.abortSignal?.aborted) {
|
|
2202
|
+
throw new DOMException("The operation was aborted", "AbortError");
|
|
2203
|
+
}
|
|
1980
2204
|
const directResult = await this.directProviderGeneration(options);
|
|
1981
2205
|
logger.debug(`[${functionTag}] Direct generation successful`);
|
|
1982
2206
|
logger.info(`[NeuroLink.generateTextInternal] generate() - COMPLETE SUCCESS`, {
|
|
@@ -1985,8 +2209,17 @@ Current user's request: ${currentInput}`;
|
|
|
1985
2209
|
responseTimeMs: Date.now() - generateInternalStartTime,
|
|
1986
2210
|
tokensUsed: directResult.usage?.total || 0,
|
|
1987
2211
|
toolsUsed: directResult.toolsUsed?.length || 0,
|
|
2212
|
+
...(directResult.usage?.cacheCreationTokens !== undefined && {
|
|
2213
|
+
cacheCreationTokens: directResult.usage.cacheCreationTokens,
|
|
2214
|
+
}),
|
|
2215
|
+
...(directResult.usage?.cacheReadTokens !== undefined && {
|
|
2216
|
+
cacheReadTokens: directResult.usage.cacheReadTokens,
|
|
2217
|
+
}),
|
|
2218
|
+
...(directResult.usage?.cacheSavingsPercent !== undefined && {
|
|
2219
|
+
cacheSavingsPercent: directResult.usage.cacheSavingsPercent,
|
|
2220
|
+
}),
|
|
1988
2221
|
});
|
|
1989
|
-
await storeConversationTurn(this.conversationMemory, options, directResult, new Date(generateInternalStartTime));
|
|
2222
|
+
await storeConversationTurn(this.conversationMemory, options, directResult, new Date(generateInternalStartTime), requestId);
|
|
1990
2223
|
this.emitter.emit("response:end", directResult.content || "");
|
|
1991
2224
|
this.emitter.emit("message", `Text generation completed successfully`);
|
|
1992
2225
|
return directResult;
|
|
@@ -2012,7 +2245,7 @@ Current user's request: ${currentInput}`;
|
|
|
2012
2245
|
enableSummarize: false, // Skip LLM call for recovery
|
|
2013
2246
|
truncationFraction: 0.75, // Aggressive truncation
|
|
2014
2247
|
});
|
|
2015
|
-
const compactionResult = await compactor.compact(conversationMessages, compactionTarget);
|
|
2248
|
+
const compactionResult = await compactor.compact(conversationMessages, compactionTarget, undefined, options.context?.requestId);
|
|
2016
2249
|
if (compactionResult.compacted) {
|
|
2017
2250
|
const repairedResult = repairToolPairs(compactionResult.messages);
|
|
2018
2251
|
logger.info(`[${functionTag}] Aggressive compaction complete, retrying`, {
|
|
@@ -2053,7 +2286,7 @@ Current user's request: ${currentInput}`;
|
|
|
2053
2286
|
model: options.model || "unknown",
|
|
2054
2287
|
responseTime: Date.now() - generateInternalStartTime,
|
|
2055
2288
|
};
|
|
2056
|
-
await withTimeout(storeConversationTurn(this.conversationMemory, options, abortedResult, new Date(generateInternalStartTime)), 5000);
|
|
2289
|
+
await withTimeout(storeConversationTurn(this.conversationMemory, options, abortedResult, new Date(generateInternalStartTime), requestId), 5000);
|
|
2057
2290
|
}
|
|
2058
2291
|
catch (storeError) {
|
|
2059
2292
|
logger.warn(`[${functionTag}] Failed to store conversation turn after abort`, {
|
|
@@ -2110,7 +2343,22 @@ Current user's request: ${currentInput}`;
|
|
|
2110
2343
|
elapsedNs: (process.hrtime.bigint() - generateInternalHrTimeStart).toString(),
|
|
2111
2344
|
message: "Starting conversation memory initialization",
|
|
2112
2345
|
});
|
|
2113
|
-
|
|
2346
|
+
try {
|
|
2347
|
+
await this.conversationMemory.initialize();
|
|
2348
|
+
}
|
|
2349
|
+
catch (err) {
|
|
2350
|
+
logger.warn("[NEUROLINK] Redis memory init failed, falling back to in-memory", {
|
|
2351
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2352
|
+
generateInternalId,
|
|
2353
|
+
});
|
|
2354
|
+
const memCfg = this.conversationMemoryConfig?.conversationMemory;
|
|
2355
|
+
this.conversationMemory = new ConversationMemoryManager({
|
|
2356
|
+
enabled: true,
|
|
2357
|
+
maxSessions: memCfg?.maxSessions ?? 100,
|
|
2358
|
+
maxTurnsPerSession: memCfg?.maxTurnsPerSession ?? 50,
|
|
2359
|
+
});
|
|
2360
|
+
await this.conversationMemory.initialize();
|
|
2361
|
+
}
|
|
2114
2362
|
const conversationMemoryEndTime = process.hrtime.bigint();
|
|
2115
2363
|
const conversationMemoryDurationNs = conversationMemoryEndTime - conversationMemoryStartTime;
|
|
2116
2364
|
logger.debug(`[NeuroLink] ✅ LOG_POINT_G004_CONVERSATION_MEMORY_INIT_SUCCESS`, {
|
|
@@ -2142,6 +2390,10 @@ Current user's request: ${currentInput}`;
|
|
|
2142
2390
|
const maxMcpRetries = RETRY_ATTEMPTS.QUICK;
|
|
2143
2391
|
const maxAttempts = maxMcpRetries + 1;
|
|
2144
2392
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2393
|
+
if (options.abortSignal?.aborted) {
|
|
2394
|
+
logger.debug(`[${functionTag}] Abort signal already fired before attempt ${attempt}, stopping retries`);
|
|
2395
|
+
throw new DOMException("The operation was aborted", "AbortError");
|
|
2396
|
+
}
|
|
2145
2397
|
try {
|
|
2146
2398
|
logger.debug(`[${functionTag}] Attempting MCP generation (attempt ${attempt}/${maxAttempts})...`);
|
|
2147
2399
|
const mcpResult = await this.tryMCPGeneration(options);
|
|
@@ -2198,7 +2450,21 @@ Current user's request: ${currentInput}`;
|
|
|
2198
2450
|
logger.debug(`[${functionTag}] All MCP attempts exhausted, falling back to direct generation`);
|
|
2199
2451
|
break;
|
|
2200
2452
|
}
|
|
2201
|
-
await new Promise((resolve) =>
|
|
2453
|
+
await new Promise((resolve) => {
|
|
2454
|
+
const timer = setTimeout(resolve, 500);
|
|
2455
|
+
if (options.abortSignal) {
|
|
2456
|
+
const onAbort = () => {
|
|
2457
|
+
clearTimeout(timer);
|
|
2458
|
+
resolve();
|
|
2459
|
+
};
|
|
2460
|
+
options.abortSignal.addEventListener("abort", onAbort, {
|
|
2461
|
+
once: true,
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
});
|
|
2465
|
+
if (options.abortSignal?.aborted) {
|
|
2466
|
+
throw new DOMException("The operation was aborted", "AbortError");
|
|
2467
|
+
}
|
|
2202
2468
|
}
|
|
2203
2469
|
}
|
|
2204
2470
|
return null;
|
|
@@ -2207,7 +2473,12 @@ Current user's request: ${currentInput}`;
|
|
|
2207
2473
|
* Try MCP-enhanced generation (no fallback recursion)
|
|
2208
2474
|
*/
|
|
2209
2475
|
async tryMCPGeneration(options) {
|
|
2476
|
+
if (options.abortSignal?.aborted) {
|
|
2477
|
+
throw new DOMException("The operation was aborted", "AbortError");
|
|
2478
|
+
}
|
|
2210
2479
|
// 🚀 EXHAUSTIVE LOGGING POINT T001: TRY MCP GENERATION ENTRY
|
|
2480
|
+
const requestId = options.context?.requestId ||
|
|
2481
|
+
"unknown";
|
|
2211
2482
|
const tryMCPId = `try-mcp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
2212
2483
|
const tryMCPStartTime = Date.now();
|
|
2213
2484
|
const tryMCPHrTimeStart = process.hrtime.bigint();
|
|
@@ -2261,6 +2532,7 @@ Current user's request: ${currentInput}`;
|
|
|
2261
2532
|
? options.systemPrompt || ""
|
|
2262
2533
|
: this.createToolAwareSystemPrompt(options.systemPrompt, availableTools);
|
|
2263
2534
|
logger.debug("Tool-aware system prompt created", {
|
|
2535
|
+
requestId,
|
|
2264
2536
|
originalPromptLength: options.systemPrompt?.length || 0,
|
|
2265
2537
|
enhancedPromptLength: enhancedSystemPrompt.length,
|
|
2266
2538
|
skippedToolInjection: !!options.skipToolPromptInjection,
|
|
@@ -2268,6 +2540,44 @@ Current user's request: ${currentInput}`;
|
|
|
2268
2540
|
});
|
|
2269
2541
|
// Get conversation messages for context
|
|
2270
2542
|
let conversationMessages = await getConversationMessages(this.conversationMemory, options);
|
|
2543
|
+
if (logger.shouldLog("debug")) {
|
|
2544
|
+
try {
|
|
2545
|
+
logger.debug("[Observability] Conversation history summary", {
|
|
2546
|
+
requestId,
|
|
2547
|
+
messageCount: conversationMessages?.length || 0,
|
|
2548
|
+
messages: conversationMessages?.map((msg, i) => {
|
|
2549
|
+
let contentLength;
|
|
2550
|
+
if (typeof msg.content === "string") {
|
|
2551
|
+
contentLength = msg.content.length;
|
|
2552
|
+
}
|
|
2553
|
+
else {
|
|
2554
|
+
try {
|
|
2555
|
+
contentLength = JSON.stringify(msg.content).length;
|
|
2556
|
+
}
|
|
2557
|
+
catch {
|
|
2558
|
+
contentLength = 0;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
return {
|
|
2562
|
+
index: i,
|
|
2563
|
+
role: msg.role,
|
|
2564
|
+
contentLength,
|
|
2565
|
+
contentPreview: typeof msg.content === "string"
|
|
2566
|
+
? msg.content.substring(0, 200)
|
|
2567
|
+
: "[multimodal]",
|
|
2568
|
+
};
|
|
2569
|
+
}),
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
catch {
|
|
2573
|
+
// Ignore serialization errors in debug logging
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
logger.debug("[Observability] Available tools for LLM", {
|
|
2577
|
+
requestId,
|
|
2578
|
+
toolCount: availableTools?.length || 0,
|
|
2579
|
+
toolNames: availableTools?.map((t) => t.name) || [],
|
|
2580
|
+
});
|
|
2271
2581
|
// Pre-generation budget check
|
|
2272
2582
|
const budgetResult = checkContextBudget({
|
|
2273
2583
|
provider: providerName,
|
|
@@ -2278,6 +2588,19 @@ Current user's request: ${currentInput}`;
|
|
|
2278
2588
|
currentPrompt: options.prompt,
|
|
2279
2589
|
toolDefinitions: availableTools,
|
|
2280
2590
|
});
|
|
2591
|
+
logger.info("[TokenBudget] Token breakdown", {
|
|
2592
|
+
requestId,
|
|
2593
|
+
system: budgetResult.breakdown?.systemPrompt || 0,
|
|
2594
|
+
history: budgetResult.breakdown?.conversationHistory || 0,
|
|
2595
|
+
tools: budgetResult.breakdown?.toolDefinitions || 0,
|
|
2596
|
+
currentPrompt: budgetResult.breakdown?.currentPrompt || 0,
|
|
2597
|
+
files: budgetResult.breakdown?.fileAttachments || 0,
|
|
2598
|
+
total: budgetResult.estimatedInputTokens,
|
|
2599
|
+
budget: budgetResult.availableInputTokens,
|
|
2600
|
+
usagePercent: Math.round(budgetResult.usageRatio * 1000) / 10,
|
|
2601
|
+
conversationMessageCount: conversationMessages?.length || 0,
|
|
2602
|
+
shouldCompact: budgetResult.shouldCompact,
|
|
2603
|
+
});
|
|
2281
2604
|
if (budgetResult.shouldCompact && this.conversationMemory) {
|
|
2282
2605
|
logger.info("[NeuroLink] Context budget exceeded, triggering auto-compaction", {
|
|
2283
2606
|
usageRatio: budgetResult.usageRatio,
|
|
@@ -2291,7 +2614,7 @@ Current user's request: ${currentInput}`;
|
|
|
2291
2614
|
summarizationModel: this.conversationMemoryConfig?.conversationMemory
|
|
2292
2615
|
?.summarizationModel,
|
|
2293
2616
|
});
|
|
2294
|
-
const compactionResult = await compactor.compact(conversationMessages, budgetResult.availableInputTokens, this.conversationMemoryConfig?.conversationMemory);
|
|
2617
|
+
const compactionResult = await compactor.compact(conversationMessages, budgetResult.availableInputTokens, this.conversationMemoryConfig?.conversationMemory, requestId);
|
|
2295
2618
|
if (compactionResult.compacted) {
|
|
2296
2619
|
const repairedResult = repairToolPairs(compactionResult.messages);
|
|
2297
2620
|
conversationMessages = repairedResult.messages;
|
|
@@ -2313,6 +2636,16 @@ Current user's request: ${currentInput}`;
|
|
|
2313
2636
|
customTools: this.getCustomTools(),
|
|
2314
2637
|
executeTool: this.executeTool.bind(this),
|
|
2315
2638
|
}, functionTag);
|
|
2639
|
+
logger.debug("[Observability] User input to LLM", {
|
|
2640
|
+
requestId,
|
|
2641
|
+
promptPreview: options.prompt?.substring(0, 200),
|
|
2642
|
+
promptLength: options.prompt?.length || 0,
|
|
2643
|
+
model: options.model,
|
|
2644
|
+
maxTokens: options.maxTokens,
|
|
2645
|
+
temperature: options.temperature,
|
|
2646
|
+
maxSteps: options.maxSteps,
|
|
2647
|
+
skipToolPromptInjection: options.skipToolPromptInjection,
|
|
2648
|
+
});
|
|
2316
2649
|
const result = await provider.generate({
|
|
2317
2650
|
...options,
|
|
2318
2651
|
systemPrompt: enhancedSystemPrompt,
|
|
@@ -2434,6 +2767,9 @@ Current user's request: ${currentInput}`;
|
|
|
2434
2767
|
let lastError = null;
|
|
2435
2768
|
// Try each provider in order
|
|
2436
2769
|
for (const providerName of tryProviders) {
|
|
2770
|
+
if (options.abortSignal?.aborted) {
|
|
2771
|
+
throw new DOMException("The operation was aborted", "AbortError");
|
|
2772
|
+
}
|
|
2437
2773
|
try {
|
|
2438
2774
|
logger.debug(`[${functionTag}] Attempting provider: ${providerName}`);
|
|
2439
2775
|
// Get conversation messages for context (use pre-compacted if provided)
|
|
@@ -2456,7 +2792,7 @@ Current user's request: ${currentInput}`;
|
|
|
2456
2792
|
});
|
|
2457
2793
|
if (budgetCheck.shouldCompact && this.conversationMemory) {
|
|
2458
2794
|
const compactor = new ContextCompactor({ provider: providerName });
|
|
2459
|
-
const compactionResult = await compactor.compact(conversationMessages, budgetCheck.availableInputTokens);
|
|
2795
|
+
const compactionResult = await compactor.compact(conversationMessages, budgetCheck.availableInputTokens, undefined, options.context?.requestId);
|
|
2460
2796
|
if (compactionResult.compacted) {
|
|
2461
2797
|
const repairedResult = repairToolPairs(compactionResult.messages);
|
|
2462
2798
|
conversationMessages = repairedResult.messages;
|
|
@@ -3031,6 +3367,8 @@ Current user's request: ${currentInput}`;
|
|
|
3031
3367
|
providerDetails,
|
|
3032
3368
|
enableSummarization: enhancedOptions.enableSummarization,
|
|
3033
3369
|
events: eventSequence.length > 0 ? eventSequence : undefined,
|
|
3370
|
+
requestId: enhancedOptions.context
|
|
3371
|
+
?.requestId,
|
|
3034
3372
|
});
|
|
3035
3373
|
logger.debug("[NeuroLink.stream] Stored conversation turn with events", {
|
|
3036
3374
|
sessionId,
|
|
@@ -3135,7 +3473,7 @@ Current user's request: ${currentInput}`;
|
|
|
3135
3473
|
});
|
|
3136
3474
|
if (streamBudget.shouldCompact && this.conversationMemory) {
|
|
3137
3475
|
const compactor = new ContextCompactor({ provider: providerName });
|
|
3138
|
-
const compactionResult = await compactor.compact(conversationMessages, streamBudget.availableInputTokens);
|
|
3476
|
+
const compactionResult = await compactor.compact(conversationMessages, streamBudget.availableInputTokens, undefined, options.context?.requestId);
|
|
3139
3477
|
if (compactionResult.compacted) {
|
|
3140
3478
|
const repairedResult = repairToolPairs(compactionResult.messages);
|
|
3141
3479
|
conversationMessages = repairedResult.messages;
|
|
@@ -3269,6 +3607,9 @@ Current user's request: ${currentInput}`;
|
|
|
3269
3607
|
startTimeStamp: new Date(startTime),
|
|
3270
3608
|
providerDetails,
|
|
3271
3609
|
enableSummarization: enhancedOptions?.enableSummarization,
|
|
3610
|
+
requestId: enhancedOptions?.context?.requestId ||
|
|
3611
|
+
options.context
|
|
3612
|
+
?.requestId,
|
|
3272
3613
|
});
|
|
3273
3614
|
}
|
|
3274
3615
|
catch (error) {
|
|
@@ -4864,7 +5205,13 @@ Current user's request: ${currentInput}`;
|
|
|
4864
5205
|
const initId = `stats-init-${Date.now()}`;
|
|
4865
5206
|
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
4866
5207
|
if (!this.conversationMemory) {
|
|
4867
|
-
throw new
|
|
5208
|
+
throw new NeuroLinkError({
|
|
5209
|
+
code: ERROR_CODES.MISSING_CONFIGURATION,
|
|
5210
|
+
message: "Conversation memory is not enabled",
|
|
5211
|
+
category: ErrorCategory.VALIDATION,
|
|
5212
|
+
severity: ErrorSeverity.HIGH,
|
|
5213
|
+
retriable: false,
|
|
5214
|
+
});
|
|
4868
5215
|
}
|
|
4869
5216
|
return await this.conversationMemory.getStats();
|
|
4870
5217
|
}
|
|
@@ -4878,10 +5225,23 @@ Current user's request: ${currentInput}`;
|
|
|
4878
5225
|
const initId = `history-init-${Date.now()}`;
|
|
4879
5226
|
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
4880
5227
|
if (!this.conversationMemory) {
|
|
4881
|
-
throw new
|
|
5228
|
+
throw new NeuroLinkError({
|
|
5229
|
+
code: ERROR_CODES.MISSING_CONFIGURATION,
|
|
5230
|
+
message: "Conversation memory is not enabled",
|
|
5231
|
+
category: ErrorCategory.VALIDATION,
|
|
5232
|
+
severity: ErrorSeverity.HIGH,
|
|
5233
|
+
retriable: false,
|
|
5234
|
+
});
|
|
4882
5235
|
}
|
|
4883
5236
|
if (!sessionId || typeof sessionId !== "string") {
|
|
4884
|
-
throw new
|
|
5237
|
+
throw new NeuroLinkError({
|
|
5238
|
+
code: ERROR_CODES.INVALID_PARAMETERS,
|
|
5239
|
+
message: "Session ID must be a non-empty string",
|
|
5240
|
+
category: ErrorCategory.VALIDATION,
|
|
5241
|
+
severity: ErrorSeverity.MEDIUM,
|
|
5242
|
+
retriable: false,
|
|
5243
|
+
context: { sessionId },
|
|
5244
|
+
});
|
|
4885
5245
|
}
|
|
4886
5246
|
try {
|
|
4887
5247
|
// Use the existing buildContextMessages method to get the complete history
|
|
@@ -4910,7 +5270,13 @@ Current user's request: ${currentInput}`;
|
|
|
4910
5270
|
const initId = `clear-session-init-${Date.now()}`;
|
|
4911
5271
|
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
4912
5272
|
if (!this.conversationMemory) {
|
|
4913
|
-
throw new
|
|
5273
|
+
throw new NeuroLinkError({
|
|
5274
|
+
code: ERROR_CODES.MISSING_CONFIGURATION,
|
|
5275
|
+
message: "Conversation memory is not enabled",
|
|
5276
|
+
category: ErrorCategory.VALIDATION,
|
|
5277
|
+
severity: ErrorSeverity.HIGH,
|
|
5278
|
+
retriable: false,
|
|
5279
|
+
});
|
|
4914
5280
|
}
|
|
4915
5281
|
return await this.conversationMemory.clearSession(sessionId);
|
|
4916
5282
|
}
|
|
@@ -4922,7 +5288,13 @@ Current user's request: ${currentInput}`;
|
|
|
4922
5288
|
const initId = `clear-all-init-${Date.now()}`;
|
|
4923
5289
|
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
4924
5290
|
if (!this.conversationMemory) {
|
|
4925
|
-
throw new
|
|
5291
|
+
throw new NeuroLinkError({
|
|
5292
|
+
code: ERROR_CODES.MISSING_CONFIGURATION,
|
|
5293
|
+
message: "Conversation memory is not enabled",
|
|
5294
|
+
category: ErrorCategory.VALIDATION,
|
|
5295
|
+
severity: ErrorSeverity.HIGH,
|
|
5296
|
+
retriable: false,
|
|
5297
|
+
});
|
|
4926
5298
|
}
|
|
4927
5299
|
await this.conversationMemory.clearAllSessions();
|
|
4928
5300
|
}
|
|
@@ -4973,6 +5345,89 @@ Current user's request: ${currentInput}`;
|
|
|
4973
5345
|
"RedisConversationMemoryManager";
|
|
4974
5346
|
return !!(isRedisStorage && hasRedisConversationMemory);
|
|
4975
5347
|
}
|
|
5348
|
+
/**
|
|
5349
|
+
* Get the raw messages array for a session.
|
|
5350
|
+
* Returns the full messages list without context filtering or summarization.
|
|
5351
|
+
* @param sessionId - The session ID to retrieve messages for
|
|
5352
|
+
* @returns Array of ChatMessage objects, or empty array if session doesn't exist
|
|
5353
|
+
*/
|
|
5354
|
+
async getSessionMessages(sessionId, userId) {
|
|
5355
|
+
const initId = `get-msgs-init-${Date.now()}`;
|
|
5356
|
+
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
5357
|
+
if (!this.conversationMemory) {
|
|
5358
|
+
throw new NeuroLinkError({
|
|
5359
|
+
code: ERROR_CODES.MISSING_CONFIGURATION,
|
|
5360
|
+
message: "Conversation memory is not enabled",
|
|
5361
|
+
category: ErrorCategory.VALIDATION,
|
|
5362
|
+
severity: ErrorSeverity.HIGH,
|
|
5363
|
+
retriable: false,
|
|
5364
|
+
});
|
|
5365
|
+
}
|
|
5366
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
5367
|
+
throw new NeuroLinkError({
|
|
5368
|
+
code: ERROR_CODES.INVALID_PARAMETERS,
|
|
5369
|
+
message: "Session ID must be a non-empty string",
|
|
5370
|
+
category: ErrorCategory.VALIDATION,
|
|
5371
|
+
severity: ErrorSeverity.MEDIUM,
|
|
5372
|
+
retriable: false,
|
|
5373
|
+
context: { sessionId },
|
|
5374
|
+
});
|
|
5375
|
+
}
|
|
5376
|
+
return await this.conversationMemory.getSessionMessages(sessionId, userId);
|
|
5377
|
+
}
|
|
5378
|
+
/**
|
|
5379
|
+
* Replace the entire messages array for a session.
|
|
5380
|
+
* @param sessionId - The session ID to update
|
|
5381
|
+
* @param messages - The new messages array
|
|
5382
|
+
* @param userId - Optional user ID for scoped Redis key lookup
|
|
5383
|
+
*/
|
|
5384
|
+
async setSessionMessages(sessionId, messages, userId) {
|
|
5385
|
+
const initId = `set-msgs-init-${Date.now()}`;
|
|
5386
|
+
await this.initializeConversationMemoryForGeneration(initId, Date.now(), process.hrtime.bigint());
|
|
5387
|
+
if (!this.conversationMemory) {
|
|
5388
|
+
throw new NeuroLinkError({
|
|
5389
|
+
code: ERROR_CODES.MISSING_CONFIGURATION,
|
|
5390
|
+
message: "Conversation memory is not enabled",
|
|
5391
|
+
category: ErrorCategory.VALIDATION,
|
|
5392
|
+
severity: ErrorSeverity.HIGH,
|
|
5393
|
+
retriable: false,
|
|
5394
|
+
});
|
|
5395
|
+
}
|
|
5396
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
5397
|
+
throw new NeuroLinkError({
|
|
5398
|
+
code: ERROR_CODES.INVALID_PARAMETERS,
|
|
5399
|
+
message: "Session ID must be a non-empty string",
|
|
5400
|
+
category: ErrorCategory.VALIDATION,
|
|
5401
|
+
severity: ErrorSeverity.MEDIUM,
|
|
5402
|
+
retriable: false,
|
|
5403
|
+
context: { sessionId },
|
|
5404
|
+
});
|
|
5405
|
+
}
|
|
5406
|
+
await this.conversationMemory.setSessionMessages(sessionId, messages, userId);
|
|
5407
|
+
}
|
|
5408
|
+
/**
|
|
5409
|
+
* Modify the last assistant message in a session using a transformer function.
|
|
5410
|
+
* Convenience wrapper around getSessionMessages/setSessionMessages.
|
|
5411
|
+
* @param sessionId - The session ID to modify
|
|
5412
|
+
* @param transformer - Function that receives the last assistant message content and returns the modified content
|
|
5413
|
+
* @param userId - Optional user ID for scoped Redis key lookup
|
|
5414
|
+
* @returns true if a message was modified, false if no assistant message was found
|
|
5415
|
+
*/
|
|
5416
|
+
async modifyLastAssistantMessage(sessionId, transformer, userId) {
|
|
5417
|
+
const messages = await this.getSessionMessages(sessionId, userId);
|
|
5418
|
+
// Find the last assistant message (searching from the end)
|
|
5419
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5420
|
+
if (messages[i].role === "assistant") {
|
|
5421
|
+
messages[i] = {
|
|
5422
|
+
...messages[i],
|
|
5423
|
+
content: transformer(messages[i].content),
|
|
5424
|
+
};
|
|
5425
|
+
await this.setSessionMessages(sessionId, messages, userId);
|
|
5426
|
+
return true;
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5429
|
+
return false;
|
|
5430
|
+
}
|
|
4976
5431
|
// ===== EXTERNAL MCP SERVER METHODS =====
|
|
4977
5432
|
/**
|
|
4978
5433
|
* Add an external MCP server
|