@juspay/neurolink 9.32.0 → 9.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/auth/anthropicOAuth.js +1 -1
- package/dist/cli/commands/proxy.js +18 -5
- package/dist/client/aiSdkAdapter.js +1 -1
- package/dist/client/index.js +137 -501
- package/dist/core/factory.js +0 -1
- package/dist/core/redisConversationMemoryManager.js +1 -1
- package/dist/features/ppt/slideGenerator.js +0 -1
- package/dist/features/ppt/utils.js +0 -1
- package/dist/lib/server/routes/claudeProxyRoutes.js +45 -9
- package/dist/mcp/elicitationProtocol.js +1 -1
- package/dist/mcp/servers/agent/directToolsServer.js +0 -1
- package/dist/providers/azureOpenai.js +1 -1
- package/dist/providers/huggingFace.js +0 -1
- package/dist/providers/openaiCompatible.js +0 -1
- package/dist/sdk/toolRegistration.js +0 -1
- package/dist/server/openapi/generator.js +1 -1
- package/dist/server/routes/claudeProxyRoutes.js +45 -9
- package/dist/types/configTypes.js +0 -5
- package/dist/types/modelTypes.js +0 -1
- package/dist/types/tools.js +0 -1
- package/dist/types/typeAliases.js +0 -1
- package/dist/types/utilities.js +1 -1
- package/dist/types/workflowTypes.js +0 -1
- package/dist/utils/providerRetry.js +0 -1
- package/dist/utils/providerUtils.js +0 -1
- package/package.json +2 -2
- package/dist/client/adapters/providerImageAdapter.js +0 -588
- package/dist/client/adapters/tts/googleTTSHandler.js +0 -344
- package/dist/client/adapters/video/directorPipeline.js +0 -516
- package/dist/client/adapters/video/ffmpegAdapter.js +0 -206
- package/dist/client/adapters/video/frameExtractor.js +0 -143
- package/dist/client/adapters/video/vertexVideoHandler.js +0 -763
- package/dist/client/adapters/video/videoAnalyzer.js +0 -238
- package/dist/client/adapters/video/videoMerger.js +0 -171
- package/dist/client/agent/directTools.js +0 -840
- package/dist/client/auth/AuthProviderFactory.js +0 -111
- package/dist/client/auth/AuthProviderRegistry.js +0 -190
- package/dist/client/auth/RequestContext.js +0 -78
- package/dist/client/auth/accountPool.js +0 -178
- package/dist/client/auth/anthropicOAuth.js +0 -974
- package/dist/client/auth/authContext.js +0 -314
- package/dist/client/auth/errors.js +0 -39
- package/dist/client/auth/index.js +0 -61
- package/dist/client/auth/middleware/AuthMiddleware.js +0 -519
- package/dist/client/auth/middleware/rateLimitByUser.js +0 -554
- package/dist/client/auth/providers/BaseAuthProvider.js +0 -723
- package/dist/client/auth/providers/CognitoProvider.js +0 -304
- package/dist/client/auth/providers/KeycloakProvider.js +0 -393
- package/dist/client/auth/providers/auth0.js +0 -274
- package/dist/client/auth/providers/betterAuth.js +0 -182
- package/dist/client/auth/providers/clerk.js +0 -317
- package/dist/client/auth/providers/custom.js +0 -112
- package/dist/client/auth/providers/firebase.js +0 -226
- package/dist/client/auth/providers/jwt.js +0 -212
- package/dist/client/auth/providers/oauth2.js +0 -303
- package/dist/client/auth/providers/supabase.js +0 -259
- package/dist/client/auth/providers/workos.js +0 -284
- package/dist/client/auth/serverBridge.js +0 -25
- package/dist/client/auth/sessionManager.js +0 -437
- package/dist/client/auth/tokenStore.js +0 -799
- package/dist/client/client/aiSdkAdapter.js +0 -487
- package/dist/client/client/auth.js +0 -473
- package/dist/client/client/errors.js +0 -552
- package/dist/client/client/httpClient.js +0 -837
- package/dist/client/client/index.js +0 -172
- package/dist/client/client/interceptors.js +0 -601
- package/dist/client/client/sseClient.js +0 -545
- package/dist/client/client/streamingClient.js +0 -917
- package/dist/client/client/wsClient.js +0 -369
- package/dist/client/config/configManager.js +0 -303
- package/dist/client/config/conversationMemory.js +0 -86
- package/dist/client/config/taskClassificationConfig.js +0 -148
- package/dist/client/constants/contextWindows.js +0 -295
- package/dist/client/constants/enums.js +0 -853
- package/dist/client/constants/index.js +0 -207
- package/dist/client/constants/performance.js +0 -389
- package/dist/client/constants/retry.js +0 -266
- package/dist/client/constants/timeouts.js +0 -182
- package/dist/client/constants/tokens.js +0 -380
- package/dist/client/constants/videoErrors.js +0 -46
- package/dist/client/context/budgetChecker.js +0 -98
- package/dist/client/context/contextCompactor.js +0 -205
- package/dist/client/context/emergencyTruncation.js +0 -88
- package/dist/client/context/errorDetection.js +0 -171
- package/dist/client/context/errors.js +0 -21
- package/dist/client/context/fileTokenBudget.js +0 -127
- package/dist/client/context/prompts/summarizationPrompt.js +0 -117
- package/dist/client/context/stages/fileReadDeduplicator.js +0 -66
- package/dist/client/context/stages/slidingWindowTruncator.js +0 -190
- package/dist/client/context/stages/structuredSummarizer.js +0 -99
- package/dist/client/context/stages/toolOutputPruner.js +0 -52
- package/dist/client/context/summarizationEngine.js +0 -136
- package/dist/client/context/toolOutputLimits.js +0 -78
- package/dist/client/context/toolPairRepair.js +0 -66
- package/dist/client/core/analytics.js +0 -88
- package/dist/client/core/baseProvider.js +0 -1385
- package/dist/client/core/constants.js +0 -140
- package/dist/client/core/conversationMemoryFactory.js +0 -141
- package/dist/client/core/conversationMemoryInitializer.js +0 -128
- package/dist/client/core/conversationMemoryManager.js +0 -344
- package/dist/client/core/dynamicModels.js +0 -358
- package/dist/client/core/evaluation.js +0 -309
- package/dist/client/core/evaluationProviders.js +0 -248
- package/dist/client/core/factory.js +0 -412
- package/dist/client/core/infrastructure/baseError.js +0 -22
- package/dist/client/core/infrastructure/baseFactory.js +0 -54
- package/dist/client/core/infrastructure/baseRegistry.js +0 -53
- package/dist/client/core/infrastructure/index.js +0 -5
- package/dist/client/core/infrastructure/retry.js +0 -20
- package/dist/client/core/infrastructure/typedEventEmitter.js +0 -23
- package/dist/client/core/modelConfiguration.js +0 -851
- package/dist/client/core/modules/GenerationHandler.js +0 -588
- package/dist/client/core/modules/MessageBuilder.js +0 -273
- package/dist/client/core/modules/StreamHandler.js +0 -185
- package/dist/client/core/modules/TelemetryHandler.js +0 -203
- package/dist/client/core/modules/ToolsManager.js +0 -499
- package/dist/client/core/modules/Utilities.js +0 -331
- package/dist/client/core/redisConversationMemoryManager.js +0 -1435
- package/dist/client/core/streamAnalytics.js +0 -131
- package/dist/client/evaluation/contextBuilder.js +0 -134
- package/dist/client/evaluation/index.js +0 -61
- package/dist/client/evaluation/prompts.js +0 -73
- package/dist/client/evaluation/ragasEvaluator.js +0 -110
- package/dist/client/evaluation/retryManager.js +0 -78
- package/dist/client/evaluation/scoring.js +0 -61
- package/dist/client/factories/providerFactory.js +0 -166
- package/dist/client/factories/providerRegistry.js +0 -166
- package/dist/client/features/ppt/constants.js +0 -896
- package/dist/client/features/ppt/contentPlanner.js +0 -529
- package/dist/client/features/ppt/presentationOrchestrator.js +0 -236
- package/dist/client/features/ppt/slideGenerator.js +0 -532
- package/dist/client/features/ppt/slideRenderers.js +0 -2383
- package/dist/client/features/ppt/slideTypeInference.js +0 -405
- package/dist/client/features/ppt/types.js +0 -13
- package/dist/client/features/ppt/utils.js +0 -443
- package/dist/client/files/fileReferenceRegistry.js +0 -1543
- package/dist/client/files/fileTools.js +0 -450
- package/dist/client/files/streamingReader.js +0 -321
- package/dist/client/files/types.js +0 -23
- package/dist/client/hitl/hitlErrors.js +0 -54
- package/dist/client/hitl/hitlManager.js +0 -460
- package/dist/client/mcp/agentExposure.js +0 -356
- package/dist/client/mcp/auth/index.js +0 -11
- package/dist/client/mcp/auth/oauthClientProvider.js +0 -325
- package/dist/client/mcp/auth/tokenStorage.js +0 -134
- package/dist/client/mcp/batching/index.js +0 -10
- package/dist/client/mcp/batching/requestBatcher.js +0 -441
- package/dist/client/mcp/caching/index.js +0 -10
- package/dist/client/mcp/caching/toolCache.js +0 -433
- package/dist/client/mcp/elicitation/elicitationManager.js +0 -376
- package/dist/client/mcp/elicitation/index.js +0 -11
- package/dist/client/mcp/elicitation/types.js +0 -10
- package/dist/client/mcp/elicitationProtocol.js +0 -375
- package/dist/client/mcp/enhancedToolDiscovery.js +0 -481
- package/dist/client/mcp/externalServerManager.js +0 -1478
- package/dist/client/mcp/factory.js +0 -161
- package/dist/client/mcp/flexibleToolValidator.js +0 -161
- package/dist/client/mcp/httpRateLimiter.js +0 -391
- package/dist/client/mcp/httpRetryHandler.js +0 -178
- package/dist/client/mcp/index.js +0 -74
- package/dist/client/mcp/mcpCircuitBreaker.js +0 -427
- package/dist/client/mcp/mcpClientFactory.js +0 -708
- package/dist/client/mcp/mcpRegistryClient.js +0 -488
- package/dist/client/mcp/mcpServerBase.js +0 -373
- package/dist/client/mcp/multiServerManager.js +0 -579
- package/dist/client/mcp/registry.js +0 -158
- package/dist/client/mcp/routing/index.js +0 -10
- package/dist/client/mcp/routing/toolRouter.js +0 -416
- package/dist/client/mcp/serverCapabilities.js +0 -502
- package/dist/client/mcp/servers/agent/directToolsServer.js +0 -150
- package/dist/client/mcp/toolAnnotations.js +0 -239
- package/dist/client/mcp/toolConverter.js +0 -258
- package/dist/client/mcp/toolDiscoveryService.js +0 -798
- package/dist/client/mcp/toolIntegration.js +0 -334
- package/dist/client/mcp/toolRegistry.js +0 -729
- package/dist/client/memory/hippocampusInitializer.js +0 -19
- package/dist/client/memory/memoryRetrievalTools.js +0 -166
- package/dist/client/middleware/builtin/analytics.js +0 -132
- package/dist/client/middleware/builtin/autoEvaluation.js +0 -203
- package/dist/client/middleware/builtin/guardrails.js +0 -109
- package/dist/client/middleware/builtin/lifecycle.js +0 -168
- package/dist/client/middleware/factory.js +0 -327
- package/dist/client/middleware/registry.js +0 -295
- package/dist/client/middleware/utils/guardrailsUtils.js +0 -396
- package/dist/client/models/anthropicModels.js +0 -527
- package/dist/client/neurolink.js +0 -8233
- package/dist/client/observability/exporterRegistry.js +0 -413
- package/dist/client/observability/exporters/arizeExporter.js +0 -138
- package/dist/client/observability/exporters/baseExporter.js +0 -190
- package/dist/client/observability/exporters/braintrustExporter.js +0 -154
- package/dist/client/observability/exporters/datadogExporter.js +0 -196
- package/dist/client/observability/exporters/laminarExporter.js +0 -302
- package/dist/client/observability/exporters/langfuseExporter.js +0 -209
- package/dist/client/observability/exporters/langsmithExporter.js +0 -143
- package/dist/client/observability/exporters/otelExporter.js +0 -164
- package/dist/client/observability/exporters/posthogExporter.js +0 -287
- package/dist/client/observability/exporters/sentryExporter.js +0 -165
- package/dist/client/observability/index.js +0 -31
- package/dist/client/observability/metricsAggregator.js +0 -556
- package/dist/client/observability/otelBridge.js +0 -131
- package/dist/client/observability/retryPolicy.js +0 -383
- package/dist/client/observability/sampling/samplers.js +0 -216
- package/dist/client/observability/spanProcessor.js +0 -303
- package/dist/client/observability/tokenTracker.js +0 -413
- package/dist/client/observability/types/exporterTypes.js +0 -5
- package/dist/client/observability/types/index.js +0 -4
- package/dist/client/observability/types/spanTypes.js +0 -92
- package/dist/client/observability/utils/safeMetadata.js +0 -25
- package/dist/client/observability/utils/spanSerializer.js +0 -292
- package/dist/client/processors/archive/ArchiveProcessor.js +0 -1308
- package/dist/client/processors/base/BaseFileProcessor.js +0 -614
- package/dist/client/processors/base/types.js +0 -82
- package/dist/client/processors/config/fileTypes.js +0 -520
- package/dist/client/processors/config/index.js +0 -92
- package/dist/client/processors/config/languageMap.js +0 -410
- package/dist/client/processors/config/mimeTypes.js +0 -363
- package/dist/client/processors/config/sizeLimits.js +0 -258
- package/dist/client/processors/document/ExcelProcessor.js +0 -590
- package/dist/client/processors/document/OpenDocumentProcessor.js +0 -212
- package/dist/client/processors/document/PptxProcessor.js +0 -157
- package/dist/client/processors/document/RtfProcessor.js +0 -361
- package/dist/client/processors/document/WordProcessor.js +0 -353
- package/dist/client/processors/errors/FileErrorCode.js +0 -255
- package/dist/client/processors/errors/errorHelpers.js +0 -386
- package/dist/client/processors/errors/errorSerializer.js +0 -507
- package/dist/client/processors/errors/index.js +0 -49
- package/dist/client/processors/markup/SvgProcessor.js +0 -240
- package/dist/client/processors/media/AudioProcessor.js +0 -707
- package/dist/client/processors/media/VideoProcessor.js +0 -1045
- package/dist/client/providers/amazonBedrock.js +0 -1512
- package/dist/client/providers/amazonSagemaker.js +0 -162
- package/dist/client/providers/anthropic.js +0 -831
- package/dist/client/providers/azureOpenai.js +0 -143
- package/dist/client/providers/googleAiStudio.js +0 -1200
- package/dist/client/providers/googleNativeGemini3.js +0 -543
- package/dist/client/providers/googleVertex.js +0 -2936
- package/dist/client/providers/huggingFace.js +0 -315
- package/dist/client/providers/litellm.js +0 -488
- package/dist/client/providers/mistral.js +0 -157
- package/dist/client/providers/ollama.js +0 -1579
- package/dist/client/providers/openAI.js +0 -627
- package/dist/client/providers/openRouter.js +0 -543
- package/dist/client/providers/openaiCompatible.js +0 -290
- package/dist/client/providers/providerTypeUtils.js +0 -46
- package/dist/client/providers/sagemaker/adaptive-semaphore.js +0 -215
- package/dist/client/providers/sagemaker/client.js +0 -472
- package/dist/client/providers/sagemaker/config.js +0 -317
- package/dist/client/providers/sagemaker/detection.js +0 -606
- package/dist/client/providers/sagemaker/error-constants.js +0 -227
- package/dist/client/providers/sagemaker/errors.js +0 -299
- package/dist/client/providers/sagemaker/language-model.js +0 -775
- package/dist/client/providers/sagemaker/parsers.js +0 -634
- package/dist/client/providers/sagemaker/streaming.js +0 -331
- package/dist/client/providers/sagemaker/structured-parser.js +0 -625
- package/dist/client/proxy/accountQuota.js +0 -162
- package/dist/client/proxy/claudeFormat.js +0 -595
- package/dist/client/proxy/modelRouter.js +0 -29
- package/dist/client/proxy/oauthFetch.js +0 -367
- package/dist/client/proxy/proxyFetch.js +0 -586
- package/dist/client/proxy/requestLogger.js +0 -207
- package/dist/client/proxy/tokenRefresh.js +0 -124
- package/dist/client/proxy/usageStats.js +0 -74
- package/dist/client/proxy/utils/noProxyUtils.js +0 -149
- package/dist/client/rag/ChunkerFactory.js +0 -320
- package/dist/client/rag/ChunkerRegistry.js +0 -421
- package/dist/client/rag/chunkers/BaseChunker.js +0 -143
- package/dist/client/rag/chunkers/CharacterChunker.js +0 -28
- package/dist/client/rag/chunkers/HTMLChunker.js +0 -38
- package/dist/client/rag/chunkers/JSONChunker.js +0 -68
- package/dist/client/rag/chunkers/LaTeXChunker.js +0 -63
- package/dist/client/rag/chunkers/MarkdownChunker.js +0 -306
- package/dist/client/rag/chunkers/RecursiveChunker.js +0 -139
- package/dist/client/rag/chunkers/SemanticMarkdownChunker.js +0 -138
- package/dist/client/rag/chunkers/SentenceChunker.js +0 -66
- package/dist/client/rag/chunkers/TokenChunker.js +0 -61
- package/dist/client/rag/chunkers/index.js +0 -15
- package/dist/client/rag/chunking/characterChunker.js +0 -142
- package/dist/client/rag/chunking/chunkerRegistry.js +0 -194
- package/dist/client/rag/chunking/htmlChunker.js +0 -247
- package/dist/client/rag/chunking/index.js +0 -17
- package/dist/client/rag/chunking/jsonChunker.js +0 -281
- package/dist/client/rag/chunking/latexChunker.js +0 -251
- package/dist/client/rag/chunking/markdownChunker.js +0 -373
- package/dist/client/rag/chunking/recursiveChunker.js +0 -148
- package/dist/client/rag/chunking/semanticChunker.js +0 -306
- package/dist/client/rag/chunking/sentenceChunker.js +0 -230
- package/dist/client/rag/chunking/tokenChunker.js +0 -183
- package/dist/client/rag/document/MDocument.js +0 -392
- package/dist/client/rag/document/index.js +0 -5
- package/dist/client/rag/document/loaders.js +0 -500
- package/dist/client/rag/errors/RAGError.js +0 -274
- package/dist/client/rag/errors/index.js +0 -6
- package/dist/client/rag/graphRag/graphRAG.js +0 -401
- package/dist/client/rag/graphRag/index.js +0 -4
- package/dist/client/rag/index.js +0 -141
- package/dist/client/rag/metadata/MetadataExtractorFactory.js +0 -418
- package/dist/client/rag/metadata/MetadataExtractorRegistry.js +0 -362
- package/dist/client/rag/metadata/index.js +0 -9
- package/dist/client/rag/metadata/metadataExtractor.js +0 -280
- package/dist/client/rag/pipeline/RAGPipeline.js +0 -436
- package/dist/client/rag/pipeline/contextAssembly.js +0 -341
- package/dist/client/rag/pipeline/index.js +0 -5
- package/dist/client/rag/ragIntegration.js +0 -321
- package/dist/client/rag/reranker/RerankerFactory.js +0 -430
- package/dist/client/rag/reranker/RerankerRegistry.js +0 -402
- package/dist/client/rag/reranker/index.js +0 -9
- package/dist/client/rag/reranker/reranker.js +0 -277
- package/dist/client/rag/resilience/CircuitBreaker.js +0 -431
- package/dist/client/rag/resilience/RetryHandler.js +0 -304
- package/dist/client/rag/resilience/index.js +0 -7
- package/dist/client/rag/retrieval/hybridSearch.js +0 -335
- package/dist/client/rag/retrieval/index.js +0 -5
- package/dist/client/rag/retrieval/vectorQueryTool.js +0 -307
- package/dist/client/rag/types.js +0 -8
- package/dist/client/sdk/toolRegistration.js +0 -377
- package/dist/client/server/abstract/baseServerAdapter.js +0 -575
- package/dist/client/server/adapters/expressAdapter.js +0 -486
- package/dist/client/server/adapters/fastifyAdapter.js +0 -472
- package/dist/client/server/adapters/honoAdapter.js +0 -632
- package/dist/client/server/adapters/koaAdapter.js +0 -510
- package/dist/client/server/errors.js +0 -486
- package/dist/client/server/factory/serverAdapterFactory.js +0 -160
- package/dist/client/server/index.js +0 -108
- package/dist/client/server/middleware/abortSignal.js +0 -111
- package/dist/client/server/middleware/auth.js +0 -388
- package/dist/client/server/middleware/cache.js +0 -359
- package/dist/client/server/middleware/common.js +0 -281
- package/dist/client/server/middleware/deprecation.js +0 -190
- package/dist/client/server/middleware/mcpBodyAttachment.js +0 -63
- package/dist/client/server/middleware/rateLimit.js +0 -227
- package/dist/client/server/middleware/validation.js +0 -388
- package/dist/client/server/openapi/generator.js +0 -398
- package/dist/client/server/openapi/index.js +0 -36
- package/dist/client/server/openapi/schemas.js +0 -695
- package/dist/client/server/openapi/templates.js +0 -374
- package/dist/client/server/routes/agentRoutes.js +0 -189
- package/dist/client/server/routes/claudeProxyRoutes.js +0 -1600
- package/dist/client/server/routes/healthRoutes.js +0 -187
- package/dist/client/server/routes/index.js +0 -57
- package/dist/client/server/routes/mcpRoutes.js +0 -342
- package/dist/client/server/routes/memoryRoutes.js +0 -350
- package/dist/client/server/routes/openApiRoutes.js +0 -126
- package/dist/client/server/routes/toolRoutes.js +0 -199
- package/dist/client/server/streaming/dataStream.js +0 -486
- package/dist/client/server/streaming/index.js +0 -11
- package/dist/client/server/types.js +0 -67
- package/dist/client/server/utils/redaction.js +0 -334
- package/dist/client/server/utils/validation.js +0 -243
- package/dist/client/server/websocket/WebSocketHandler.js +0 -383
- package/dist/client/server/websocket/index.js +0 -4
- package/dist/client/services/server/ai/observability/instrumentation.js +0 -808
- package/dist/client/telemetry/attributes.js +0 -100
- package/dist/client/telemetry/index.js +0 -26
- package/dist/client/telemetry/telemetryService.js +0 -308
- package/dist/client/telemetry/tracers.js +0 -17
- package/dist/client/telemetry/withSpan.js +0 -34
- package/dist/client/types/actionTypes.js +0 -6
- package/dist/client/types/analytics.js +0 -5
- package/dist/client/types/authTypes.js +0 -9
- package/dist/client/types/circuitBreakerErrors.js +0 -34
- package/dist/client/types/cli.js +0 -21
- package/dist/client/types/clientTypes.js +0 -10
- package/dist/client/types/common.js +0 -51
- package/dist/client/types/configTypes.js +0 -49
- package/dist/client/types/content.js +0 -19
- package/dist/client/types/contextTypes.js +0 -400
- package/dist/client/types/conversation.js +0 -47
- package/dist/client/types/conversationMemoryInterface.js +0 -6
- package/dist/client/types/domainTypes.js +0 -5
- package/dist/client/types/errors.js +0 -167
- package/dist/client/types/evaluation.js +0 -5
- package/dist/client/types/evaluationProviders.js +0 -5
- package/dist/client/types/evaluationTypes.js +0 -1
- package/dist/client/types/externalMcp.js +0 -6
- package/dist/client/types/fileReferenceTypes.js +0 -8
- package/dist/client/types/fileTypes.js +0 -4
- package/dist/client/types/generateTypes.js +0 -1
- package/dist/client/types/guardrails.js +0 -1
- package/dist/client/types/hitlTypes.js +0 -8
- package/dist/client/types/index.js +0 -57
- package/dist/client/types/mcpTypes.js +0 -5
- package/dist/client/types/middlewareTypes.js +0 -1
- package/dist/client/types/modelTypes.js +0 -30
- package/dist/client/types/multimodal.js +0 -135
- package/dist/client/types/observability.js +0 -6
- package/dist/client/types/pptTypes.js +0 -82
- package/dist/client/types/providers.js +0 -111
- package/dist/client/types/proxyTypes.js +0 -16
- package/dist/client/types/ragTypes.js +0 -7
- package/dist/client/types/sdkTypes.js +0 -8
- package/dist/client/types/serviceTypes.js +0 -5
- package/dist/client/types/streamTypes.js +0 -1
- package/dist/client/types/subscriptionTypes.js +0 -9
- package/dist/client/types/taskClassificationTypes.js +0 -5
- package/dist/client/types/tools.js +0 -24
- package/dist/client/types/ttsTypes.js +0 -57
- package/dist/client/types/typeAliases.js +0 -48
- package/dist/client/types/utilities.js +0 -4
- package/dist/client/types/workflowTypes.js +0 -30
- package/dist/client/utils/async/withTimeout.js +0 -98
- package/dist/client/utils/asyncMutex.js +0 -60
- package/dist/client/utils/conversationMemory.js +0 -431
- package/dist/client/utils/csvProcessor.js +0 -846
- package/dist/client/utils/errorHandling.js +0 -936
- package/dist/client/utils/evaluationUtils.js +0 -131
- package/dist/client/utils/factoryProcessing.js +0 -589
- package/dist/client/utils/fileDetector.js +0 -2161
- package/dist/client/utils/imageCache.js +0 -376
- package/dist/client/utils/imageProcessor.js +0 -704
- package/dist/client/utils/logger.js +0 -491
- package/dist/client/utils/mcpDefaults.js +0 -134
- package/dist/client/utils/messageBuilder.js +0 -1653
- package/dist/client/utils/modelAliasResolver.js +0 -54
- package/dist/client/utils/modelDetection.js +0 -80
- package/dist/client/utils/modelRouter.js +0 -292
- package/dist/client/utils/multimodalOptionsBuilder.js +0 -65
- package/dist/client/utils/observabilityHelpers.js +0 -47
- package/dist/client/utils/parameterValidation.js +0 -966
- package/dist/client/utils/pdfProcessor.js +0 -410
- package/dist/client/utils/performance.js +0 -222
- package/dist/client/utils/pricing.js +0 -340
- package/dist/client/utils/promptRedaction.js +0 -62
- package/dist/client/utils/providerConfig.js +0 -1009
- package/dist/client/utils/providerHealth.js +0 -1237
- package/dist/client/utils/providerRetry.js +0 -112
- package/dist/client/utils/providerUtils.js +0 -434
- package/dist/client/utils/rateLimiter.js +0 -200
- package/dist/client/utils/redis.js +0 -368
- package/dist/client/utils/retryHandler.js +0 -269
- package/dist/client/utils/retryability.js +0 -22
- package/dist/client/utils/sanitizers/svg.js +0 -481
- package/dist/client/utils/schemaConversion.js +0 -255
- package/dist/client/utils/taskClassificationUtils.js +0 -149
- package/dist/client/utils/taskClassifier.js +0 -94
- package/dist/client/utils/thinkingConfig.js +0 -104
- package/dist/client/utils/timeout.js +0 -359
- package/dist/client/utils/tokenEstimation.js +0 -142
- package/dist/client/utils/tokenLimits.js +0 -125
- package/dist/client/utils/tokenUtils.js +0 -239
- package/dist/client/utils/toolUtils.js +0 -75
- package/dist/client/utils/transformationUtils.js +0 -554
- package/dist/client/utils/ttsProcessor.js +0 -286
- package/dist/client/utils/typeUtils.js +0 -97
- package/dist/client/utils/videoAnalysisProcessor.js +0 -67
- package/dist/client/workflow/config.js +0 -398
- package/dist/client/workflow/core/ensembleExecutor.js +0 -407
- package/dist/client/workflow/core/judgeScorer.js +0 -544
- package/dist/client/workflow/core/responseConditioner.js +0 -225
- package/dist/client/workflow/core/types/conditionerTypes.js +0 -7
- package/dist/client/workflow/core/types/ensembleTypes.js +0 -7
- package/dist/client/workflow/core/types/index.js +0 -7
- package/dist/client/workflow/core/types/judgeTypes.js +0 -7
- package/dist/client/workflow/core/types/layerTypes.js +0 -7
- package/dist/client/workflow/core/types/registryTypes.js +0 -7
- package/dist/client/workflow/core/workflowRegistry.js +0 -304
- package/dist/client/workflow/core/workflowRunner.js +0 -586
- package/dist/client/workflow/index.js +0 -50
- package/dist/client/workflow/types.js +0 -9
- package/dist/client/workflow/utils/types/index.js +0 -7
- package/dist/client/workflow/utils/workflowMetrics.js +0 -311
- package/dist/client/workflow/utils/workflowValidation.js +0 -420
- package/dist/client/workflow/workflows/adaptiveWorkflow.js +0 -366
- package/dist/client/workflow/workflows/consensusWorkflow.js +0 -192
- package/dist/client/workflow/workflows/fallbackWorkflow.js +0 -225
- package/dist/client/workflow/workflows/multiJudgeWorkflow.js +0 -351
- /package/dist/client/{client/reactHooks.js → reactHooks.js} +0 -0
|
@@ -1,1435 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Redis Conversation Memory Manager for NeuroLink
|
|
3
|
-
* Redis-based implementation of conversation storage with same interface as ConversationMemoryManager
|
|
4
|
-
*/
|
|
5
|
-
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
6
|
-
import { tracers } from "../telemetry/tracers.js";
|
|
7
|
-
import { randomUUID } from "crypto";
|
|
8
|
-
import { MESSAGES_PER_TURN } from "../config/conversationMemory.js";
|
|
9
|
-
import { generateToolOutputPreview } from "../context/toolOutputLimits.js";
|
|
10
|
-
import { SummarizationEngine } from "../context/summarizationEngine.js";
|
|
11
|
-
import { NeuroLink } from "../neurolink.js";
|
|
12
|
-
import { ConversationMemoryError } from "../types/conversation.js";
|
|
13
|
-
import { withTimeout } from "../utils/errorHandling.js";
|
|
14
|
-
import { buildContextFromPointer, getEffectiveTokenThreshold, } from "../utils/conversationMemory.js";
|
|
15
|
-
import { runWithCurrentLangfuseContext } from "../services/server/ai/observability/instrumentation.js";
|
|
16
|
-
import { logger } from "../utils/logger.js";
|
|
17
|
-
import { deserializeConversation, getNormalizedConfig, getPooledRedisClient, getSessionKey, getUserSessionsKey, releasePooledRedisClient, scanKeys, serializeConversation, } from "../utils/redis.js";
|
|
18
|
-
const redisTracer = tracers.redis;
|
|
19
|
-
/**
|
|
20
|
-
* Redis-based implementation of the ConversationMemoryManager
|
|
21
|
-
* Uses the same interface but stores data in Redis
|
|
22
|
-
*/
|
|
23
|
-
export class RedisConversationMemoryManager {
|
|
24
|
-
config;
|
|
25
|
-
isInitialized = false;
|
|
26
|
-
summarizationEngine = new SummarizationEngine();
|
|
27
|
-
redisConfig;
|
|
28
|
-
redisClient = null;
|
|
29
|
-
/**
|
|
30
|
-
* Temporary storage for tool execution data to prevent race conditions
|
|
31
|
-
* Key format: "${sessionId}:${userId}"
|
|
32
|
-
*/
|
|
33
|
-
pendingToolExecutions = new Map();
|
|
34
|
-
/**
|
|
35
|
-
* Track sessions currently generating titles to prevent race conditions
|
|
36
|
-
* Key format: "${sessionId}:${userId}"
|
|
37
|
-
*/
|
|
38
|
-
titleGenerationInProgress = new Set();
|
|
39
|
-
/**
|
|
40
|
-
* Track sessions currently being summarized to prevent race conditions
|
|
41
|
-
* Key format: "${sessionId}:${userId}"
|
|
42
|
-
*/
|
|
43
|
-
summarizationInProgress = new Set();
|
|
44
|
-
constructor(config, redisConfig = {}) {
|
|
45
|
-
this.config = config;
|
|
46
|
-
this.redisConfig = getNormalizedConfig(redisConfig);
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Initialize the memory manager with Redis connection
|
|
50
|
-
*/
|
|
51
|
-
async initialize() {
|
|
52
|
-
if (this.isInitialized) {
|
|
53
|
-
logger.debug("[RedisConversationMemoryManager] Already initialized, skipping");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
await redisTracer.startActiveSpan("neurolink.memory.initialize", {
|
|
57
|
-
kind: SpanKind.CLIENT,
|
|
58
|
-
attributes: {
|
|
59
|
-
"redis.host": this.redisConfig.host,
|
|
60
|
-
"redis.port": this.redisConfig.port,
|
|
61
|
-
"redis.key_prefix": this.redisConfig.keyPrefix,
|
|
62
|
-
},
|
|
63
|
-
}, async (span) => {
|
|
64
|
-
try {
|
|
65
|
-
logger.debug("[RedisConversationMemoryManager] Initializing with config", {
|
|
66
|
-
host: this.redisConfig.host,
|
|
67
|
-
port: this.redisConfig.port,
|
|
68
|
-
keyPrefix: this.redisConfig.keyPrefix,
|
|
69
|
-
ttl: this.redisConfig.ttl,
|
|
70
|
-
});
|
|
71
|
-
this.redisClient = await getPooledRedisClient(this.redisConfig);
|
|
72
|
-
this.isInitialized = true;
|
|
73
|
-
logger.info("RedisConversationMemoryManager initialized", {
|
|
74
|
-
storage: "redis",
|
|
75
|
-
host: this.redisConfig.host,
|
|
76
|
-
port: this.redisConfig.port,
|
|
77
|
-
maxSessions: this.config.maxSessions,
|
|
78
|
-
maxTurnsPerSession: this.config.maxTurnsPerSession,
|
|
79
|
-
});
|
|
80
|
-
logger.debug("[RedisConversationMemoryManager] Redis client created successfully", {
|
|
81
|
-
clientType: this.redisClient?.constructor?.name || "unknown",
|
|
82
|
-
isConnected: !!this.redisClient,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
span.setStatus({
|
|
87
|
-
code: SpanStatusCode.ERROR,
|
|
88
|
-
message: error instanceof Error ? error.message : String(error),
|
|
89
|
-
});
|
|
90
|
-
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
91
|
-
logger.error("[RedisConversationMemoryManager] Failed to initialize", {
|
|
92
|
-
error: error instanceof Error ? error.message : String(error),
|
|
93
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
94
|
-
config: {
|
|
95
|
-
host: this.redisConfig.host,
|
|
96
|
-
port: this.redisConfig.port,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
throw new ConversationMemoryError("Failed to initialize Redis conversation memory", "CONFIG_ERROR", {
|
|
100
|
-
error: error instanceof Error ? error.message : String(error),
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
finally {
|
|
104
|
-
span.end();
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
/** Whether this memory manager can persist data (Redis connected and initialized) */
|
|
109
|
-
get canPersist() {
|
|
110
|
-
return (this.isInitialized && this.redisClient !== null && this.redisClient.isOpen);
|
|
111
|
-
}
|
|
112
|
-
/** Whether Redis client is configured and connected */
|
|
113
|
-
get isRedisConfigured() {
|
|
114
|
-
return this.redisClient !== null && this.redisClient.isOpen;
|
|
115
|
-
}
|
|
116
|
-
/** Get health status for monitoring */
|
|
117
|
-
getHealthStatus() {
|
|
118
|
-
return {
|
|
119
|
-
initialized: this.isInitialized,
|
|
120
|
-
connected: this.redisClient?.isOpen ?? false,
|
|
121
|
-
host: this.redisConfig.host,
|
|
122
|
-
keyPrefix: this.redisConfig.keyPrefix,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Get session by ID, reconstructing a SessionMemory from Redis storage.
|
|
127
|
-
*/
|
|
128
|
-
async getSession(sessionId, userId, requestId) {
|
|
129
|
-
await this.ensureInitialized();
|
|
130
|
-
if (!this.redisClient) {
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
133
|
-
return redisTracer.startActiveSpan("neurolink.memory.getSession", { kind: SpanKind.CLIENT, attributes: { "session.id": sessionId } }, async (span) => {
|
|
134
|
-
if (userId) {
|
|
135
|
-
span.setAttribute("user.id", userId);
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
139
|
-
const conversationData = await this.redisClient.get(redisKey);
|
|
140
|
-
const conversation = deserializeConversation(conversationData || null);
|
|
141
|
-
if (!conversation) {
|
|
142
|
-
span.setAttribute("session.found", false);
|
|
143
|
-
return undefined;
|
|
144
|
-
}
|
|
145
|
-
span.setAttribute("session.found", true);
|
|
146
|
-
// Log session load metadata for observability
|
|
147
|
-
const blobSizeBytes = conversationData
|
|
148
|
-
? Buffer.byteLength(conversationData, "utf8")
|
|
149
|
-
: 0;
|
|
150
|
-
const messageCount = conversation.messages.length;
|
|
151
|
-
const hasSummary = !!conversation.summarizedUpToMessageId;
|
|
152
|
-
const pointerIndex = hasSummary
|
|
153
|
-
? conversation.messages.findIndex((msg) => msg.id === conversation.summarizedUpToMessageId)
|
|
154
|
-
: -1;
|
|
155
|
-
const recentMessageCount = hasSummary && pointerIndex !== -1
|
|
156
|
-
? messageCount - pointerIndex - 1
|
|
157
|
-
: messageCount;
|
|
158
|
-
span.setAttribute("message.count", messageCount);
|
|
159
|
-
span.setAttribute("blob.size_bytes", blobSizeBytes);
|
|
160
|
-
logger.info("[ConversationMemory] Session loaded", {
|
|
161
|
-
requestId,
|
|
162
|
-
sessionId,
|
|
163
|
-
blobSizeBytes,
|
|
164
|
-
messageCount,
|
|
165
|
-
hasSummary,
|
|
166
|
-
recentMessageCount,
|
|
167
|
-
});
|
|
168
|
-
if (blobSizeBytes > 512 * 1024) {
|
|
169
|
-
logger.warn("[ConversationMemory] Large session blob", {
|
|
170
|
-
requestId,
|
|
171
|
-
sessionId,
|
|
172
|
-
blobSizeBytes,
|
|
173
|
-
messageCount,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
sessionId: conversation.sessionId,
|
|
178
|
-
userId: conversation.userId,
|
|
179
|
-
messages: conversation.messages,
|
|
180
|
-
summarizedUpToMessageId: conversation.summarizedUpToMessageId,
|
|
181
|
-
summarizedMessage: conversation.summarizedMessage,
|
|
182
|
-
tokenThreshold: conversation.tokenThreshold,
|
|
183
|
-
lastTokenCount: conversation.lastTokenCount,
|
|
184
|
-
lastCountedAt: conversation.lastCountedAt,
|
|
185
|
-
lastApiTokenCount: conversation.lastApiTokenCount,
|
|
186
|
-
createdAt: new Date(conversation.createdAt).getTime(),
|
|
187
|
-
lastActivity: new Date(conversation.updatedAt).getTime(),
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
span.setStatus({
|
|
192
|
-
code: SpanStatusCode.ERROR,
|
|
193
|
-
message: error instanceof Error ? error.message : String(error),
|
|
194
|
-
});
|
|
195
|
-
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
196
|
-
logger.error("[RedisConversationMemoryManager] Failed to get session", {
|
|
197
|
-
sessionId,
|
|
198
|
-
userId,
|
|
199
|
-
error: error instanceof Error ? error.message : String(error),
|
|
200
|
-
});
|
|
201
|
-
return undefined;
|
|
202
|
-
}
|
|
203
|
-
finally {
|
|
204
|
-
span.end();
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Get raw session data without any filtering or transformation.
|
|
210
|
-
* Used by the memory retrieval tool and internal APIs that need
|
|
211
|
-
* access to full message data including unmodified tool outputs.
|
|
212
|
-
*/
|
|
213
|
-
async getSessionRaw(sessionId, userId) {
|
|
214
|
-
try {
|
|
215
|
-
await this.ensureInitialized();
|
|
216
|
-
if (!this.redisClient) {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
220
|
-
const conversationData = await this.redisClient.get(redisKey);
|
|
221
|
-
return deserializeConversation(conversationData || null);
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
logger.error("[RedisConversationMemoryManager] Failed to get raw session", {
|
|
225
|
-
sessionId,
|
|
226
|
-
userId,
|
|
227
|
-
error: error instanceof Error ? error.message : String(error),
|
|
228
|
-
});
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Get all sessions for a specific user
|
|
234
|
-
*/
|
|
235
|
-
async getUserSessions(userId) {
|
|
236
|
-
// Ensure initialization
|
|
237
|
-
await this.ensureInitialized();
|
|
238
|
-
if (!this.redisClient) {
|
|
239
|
-
logger.warn("[RedisConversationMemoryManager] Redis client not available", { userId });
|
|
240
|
-
return [];
|
|
241
|
-
}
|
|
242
|
-
try {
|
|
243
|
-
const userSessionsKey = getUserSessionsKey(this.redisConfig, userId);
|
|
244
|
-
const sessions = await this.redisClient.sMembers(userSessionsKey);
|
|
245
|
-
return Array.from(sessions).map(String);
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
logger.error("[RedisConversationMemoryManager] Failed to get user sessions", {
|
|
249
|
-
userId,
|
|
250
|
-
error: error instanceof Error ? error.message : String(error),
|
|
251
|
-
});
|
|
252
|
-
return [];
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Add a session to user's session set (private method)
|
|
257
|
-
*/
|
|
258
|
-
async addUserSession(userId, sessionId) {
|
|
259
|
-
if (!this.redisClient || !userId) {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
try {
|
|
263
|
-
const userSessionsKey = getUserSessionsKey(this.redisConfig, userId);
|
|
264
|
-
await this.redisClient.sAdd(userSessionsKey, sessionId);
|
|
265
|
-
if (this.redisConfig.ttl > 0) {
|
|
266
|
-
await this.redisClient.expire(userSessionsKey, this.redisConfig.ttl);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
logger.error("[RedisConversationMemoryManager] Failed to add session to user set", {
|
|
271
|
-
userId,
|
|
272
|
-
sessionId,
|
|
273
|
-
error: error instanceof Error ? error.message : String(error),
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Remove a session from user's session set (private method)
|
|
279
|
-
*/
|
|
280
|
-
async removeUserSession(userId, sessionId) {
|
|
281
|
-
if (!this.redisClient || !userId) {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
try {
|
|
285
|
-
const userSessionsKey = getUserSessionsKey(this.redisConfig, userId);
|
|
286
|
-
const result = await this.redisClient.sRem(userSessionsKey, sessionId);
|
|
287
|
-
return Number(result) > 0;
|
|
288
|
-
}
|
|
289
|
-
catch (error) {
|
|
290
|
-
logger.error("[RedisConversationMemoryManager] Failed to remove session from user set", {
|
|
291
|
-
userId,
|
|
292
|
-
sessionId,
|
|
293
|
-
error: error instanceof Error ? error.message : String(error),
|
|
294
|
-
});
|
|
295
|
-
return false;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Generate current timestamp in ISO format
|
|
300
|
-
*/
|
|
301
|
-
generateTimestamp() {
|
|
302
|
-
return new Date().toISOString();
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Store tool execution data for a session (temporarily to avoid race conditions)
|
|
306
|
-
*/
|
|
307
|
-
async storeToolExecution(sessionId, userId, toolCalls, toolResults, currentTime) {
|
|
308
|
-
logger.debug("[RedisConversationMemoryManager] Storing tool execution temporarily", {
|
|
309
|
-
sessionId,
|
|
310
|
-
userId,
|
|
311
|
-
toolCallsCount: toolCalls?.length || 0,
|
|
312
|
-
toolResultsCount: toolResults?.length || 0,
|
|
313
|
-
});
|
|
314
|
-
try {
|
|
315
|
-
const normalizedUserId = userId || "randomUser";
|
|
316
|
-
const pendingKey = `${sessionId}:${normalizedUserId}`;
|
|
317
|
-
// Store tool execution data temporarily to prevent race conditions
|
|
318
|
-
const pendingData = {
|
|
319
|
-
toolCalls: (toolCalls || []).map((call) => ({
|
|
320
|
-
...call,
|
|
321
|
-
timestamp: currentTime,
|
|
322
|
-
})),
|
|
323
|
-
toolResults: (toolResults || []).map((result) => ({
|
|
324
|
-
...result,
|
|
325
|
-
timestamp: currentTime,
|
|
326
|
-
})),
|
|
327
|
-
timestamp: Date.now(),
|
|
328
|
-
};
|
|
329
|
-
// Check if there's existing pending data and merge
|
|
330
|
-
const existingData = this.pendingToolExecutions.get(pendingKey);
|
|
331
|
-
if (existingData) {
|
|
332
|
-
logger.debug("[RedisConversationMemoryManager] Merging with existing pending tool data", {
|
|
333
|
-
sessionId,
|
|
334
|
-
existingToolCalls: existingData.toolCalls.length,
|
|
335
|
-
existingToolResults: existingData.toolResults.length,
|
|
336
|
-
newToolCalls: toolCalls?.length || 0,
|
|
337
|
-
newToolResults: toolResults?.length || 0,
|
|
338
|
-
});
|
|
339
|
-
// Merge tool calls and results
|
|
340
|
-
pendingData.toolCalls = [
|
|
341
|
-
...existingData.toolCalls,
|
|
342
|
-
...pendingData.toolCalls,
|
|
343
|
-
];
|
|
344
|
-
pendingData.toolResults = [
|
|
345
|
-
...existingData.toolResults,
|
|
346
|
-
...pendingData.toolResults,
|
|
347
|
-
];
|
|
348
|
-
}
|
|
349
|
-
this.pendingToolExecutions.set(pendingKey, pendingData);
|
|
350
|
-
logger.debug("[RedisConversationMemoryManager] Tool execution stored temporarily", {
|
|
351
|
-
sessionId,
|
|
352
|
-
userId: normalizedUserId,
|
|
353
|
-
pendingKey,
|
|
354
|
-
totalToolCalls: pendingData.toolCalls.length,
|
|
355
|
-
totalToolResults: pendingData.toolResults.length,
|
|
356
|
-
});
|
|
357
|
-
// Clean up stale pending data (older than 5 minutes)
|
|
358
|
-
this.cleanupStalePendingData();
|
|
359
|
-
}
|
|
360
|
-
catch (error) {
|
|
361
|
-
logger.error("[RedisConversationMemoryManager] Failed to store tool execution temporarily", {
|
|
362
|
-
sessionId,
|
|
363
|
-
error: error instanceof Error ? error.message : String(error),
|
|
364
|
-
});
|
|
365
|
-
// Don't throw - tool storage failures shouldn't break generation
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Store a conversation turn for a session
|
|
370
|
-
*/
|
|
371
|
-
async storeConversationTurn(options) {
|
|
372
|
-
logger.debug("[RedisConversationMemoryManager] Storing conversation turn", {
|
|
373
|
-
sessionId: options.sessionId,
|
|
374
|
-
userId: options.userId,
|
|
375
|
-
});
|
|
376
|
-
await this.ensureInitialized();
|
|
377
|
-
// NLK-GAP-012: Add span for storeTurn CRUD operation
|
|
378
|
-
return redisTracer.startActiveSpan("neurolink.memory.storeTurn", {
|
|
379
|
-
kind: SpanKind.CLIENT,
|
|
380
|
-
attributes: {
|
|
381
|
-
"session.id": options.sessionId,
|
|
382
|
-
...(options.userId && { "user.id": options.userId }),
|
|
383
|
-
},
|
|
384
|
-
}, async (span) => {
|
|
385
|
-
try {
|
|
386
|
-
if (!this.redisClient) {
|
|
387
|
-
throw new Error("Redis client not initialized");
|
|
388
|
-
}
|
|
389
|
-
const redisKey = getSessionKey(this.redisConfig, options.sessionId, options.userId);
|
|
390
|
-
const conversationData = await this.redisClient.get(redisKey);
|
|
391
|
-
let conversation = deserializeConversation(conversationData);
|
|
392
|
-
const currentTime = new Date().toISOString();
|
|
393
|
-
const normalizedUserId = options.userId || "randomUser";
|
|
394
|
-
if (!conversation) {
|
|
395
|
-
const titleGenerationKey = `${options.sessionId}:${normalizedUserId}`;
|
|
396
|
-
// Capture the current Langfuse ALS context before setImmediate,
|
|
397
|
-
// which breaks automatic AsyncLocalStorage propagation and would
|
|
398
|
-
// otherwise cause orphaned traces in Langfuse.
|
|
399
|
-
const generateTitleWithContext = runWithCurrentLangfuseContext(async () => {
|
|
400
|
-
if (this.titleGenerationInProgress.has(titleGenerationKey)) {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
this.titleGenerationInProgress.add(titleGenerationKey);
|
|
404
|
-
try {
|
|
405
|
-
const title = await this.generateConversationTitle(options.userMessage);
|
|
406
|
-
const updatedRedisKey = getSessionKey(this.redisConfig, options.sessionId, options.userId || undefined);
|
|
407
|
-
const updatedConversationData = await this.redisClient?.get(updatedRedisKey);
|
|
408
|
-
const updatedConversation = deserializeConversation(updatedConversationData || null);
|
|
409
|
-
if (updatedConversation) {
|
|
410
|
-
updatedConversation.title = title;
|
|
411
|
-
updatedConversation.updatedAt = new Date().toISOString();
|
|
412
|
-
const serializedData = serializeConversation(updatedConversation);
|
|
413
|
-
await this.redisClient?.set(updatedRedisKey, serializedData);
|
|
414
|
-
if (this.redisConfig.ttl > 0) {
|
|
415
|
-
await this.redisClient?.expire(updatedRedisKey, this.redisConfig.ttl);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
catch (titleError) {
|
|
420
|
-
logger.warn("[RedisConversationMemoryManager] Failed to generate conversation title in background", {
|
|
421
|
-
sessionId: options.sessionId,
|
|
422
|
-
userId: normalizedUserId,
|
|
423
|
-
error: titleError instanceof Error
|
|
424
|
-
? titleError.message
|
|
425
|
-
: String(titleError),
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
finally {
|
|
429
|
-
this.titleGenerationInProgress.delete(titleGenerationKey);
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
setImmediate(generateTitleWithContext);
|
|
433
|
-
conversation = {
|
|
434
|
-
id: randomUUID(),
|
|
435
|
-
title: "New Conversation", // Temporary title until generated
|
|
436
|
-
sessionId: options.sessionId,
|
|
437
|
-
userId: normalizedUserId,
|
|
438
|
-
createdAt: options.startTimeStamp?.toISOString() || currentTime,
|
|
439
|
-
updatedAt: options.startTimeStamp?.toISOString() || currentTime,
|
|
440
|
-
messages: [],
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
conversation.updatedAt = currentTime;
|
|
445
|
-
}
|
|
446
|
-
const tokenThreshold = options.providerDetails
|
|
447
|
-
? getEffectiveTokenThreshold(options.providerDetails.provider, options.providerDetails.model, this.config.tokenThreshold, conversation.tokenThreshold)
|
|
448
|
-
: this.config.tokenThreshold || 50000;
|
|
449
|
-
const userMsg = {
|
|
450
|
-
id: randomUUID(),
|
|
451
|
-
timestamp: options.startTimeStamp?.toISOString() || this.generateTimestamp(),
|
|
452
|
-
role: "user",
|
|
453
|
-
content: options.userMessage,
|
|
454
|
-
};
|
|
455
|
-
conversation.messages.push(userMsg);
|
|
456
|
-
await this.flushPendingToolData(conversation, options.sessionId, normalizedUserId);
|
|
457
|
-
const assistantMsg = {
|
|
458
|
-
id: randomUUID(),
|
|
459
|
-
timestamp: this.generateTimestamp(),
|
|
460
|
-
role: "assistant",
|
|
461
|
-
content: options.aiResponse,
|
|
462
|
-
events: options.events || undefined,
|
|
463
|
-
};
|
|
464
|
-
conversation.messages.push(assistantMsg);
|
|
465
|
-
// Store API-reported token counts if available
|
|
466
|
-
if (options.tokenUsage) {
|
|
467
|
-
conversation.lastApiTokenCount = options.tokenUsage;
|
|
468
|
-
}
|
|
469
|
-
logger.info("[RedisConversationMemoryManager] Added new messages", {
|
|
470
|
-
sessionId: conversation.sessionId,
|
|
471
|
-
userId: conversation.userId,
|
|
472
|
-
});
|
|
473
|
-
// Use per-request enableSummarization with higher priority than instance config
|
|
474
|
-
const shouldSummarize = options.enableSummarization !== undefined
|
|
475
|
-
? options.enableSummarization
|
|
476
|
-
: this.config.enableSummarization;
|
|
477
|
-
if (shouldSummarize) {
|
|
478
|
-
const normalizedUserId = options.userId || "randomUser";
|
|
479
|
-
const summarizationKey = `${options.sessionId}:${normalizedUserId}`;
|
|
480
|
-
// Only trigger summarization if not already in progress for this session
|
|
481
|
-
if (!this.summarizationInProgress.has(summarizationKey)) {
|
|
482
|
-
// Capture the current Langfuse ALS context before setImmediate,
|
|
483
|
-
// which breaks automatic AsyncLocalStorage propagation and would
|
|
484
|
-
// otherwise cause orphaned traces in Langfuse.
|
|
485
|
-
const summarizeWithContext = runWithCurrentLangfuseContext(async () => {
|
|
486
|
-
try {
|
|
487
|
-
await this.checkAndSummarize(conversation, tokenThreshold, options.sessionId, options.userId, options.requestId);
|
|
488
|
-
}
|
|
489
|
-
catch (error) {
|
|
490
|
-
logger.error("Background summarization failed", {
|
|
491
|
-
sessionId: conversation.sessionId,
|
|
492
|
-
error: error instanceof Error ? error.message : String(error),
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
setImmediate(summarizeWithContext);
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
logger.debug("[RedisConversationMemoryManager] Summarization already in progress, skipping", {
|
|
500
|
-
sessionId: options.sessionId,
|
|
501
|
-
userId: normalizedUserId,
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
const serializedData = serializeConversation(conversation);
|
|
506
|
-
await this.redisClient.set(redisKey, serializedData);
|
|
507
|
-
// Log turn storage metadata for observability
|
|
508
|
-
const blobSizeBytes = Buffer.byteLength(serializedData, "utf8");
|
|
509
|
-
logger.info("[ConversationMemory] Turn stored", {
|
|
510
|
-
requestId: options.requestId,
|
|
511
|
-
sessionId: options.sessionId,
|
|
512
|
-
blobSizeBytes,
|
|
513
|
-
totalMessages: conversation.messages.length,
|
|
514
|
-
userMsgChars: options.userMessage.length,
|
|
515
|
-
assistantMsgChars: options.aiResponse.length,
|
|
516
|
-
});
|
|
517
|
-
if (blobSizeBytes > 512 * 1024) {
|
|
518
|
-
logger.warn("[ConversationMemory] Large session blob", {
|
|
519
|
-
requestId: options.requestId,
|
|
520
|
-
sessionId: options.sessionId,
|
|
521
|
-
blobSizeBytes,
|
|
522
|
-
messageCount: conversation.messages.length,
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
if (this.redisConfig.ttl > 0) {
|
|
526
|
-
await this.redisClient.expire(redisKey, this.redisConfig.ttl);
|
|
527
|
-
}
|
|
528
|
-
if (options.userId) {
|
|
529
|
-
await this.addUserSession(options.userId, options.sessionId);
|
|
530
|
-
}
|
|
531
|
-
span.setAttribute("message.count", conversation.messages.length);
|
|
532
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
533
|
-
logger.debug("[RedisConversationMemoryManager] Successfully stored conversation turn", {
|
|
534
|
-
sessionId: options.sessionId,
|
|
535
|
-
totalMessages: conversation.messages.length,
|
|
536
|
-
title: conversation.title,
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
catch (error) {
|
|
540
|
-
span.setStatus({
|
|
541
|
-
code: SpanStatusCode.ERROR,
|
|
542
|
-
message: error instanceof Error ? error.message : String(error),
|
|
543
|
-
});
|
|
544
|
-
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
545
|
-
throw new ConversationMemoryError(`Failed to store conversation turn in Redis for session ${options.sessionId}`, "STORAGE_ERROR", {
|
|
546
|
-
sessionId: options.sessionId,
|
|
547
|
-
error: error instanceof Error ? error.message : String(error),
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
finally {
|
|
551
|
-
span.end();
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Check if summarization is needed based on token count
|
|
557
|
-
*/
|
|
558
|
-
async checkAndSummarize(conversation, threshold, sessionId, userId, requestId) {
|
|
559
|
-
const normalizedUserId = userId || "randomUser";
|
|
560
|
-
const summarizationKey = `${sessionId}:${normalizedUserId}`;
|
|
561
|
-
if (this.summarizationInProgress.has(summarizationKey)) {
|
|
562
|
-
logger.debug("[RedisConversationMemoryManager] Summarization already in progress, skipping", {
|
|
563
|
-
sessionId,
|
|
564
|
-
userId: normalizedUserId,
|
|
565
|
-
});
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
this.summarizationInProgress.add(summarizationKey);
|
|
569
|
-
try {
|
|
570
|
-
const session = {
|
|
571
|
-
sessionId: conversation.sessionId,
|
|
572
|
-
userId: conversation.userId,
|
|
573
|
-
messages: conversation.messages,
|
|
574
|
-
summarizedUpToMessageId: conversation.summarizedUpToMessageId,
|
|
575
|
-
summarizedMessage: conversation.summarizedMessage,
|
|
576
|
-
tokenThreshold: conversation.tokenThreshold,
|
|
577
|
-
lastTokenCount: conversation.lastTokenCount,
|
|
578
|
-
lastCountedAt: conversation.lastCountedAt,
|
|
579
|
-
createdAt: new Date(conversation.createdAt).getTime(),
|
|
580
|
-
lastActivity: new Date(conversation.updatedAt).getTime(),
|
|
581
|
-
};
|
|
582
|
-
const summarized = await this.summarizationEngine.checkAndSummarize(session, threshold, this.config, "[RedisConversationMemoryManager]", requestId);
|
|
583
|
-
conversation.lastTokenCount = session.lastTokenCount;
|
|
584
|
-
conversation.lastCountedAt = session.lastCountedAt;
|
|
585
|
-
if (summarized) {
|
|
586
|
-
conversation.summarizedUpToMessageId = session.summarizedUpToMessageId;
|
|
587
|
-
conversation.summarizedMessage = session.summarizedMessage;
|
|
588
|
-
if (this.redisClient) {
|
|
589
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
590
|
-
// Re-read current state to avoid clobbering messages added during summarization
|
|
591
|
-
const latestData = await this.redisClient.get(redisKey);
|
|
592
|
-
if (latestData) {
|
|
593
|
-
const latestConversation = deserializeConversation(latestData);
|
|
594
|
-
if (latestConversation) {
|
|
595
|
-
// Apply only summarization metadata onto the fresh state
|
|
596
|
-
latestConversation.summarizedUpToMessageId =
|
|
597
|
-
conversation.summarizedUpToMessageId;
|
|
598
|
-
latestConversation.summarizedMessage =
|
|
599
|
-
conversation.summarizedMessage;
|
|
600
|
-
latestConversation.lastTokenCount = conversation.lastTokenCount;
|
|
601
|
-
latestConversation.lastCountedAt = conversation.lastCountedAt;
|
|
602
|
-
const freshSerialized = serializeConversation(latestConversation);
|
|
603
|
-
await this.redisClient.set(redisKey, freshSerialized);
|
|
604
|
-
if (this.redisConfig.ttl > 0) {
|
|
605
|
-
await this.redisClient.expire(redisKey, this.redisConfig.ttl);
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
catch (error) {
|
|
613
|
-
logger.error("Token counting or summarization failed", {
|
|
614
|
-
sessionId: conversation.sessionId,
|
|
615
|
-
error: error instanceof Error ? error.message : String(error),
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
finally {
|
|
619
|
-
this.summarizationInProgress.delete(summarizationKey);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Build context messages for AI prompt injection (TOKEN-BASED)
|
|
624
|
-
* Returns messages from pointer onwards (or all if no pointer)
|
|
625
|
-
* Applies sendToolPreview toggle and hydrates result.result for backward compat
|
|
626
|
-
*/
|
|
627
|
-
async buildContextMessages(sessionId, userId, enableSummarization, requestId) {
|
|
628
|
-
await this.ensureInitialized();
|
|
629
|
-
if (!this.redisClient) {
|
|
630
|
-
logger.warn("[RedisConversationMemoryManager] Redis client not available in buildContextMessages");
|
|
631
|
-
return [];
|
|
632
|
-
}
|
|
633
|
-
// NLK-GAP-012: Add span for buildContext CRUD operation
|
|
634
|
-
return redisTracer.startActiveSpan("neurolink.memory.buildContext", {
|
|
635
|
-
kind: SpanKind.CLIENT,
|
|
636
|
-
attributes: {
|
|
637
|
-
"session.id": sessionId,
|
|
638
|
-
...(userId && { "user.id": userId }),
|
|
639
|
-
},
|
|
640
|
-
}, async (span) => {
|
|
641
|
-
try {
|
|
642
|
-
logger.info("[RedisConversationMemoryManager] Building context messages", {
|
|
643
|
-
sessionId,
|
|
644
|
-
userId,
|
|
645
|
-
method: "buildContextMessages",
|
|
646
|
-
});
|
|
647
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
648
|
-
const conversationData = await this.redisClient.get(redisKey);
|
|
649
|
-
const conversation = deserializeConversation(conversationData || null);
|
|
650
|
-
if (!conversation) {
|
|
651
|
-
span.setAttribute("session.found", false);
|
|
652
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
653
|
-
return [];
|
|
654
|
-
}
|
|
655
|
-
const session = {
|
|
656
|
-
sessionId: conversation.sessionId,
|
|
657
|
-
userId: conversation.userId,
|
|
658
|
-
messages: conversation.messages,
|
|
659
|
-
summarizedUpToMessageId: conversation.summarizedUpToMessageId,
|
|
660
|
-
summarizedMessage: conversation.summarizedMessage,
|
|
661
|
-
tokenThreshold: conversation.tokenThreshold,
|
|
662
|
-
lastTokenCount: conversation.lastTokenCount,
|
|
663
|
-
lastCountedAt: conversation.lastCountedAt,
|
|
664
|
-
createdAt: new Date(conversation.createdAt).getTime(),
|
|
665
|
-
lastActivity: new Date(conversation.updatedAt).getTime(),
|
|
666
|
-
};
|
|
667
|
-
const contextMessages = buildContextFromPointer(session, requestId);
|
|
668
|
-
const sendToolPreview = this.config?.contextCompaction?.sendToolPreview === true;
|
|
669
|
-
// Map tool_result messages: apply preview toggle + hydrate result.result
|
|
670
|
-
const finalMessages = contextMessages.map((msg) => {
|
|
671
|
-
if (msg.role !== "tool_result") {
|
|
672
|
-
return msg;
|
|
673
|
-
}
|
|
674
|
-
// Toggle: swap content to preview if enabled AND a preview exists
|
|
675
|
-
const content = sendToolPreview && msg.metadata?.toolOutputPreview
|
|
676
|
-
? msg.metadata.toolOutputPreview
|
|
677
|
-
: msg.content;
|
|
678
|
-
// Hydrate result.result from content for backward compatibility
|
|
679
|
-
// (result.result is no longer stored — inferred from content at read time)
|
|
680
|
-
let hydratedResult = msg.result;
|
|
681
|
-
if (msg.result && msg.result.result === undefined) {
|
|
682
|
-
let parsedResult = content;
|
|
683
|
-
try {
|
|
684
|
-
parsedResult = JSON.parse(content);
|
|
685
|
-
}
|
|
686
|
-
catch {
|
|
687
|
-
/* plain text — use as-is */
|
|
688
|
-
}
|
|
689
|
-
hydratedResult = { ...msg.result, result: parsedResult };
|
|
690
|
-
}
|
|
691
|
-
return { ...msg, content, result: hydratedResult };
|
|
692
|
-
});
|
|
693
|
-
// Tool messages now have real content and participate in context properly.
|
|
694
|
-
// The tool output pruner (Stage 1) handles bounding old tool outputs.
|
|
695
|
-
span.setAttribute("context.message_count", finalMessages.length);
|
|
696
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
697
|
-
logger.info("[RedisConversationMemoryManager] Retrieved context messages", {
|
|
698
|
-
sessionId,
|
|
699
|
-
userId,
|
|
700
|
-
});
|
|
701
|
-
return finalMessages;
|
|
702
|
-
}
|
|
703
|
-
catch (error) {
|
|
704
|
-
span.setStatus({
|
|
705
|
-
code: SpanStatusCode.ERROR,
|
|
706
|
-
message: error instanceof Error ? error.message : String(error),
|
|
707
|
-
});
|
|
708
|
-
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
709
|
-
throw error;
|
|
710
|
-
}
|
|
711
|
-
finally {
|
|
712
|
-
span.end();
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* Get session metadata for a specific user session (optimized for listing)
|
|
718
|
-
* Fetches only essential metadata without heavy message arrays
|
|
719
|
-
*
|
|
720
|
-
* @param userId The user identifier
|
|
721
|
-
* @param sessionId The session identifier
|
|
722
|
-
* @returns Session metadata or null if session doesn't exist
|
|
723
|
-
*/
|
|
724
|
-
async getUserSessionMetadata(userId, sessionId) {
|
|
725
|
-
logger.debug("[RedisConversationMemoryManager] Getting user session metadata", {
|
|
726
|
-
userId,
|
|
727
|
-
sessionId,
|
|
728
|
-
});
|
|
729
|
-
await this.ensureInitialized();
|
|
730
|
-
if (!this.redisClient) {
|
|
731
|
-
logger.warn("[RedisConversationMemoryManager] Redis client not available", { userId, sessionId });
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
try {
|
|
735
|
-
const sessionKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
736
|
-
const conversationData = await this.redisClient.get(sessionKey);
|
|
737
|
-
if (!conversationData) {
|
|
738
|
-
logger.debug("[RedisConversationMemoryManager] No session data found", {
|
|
739
|
-
userId,
|
|
740
|
-
sessionId,
|
|
741
|
-
sessionKey,
|
|
742
|
-
});
|
|
743
|
-
return null;
|
|
744
|
-
}
|
|
745
|
-
// Deserialize conversation object but extract only metadata
|
|
746
|
-
const conversation = deserializeConversation(conversationData);
|
|
747
|
-
if (conversation) {
|
|
748
|
-
return {
|
|
749
|
-
id: conversation.sessionId,
|
|
750
|
-
title: conversation.title,
|
|
751
|
-
createdAt: conversation.createdAt,
|
|
752
|
-
updatedAt: conversation.updatedAt,
|
|
753
|
-
metadata: conversation.additionalMetadata?.agenticLoopReports
|
|
754
|
-
? {
|
|
755
|
-
agenticLoopReports: conversation.additionalMetadata.agenticLoopReports,
|
|
756
|
-
}
|
|
757
|
-
: undefined,
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
logger.debug("[RedisConversationMemoryManager] No valid conversation data found", {
|
|
761
|
-
userId,
|
|
762
|
-
sessionId,
|
|
763
|
-
sessionKey,
|
|
764
|
-
});
|
|
765
|
-
return null;
|
|
766
|
-
}
|
|
767
|
-
catch (error) {
|
|
768
|
-
logger.error("[RedisConversationMemoryManager] Failed to get user session metadata", {
|
|
769
|
-
userId,
|
|
770
|
-
sessionId,
|
|
771
|
-
error: error instanceof Error ? error.message : String(error),
|
|
772
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
773
|
-
});
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Get conversation history for a specific user session
|
|
779
|
-
*
|
|
780
|
-
* @param userId The user identifier
|
|
781
|
-
* @param sessionId The session identifier
|
|
782
|
-
* @returns Array of chat messages or null if session doesn't exist
|
|
783
|
-
*/
|
|
784
|
-
async getUserSessionHistory(userId, sessionId) {
|
|
785
|
-
logger.debug("[RedisConversationMemoryManager] Getting user session history via getUserSessionObject", {
|
|
786
|
-
userId,
|
|
787
|
-
sessionId,
|
|
788
|
-
});
|
|
789
|
-
try {
|
|
790
|
-
const sessionObject = await this.getUserSessionObject(userId, sessionId);
|
|
791
|
-
if (!sessionObject) {
|
|
792
|
-
logger.debug("[RedisConversationMemoryManager] No session object found, returning null", {
|
|
793
|
-
userId,
|
|
794
|
-
sessionId,
|
|
795
|
-
});
|
|
796
|
-
return null;
|
|
797
|
-
}
|
|
798
|
-
return sessionObject.messages;
|
|
799
|
-
}
|
|
800
|
-
catch (error) {
|
|
801
|
-
logger.error("[RedisConversationMemoryManager] Failed to get user session history via getUserSessionObject", {
|
|
802
|
-
userId,
|
|
803
|
-
sessionId,
|
|
804
|
-
error: error instanceof Error ? error.message : String(error),
|
|
805
|
-
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
806
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
807
|
-
});
|
|
808
|
-
return null;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Get the complete conversation object for a specific user session
|
|
813
|
-
*
|
|
814
|
-
* This method returns the full conversation object including title, metadata,
|
|
815
|
-
* timestamps, and all chat messages. Unlike getUserSessionHistory() which returns
|
|
816
|
-
* only the messages array, this method provides the complete conversation context.
|
|
817
|
-
*
|
|
818
|
-
* @param userId The user identifier who owns the session
|
|
819
|
-
* @param sessionId The unique session identifier
|
|
820
|
-
* @returns Complete conversation object with all data, or null if session doesn't exist
|
|
821
|
-
*/
|
|
822
|
-
async getUserSessionObject(userId, sessionId) {
|
|
823
|
-
logger.debug("[RedisConversationMemoryManager] Getting complete user session object", {
|
|
824
|
-
userId,
|
|
825
|
-
sessionId,
|
|
826
|
-
method: "getUserSessionObject",
|
|
827
|
-
});
|
|
828
|
-
// Validate input parameters
|
|
829
|
-
if (!userId || typeof userId !== "string") {
|
|
830
|
-
logger.warn("[RedisConversationMemoryManager] Invalid userId provided", {
|
|
831
|
-
userId,
|
|
832
|
-
sessionId,
|
|
833
|
-
});
|
|
834
|
-
return null;
|
|
835
|
-
}
|
|
836
|
-
if (!sessionId || typeof sessionId !== "string") {
|
|
837
|
-
logger.warn("[RedisConversationMemoryManager] Invalid sessionId provided", { userId, sessionId });
|
|
838
|
-
return null;
|
|
839
|
-
}
|
|
840
|
-
await this.ensureInitialized();
|
|
841
|
-
if (!this.redisClient) {
|
|
842
|
-
logger.warn("[RedisConversationMemoryManager] Redis client not available for getUserSessionObject", {
|
|
843
|
-
userId,
|
|
844
|
-
sessionId,
|
|
845
|
-
});
|
|
846
|
-
return null;
|
|
847
|
-
}
|
|
848
|
-
try {
|
|
849
|
-
const sessionKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
850
|
-
const conversationData = await this.redisClient.get(sessionKey);
|
|
851
|
-
if (!conversationData) {
|
|
852
|
-
logger.debug("[RedisConversationMemoryManager] No conversation data found in Redis", {
|
|
853
|
-
userId,
|
|
854
|
-
sessionId,
|
|
855
|
-
sessionKey,
|
|
856
|
-
});
|
|
857
|
-
return null;
|
|
858
|
-
}
|
|
859
|
-
// Deserialize the complete conversation object
|
|
860
|
-
const conversation = deserializeConversation(conversationData);
|
|
861
|
-
if (!conversation) {
|
|
862
|
-
logger.debug("[RedisConversationMemoryManager] Failed to deserialize conversation data", {
|
|
863
|
-
userId,
|
|
864
|
-
sessionId,
|
|
865
|
-
sessionKey,
|
|
866
|
-
dataLength: conversationData.length,
|
|
867
|
-
});
|
|
868
|
-
return null;
|
|
869
|
-
}
|
|
870
|
-
// Validate conversation object structure
|
|
871
|
-
if (!conversation.messages || !Array.isArray(conversation.messages)) {
|
|
872
|
-
logger.warn("[RedisConversationMemoryManager] Invalid conversation structure - missing messages array", {
|
|
873
|
-
userId,
|
|
874
|
-
sessionId,
|
|
875
|
-
hasMessages: !!conversation.messages,
|
|
876
|
-
messagesType: typeof conversation.messages,
|
|
877
|
-
});
|
|
878
|
-
return null;
|
|
879
|
-
}
|
|
880
|
-
return conversation;
|
|
881
|
-
}
|
|
882
|
-
catch (error) {
|
|
883
|
-
logger.error("[RedisConversationMemoryManager] Failed to get complete user session object", {
|
|
884
|
-
userId,
|
|
885
|
-
sessionId,
|
|
886
|
-
error: error instanceof Error ? error.message : String(error),
|
|
887
|
-
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
888
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
889
|
-
});
|
|
890
|
-
return null;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* Generate a conversation title from the first user message
|
|
895
|
-
* Uses AI to create a concise, descriptive title (5-8 words)
|
|
896
|
-
*/
|
|
897
|
-
async generateConversationTitle(userMessage) {
|
|
898
|
-
logger.info("[RedisConversationMemoryManager] Generating conversation title", {
|
|
899
|
-
userMessageLength: userMessage.length,
|
|
900
|
-
userMessagePreview: userMessage.substring(0, 100),
|
|
901
|
-
});
|
|
902
|
-
try {
|
|
903
|
-
// Create a NeuroLink instance for title generation
|
|
904
|
-
const titleGenerator = new NeuroLink({
|
|
905
|
-
conversationMemory: { enabled: false },
|
|
906
|
-
});
|
|
907
|
-
const titlePrompt = `Generate a clear, concise, and descriptive title (5–8 words maximum) for a conversation based on the following user message.
|
|
908
|
-
The title must meaningfully reflect the topic or intent of the message.
|
|
909
|
-
Do not output anything unrelated, vague, or generic.
|
|
910
|
-
Do not say you cannot create a title. Always return a valid title.
|
|
911
|
-
|
|
912
|
-
User message: "${userMessage}"`;
|
|
913
|
-
const result = await titleGenerator.generate({
|
|
914
|
-
input: { text: titlePrompt },
|
|
915
|
-
provider: this.config.summarizationProvider || "vertex",
|
|
916
|
-
model: this.config.summarizationModel || "gemini-2.5-flash",
|
|
917
|
-
disableTools: true, // Title generation doesn't need tools — saves ~600 tokens of tool descriptions
|
|
918
|
-
});
|
|
919
|
-
// Clean up the generated title
|
|
920
|
-
let title = result.content?.trim() || "New Conversation";
|
|
921
|
-
// Remove common prefixes/suffixes that might be added by the AI
|
|
922
|
-
title = title.replace(/^(Title:|Here's a title:|The title is:)\s*/i, "");
|
|
923
|
-
title = title.replace(/['"]/g, ""); // Remove quotes
|
|
924
|
-
title = title.replace(/\.$/, ""); // Remove trailing period
|
|
925
|
-
if (title.length > 60) {
|
|
926
|
-
title = title.substring(0, 57) + "...";
|
|
927
|
-
}
|
|
928
|
-
if (title.length < 3) {
|
|
929
|
-
title = "New Conversation";
|
|
930
|
-
}
|
|
931
|
-
logger.info("[RedisConversationMemoryManager] Generated conversation title", {
|
|
932
|
-
originalLength: result.content?.length || 0,
|
|
933
|
-
cleanedTitle: title,
|
|
934
|
-
titleLength: title.length,
|
|
935
|
-
});
|
|
936
|
-
return title;
|
|
937
|
-
}
|
|
938
|
-
catch (error) {
|
|
939
|
-
logger.error("[RedisConversationMemoryManager] Failed to generate conversation title", {
|
|
940
|
-
error: error instanceof Error ? error.message : String(error),
|
|
941
|
-
userMessagePreview: userMessage.substring(0, 100),
|
|
942
|
-
});
|
|
943
|
-
// Fallback to a simple title based on the user message
|
|
944
|
-
const fallbackTitle = userMessage.length > 30
|
|
945
|
-
? userMessage.substring(0, 30) + "..."
|
|
946
|
-
: userMessage || "New Conversation";
|
|
947
|
-
return fallbackTitle;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Create summary system message
|
|
952
|
-
*/
|
|
953
|
-
createSummarySystemMessage(content, summarizesFrom, summarizesTo) {
|
|
954
|
-
return {
|
|
955
|
-
id: `summary-${randomUUID()}`,
|
|
956
|
-
role: "system",
|
|
957
|
-
content: `Summary of previous conversation turns:\n\n${content}`,
|
|
958
|
-
timestamp: new Date().toISOString(),
|
|
959
|
-
metadata: {
|
|
960
|
-
isSummary: true,
|
|
961
|
-
summarizesFrom,
|
|
962
|
-
summarizesTo,
|
|
963
|
-
},
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
/**
|
|
967
|
-
* Get the raw messages array for a session.
|
|
968
|
-
* Returns the full messages list without context filtering or summarization.
|
|
969
|
-
*/
|
|
970
|
-
async getSessionMessages(sessionId, userId) {
|
|
971
|
-
await this.ensureInitialized();
|
|
972
|
-
if (!this.redisClient) {
|
|
973
|
-
return [];
|
|
974
|
-
}
|
|
975
|
-
try {
|
|
976
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
977
|
-
const conversationData = await this.redisClient.get(redisKey);
|
|
978
|
-
const conversation = deserializeConversation(conversationData || null);
|
|
979
|
-
return conversation?.messages ?? [];
|
|
980
|
-
}
|
|
981
|
-
catch (error) {
|
|
982
|
-
logger.error("[RedisConversationMemoryManager] Failed to get session messages", {
|
|
983
|
-
sessionId,
|
|
984
|
-
userId,
|
|
985
|
-
error: error instanceof Error ? error.message : String(error),
|
|
986
|
-
});
|
|
987
|
-
return [];
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
/**
|
|
991
|
-
* Replace the entire messages array for a session.
|
|
992
|
-
* The session must already exist in Redis.
|
|
993
|
-
*/
|
|
994
|
-
async setSessionMessages(sessionId, messages, userId) {
|
|
995
|
-
await this.ensureInitialized();
|
|
996
|
-
if (!this.redisClient) {
|
|
997
|
-
throw new ConversationMemoryError("Redis client not initialized", "STORAGE_ERROR", { sessionId });
|
|
998
|
-
}
|
|
999
|
-
try {
|
|
1000
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
1001
|
-
const conversationData = await this.redisClient.get(redisKey);
|
|
1002
|
-
const conversation = deserializeConversation(conversationData || null);
|
|
1003
|
-
if (!conversation) {
|
|
1004
|
-
throw new ConversationMemoryError(`Session ${sessionId} not found`, "STORAGE_ERROR", { sessionId });
|
|
1005
|
-
}
|
|
1006
|
-
conversation.messages = messages;
|
|
1007
|
-
conversation.updatedAt = new Date().toISOString();
|
|
1008
|
-
// Reset summarization pointers — the old summary no longer applies
|
|
1009
|
-
// to the replaced messages array
|
|
1010
|
-
conversation.summarizedUpToMessageId = undefined;
|
|
1011
|
-
conversation.summarizedMessage = undefined;
|
|
1012
|
-
conversation.lastTokenCount = undefined;
|
|
1013
|
-
conversation.lastCountedAt = undefined;
|
|
1014
|
-
const serializedData = serializeConversation(conversation);
|
|
1015
|
-
await this.redisClient.set(redisKey, serializedData);
|
|
1016
|
-
if (this.redisConfig.ttl > 0) {
|
|
1017
|
-
await this.redisClient.expire(redisKey, this.redisConfig.ttl);
|
|
1018
|
-
}
|
|
1019
|
-
logger.debug("[RedisConversationMemoryManager] Session messages replaced", {
|
|
1020
|
-
sessionId,
|
|
1021
|
-
userId,
|
|
1022
|
-
messageCount: messages.length,
|
|
1023
|
-
});
|
|
1024
|
-
}
|
|
1025
|
-
catch (error) {
|
|
1026
|
-
if (error instanceof ConversationMemoryError) {
|
|
1027
|
-
throw error;
|
|
1028
|
-
}
|
|
1029
|
-
throw new ConversationMemoryError(`Failed to set session messages for session ${sessionId}`, "STORAGE_ERROR", {
|
|
1030
|
-
sessionId,
|
|
1031
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
/**
|
|
1036
|
-
* Close Redis connection
|
|
1037
|
-
*/
|
|
1038
|
-
async close() {
|
|
1039
|
-
if (this.redisClient) {
|
|
1040
|
-
await releasePooledRedisClient(this.redisConfig);
|
|
1041
|
-
this.redisClient = null;
|
|
1042
|
-
this.isInitialized = false;
|
|
1043
|
-
logger.info("Redis connection closed");
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
/**
|
|
1047
|
-
* Get statistics about conversation storage
|
|
1048
|
-
*/
|
|
1049
|
-
async getStats() {
|
|
1050
|
-
await this.ensureInitialized();
|
|
1051
|
-
if (!this.redisClient) {
|
|
1052
|
-
return { totalSessions: 0, totalTurns: 0 };
|
|
1053
|
-
}
|
|
1054
|
-
// Get all session keys using SCAN instead of KEYS to avoid blocking
|
|
1055
|
-
const pattern = `${this.redisConfig.keyPrefix}*`;
|
|
1056
|
-
const keys = await scanKeys(this.redisClient, pattern);
|
|
1057
|
-
logger.debug("[RedisConversationMemoryManager] Got session keys with SCAN", {
|
|
1058
|
-
pattern,
|
|
1059
|
-
keyCount: keys.length,
|
|
1060
|
-
});
|
|
1061
|
-
// Count messages in each session
|
|
1062
|
-
let totalTurns = 0;
|
|
1063
|
-
for (const key of keys) {
|
|
1064
|
-
const conversationData = await this.redisClient.get(key);
|
|
1065
|
-
const conversation = deserializeConversation(conversationData);
|
|
1066
|
-
if (conversation?.messages) {
|
|
1067
|
-
totalTurns += conversation.messages.length / MESSAGES_PER_TURN;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
return {
|
|
1071
|
-
totalSessions: keys.length,
|
|
1072
|
-
totalTurns,
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Clear a specific session
|
|
1077
|
-
*/
|
|
1078
|
-
async clearSession(sessionId, userId) {
|
|
1079
|
-
await this.ensureInitialized();
|
|
1080
|
-
if (!this.redisClient) {
|
|
1081
|
-
return false;
|
|
1082
|
-
}
|
|
1083
|
-
// NLK-GAP-012: Add span for clearSession CRUD operation
|
|
1084
|
-
return redisTracer.startActiveSpan("neurolink.memory.clear", {
|
|
1085
|
-
kind: SpanKind.CLIENT,
|
|
1086
|
-
attributes: {
|
|
1087
|
-
"session.id": sessionId,
|
|
1088
|
-
...(userId && { "user.id": userId }),
|
|
1089
|
-
},
|
|
1090
|
-
}, async (span) => {
|
|
1091
|
-
try {
|
|
1092
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId);
|
|
1093
|
-
const result = await this.redisClient.del(redisKey);
|
|
1094
|
-
if (Number(result) > 0) {
|
|
1095
|
-
// Remove session from user's session set
|
|
1096
|
-
if (userId) {
|
|
1097
|
-
await this.removeUserSession(userId, sessionId);
|
|
1098
|
-
}
|
|
1099
|
-
span.setAttribute("session.deleted", true);
|
|
1100
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
1101
|
-
logger.info("Redis session cleared", { sessionId });
|
|
1102
|
-
return true;
|
|
1103
|
-
}
|
|
1104
|
-
span.setAttribute("session.deleted", false);
|
|
1105
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
1106
|
-
return false;
|
|
1107
|
-
}
|
|
1108
|
-
catch (error) {
|
|
1109
|
-
span.setStatus({
|
|
1110
|
-
code: SpanStatusCode.ERROR,
|
|
1111
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1112
|
-
});
|
|
1113
|
-
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
1114
|
-
throw error;
|
|
1115
|
-
}
|
|
1116
|
-
finally {
|
|
1117
|
-
span.end();
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* Clear all sessions
|
|
1123
|
-
*/
|
|
1124
|
-
async clearAllSessions() {
|
|
1125
|
-
await this.ensureInitialized();
|
|
1126
|
-
if (!this.redisClient) {
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
const conversationPattern = `${this.redisConfig.keyPrefix}*`;
|
|
1130
|
-
const userSessionsPattern = `${this.redisConfig.userSessionsKeyPrefix}*`;
|
|
1131
|
-
// Use SCAN instead of KEYS to avoid blocking the server
|
|
1132
|
-
const conversationKeys = await scanKeys(this.redisClient, conversationPattern);
|
|
1133
|
-
const userSessionsKeys = await scanKeys(this.redisClient, userSessionsPattern);
|
|
1134
|
-
const allKeys = [...conversationKeys, ...userSessionsKeys];
|
|
1135
|
-
logger.debug("[RedisConversationMemoryManager] Got all keys with SCAN for clearing", {
|
|
1136
|
-
conversationPattern,
|
|
1137
|
-
userSessionsPattern,
|
|
1138
|
-
conversationKeyCount: conversationKeys.length,
|
|
1139
|
-
userSessionsKeyCount: userSessionsKeys.length,
|
|
1140
|
-
totalKeyCount: allKeys.length,
|
|
1141
|
-
});
|
|
1142
|
-
if (allKeys.length > 0) {
|
|
1143
|
-
// Process keys in batches to avoid blocking Redis for too long
|
|
1144
|
-
const batchSize = 100;
|
|
1145
|
-
for (let i = 0; i < allKeys.length; i += batchSize) {
|
|
1146
|
-
const batch = allKeys.slice(i, i + batchSize);
|
|
1147
|
-
await this.redisClient.del(batch);
|
|
1148
|
-
logger.debug("[RedisConversationMemoryManager] Cleared batch of sessions and user mappings", {
|
|
1149
|
-
batchIndex: Math.floor(i / batchSize) + 1,
|
|
1150
|
-
batchSize: batch.length,
|
|
1151
|
-
totalProcessed: i + batch.length,
|
|
1152
|
-
totalKeys: allKeys.length,
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
logger.info("All Redis sessions and user session mappings cleared", {
|
|
1156
|
-
clearedCount: allKeys.length,
|
|
1157
|
-
conversationSessions: conversationKeys.length,
|
|
1158
|
-
userSessionMappings: userSessionsKeys.length,
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Ensure Redis client is initialized
|
|
1164
|
-
*/
|
|
1165
|
-
async ensureInitialized() {
|
|
1166
|
-
logger.debug("[RedisConversationMemoryManager] Ensuring initialization");
|
|
1167
|
-
if (!this.isInitialized) {
|
|
1168
|
-
logger.debug("[RedisConversationMemoryManager] Not initialized, initializing now");
|
|
1169
|
-
await this.initialize();
|
|
1170
|
-
}
|
|
1171
|
-
else {
|
|
1172
|
-
logger.debug("[RedisConversationMemoryManager] Already initialized");
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
/**
|
|
1176
|
-
* Get session metadata for all sessions of a user (optimized for listing)
|
|
1177
|
-
* Returns only essential metadata without heavy message arrays
|
|
1178
|
-
*
|
|
1179
|
-
* @param userId The user identifier
|
|
1180
|
-
* @returns Array of session metadata objects
|
|
1181
|
-
*/
|
|
1182
|
-
async getUserAllSessionsHistory(userId) {
|
|
1183
|
-
await this.ensureInitialized();
|
|
1184
|
-
if (!this.redisClient) {
|
|
1185
|
-
logger.warn("[RedisConversationMemoryManager] Redis client not available", { userId });
|
|
1186
|
-
return [];
|
|
1187
|
-
}
|
|
1188
|
-
const results = [];
|
|
1189
|
-
try {
|
|
1190
|
-
// Get all session IDs for the user using existing method
|
|
1191
|
-
const sessionIds = await this.getUserSessions(userId);
|
|
1192
|
-
if (sessionIds.length === 0) {
|
|
1193
|
-
return results;
|
|
1194
|
-
}
|
|
1195
|
-
// Fetch metadata for each session using our optimized helper method
|
|
1196
|
-
for (const sessionId of sessionIds) {
|
|
1197
|
-
try {
|
|
1198
|
-
const metadata = await this.getUserSessionMetadata(userId, sessionId);
|
|
1199
|
-
if (metadata) {
|
|
1200
|
-
results.push(metadata);
|
|
1201
|
-
}
|
|
1202
|
-
else {
|
|
1203
|
-
logger.debug("[RedisConversationMemoryManager] Empty or missing session metadata - removing from user history", {
|
|
1204
|
-
userId,
|
|
1205
|
-
sessionId,
|
|
1206
|
-
});
|
|
1207
|
-
await this.removeUserSession(userId, sessionId);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
catch (sessionError) {
|
|
1211
|
-
logger.error("[RedisConversationMemoryManager] Failed to get session metadata", {
|
|
1212
|
-
userId,
|
|
1213
|
-
sessionId,
|
|
1214
|
-
error: sessionError instanceof Error
|
|
1215
|
-
? sessionError.message
|
|
1216
|
-
: String(sessionError),
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
return results;
|
|
1221
|
-
}
|
|
1222
|
-
catch (error) {
|
|
1223
|
-
logger.error("[RedisConversationMemoryManager] Failed to get user all sessions metadata", {
|
|
1224
|
-
userId,
|
|
1225
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1226
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
1227
|
-
});
|
|
1228
|
-
return results;
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
/**
|
|
1232
|
-
* Clean up stale pending tool execution data
|
|
1233
|
-
* Removes data older than 5 minutes to prevent memory leaks
|
|
1234
|
-
*/
|
|
1235
|
-
cleanupStalePendingData() {
|
|
1236
|
-
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
|
|
1237
|
-
const keysToDelete = [];
|
|
1238
|
-
for (const [key, data] of this.pendingToolExecutions) {
|
|
1239
|
-
if (data.timestamp < fiveMinutesAgo) {
|
|
1240
|
-
keysToDelete.push(key);
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
if (keysToDelete.length > 0) {
|
|
1244
|
-
logger.debug("[RedisConversationMemoryManager] Cleaning up stale pending tool data", {
|
|
1245
|
-
stalePendingKeys: keysToDelete.length,
|
|
1246
|
-
totalPendingKeys: this.pendingToolExecutions.size,
|
|
1247
|
-
});
|
|
1248
|
-
keysToDelete.forEach((key) => this.pendingToolExecutions.delete(key));
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
/**
|
|
1252
|
-
* Flush pending tool execution data for a session and merge into conversation
|
|
1253
|
-
*/
|
|
1254
|
-
async flushPendingToolData(conversation, sessionId, userId) {
|
|
1255
|
-
const pendingKey = `${sessionId}:${userId}`;
|
|
1256
|
-
const pendingData = this.pendingToolExecutions.get(pendingKey);
|
|
1257
|
-
if (!pendingData) {
|
|
1258
|
-
logger.debug("[RedisConversationMemoryManager] No pending tool data to flush", {
|
|
1259
|
-
sessionId,
|
|
1260
|
-
userId,
|
|
1261
|
-
pendingKey,
|
|
1262
|
-
});
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
logger.debug("[RedisConversationMemoryManager] Flushing pending tool data", {
|
|
1266
|
-
sessionId,
|
|
1267
|
-
userId,
|
|
1268
|
-
toolCallsCount: pendingData.toolCalls.length,
|
|
1269
|
-
toolResultsCount: pendingData.toolResults.length,
|
|
1270
|
-
});
|
|
1271
|
-
try {
|
|
1272
|
-
// Create a mapping from toolCallId to toolName for matching tool results
|
|
1273
|
-
const toolCallMap = new Map();
|
|
1274
|
-
// Create separate messages for tool calls and build the mapping
|
|
1275
|
-
for (const toolCall of pendingData.toolCalls) {
|
|
1276
|
-
const toolCallId = toolCall.toolCallId ?? "";
|
|
1277
|
-
const toolName = toolCall.toolName ?? "";
|
|
1278
|
-
// Store in mapping for tool results
|
|
1279
|
-
toolCallMap.set(toolCallId, toolName);
|
|
1280
|
-
const toolCallMessage = {
|
|
1281
|
-
id: randomUUID(),
|
|
1282
|
-
timestamp: toolCall.timestamp?.toISOString() || this.generateTimestamp(),
|
|
1283
|
-
role: "tool_call",
|
|
1284
|
-
content: "", // Can be empty for tool calls
|
|
1285
|
-
tool: toolName,
|
|
1286
|
-
args: (toolCall.args ||
|
|
1287
|
-
toolCall.arguments ||
|
|
1288
|
-
toolCall.parameters ||
|
|
1289
|
-
{}),
|
|
1290
|
-
};
|
|
1291
|
-
conversation.messages.push(toolCallMessage);
|
|
1292
|
-
}
|
|
1293
|
-
// Create separate messages for tool results using the mapping
|
|
1294
|
-
for (const toolResult of pendingData.toolResults) {
|
|
1295
|
-
const toolCallId = String(toolResult.toolCallId || toolResult.id || "unknown");
|
|
1296
|
-
const toolName = toolCallMap.get(toolCallId) || "unknown";
|
|
1297
|
-
// Serialize the tool result to string for content field
|
|
1298
|
-
let serializedResult;
|
|
1299
|
-
if (typeof toolResult.result === "string") {
|
|
1300
|
-
serializedResult = toolResult.result;
|
|
1301
|
-
}
|
|
1302
|
-
else if (toolResult.result === undefined ||
|
|
1303
|
-
toolResult.result === null) {
|
|
1304
|
-
serializedResult = String(toolResult.result ?? "null");
|
|
1305
|
-
}
|
|
1306
|
-
else {
|
|
1307
|
-
try {
|
|
1308
|
-
serializedResult = JSON.stringify(toolResult.result, null, 2);
|
|
1309
|
-
}
|
|
1310
|
-
catch (serializeError) {
|
|
1311
|
-
serializedResult = `[Serialization failed: ${serializeError instanceof Error ? serializeError.message : String(serializeError)}]`;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
// Generate preview (uses existing config fields that were previously unused)
|
|
1315
|
-
const { preview, truncated, originalSize } = generateToolOutputPreview(serializedResult, {
|
|
1316
|
-
maxBytes: this.config?.contextCompaction?.maxToolOutputBytes,
|
|
1317
|
-
maxLines: this.config?.contextCompaction?.maxToolOutputLines,
|
|
1318
|
-
});
|
|
1319
|
-
// Build metadata — only store preview when truncation occurred (no duplication)
|
|
1320
|
-
const metadata = {
|
|
1321
|
-
truncated,
|
|
1322
|
-
...(truncated && { toolOutputPreview: preview }),
|
|
1323
|
-
...(truncated && { originalSize }),
|
|
1324
|
-
};
|
|
1325
|
-
// Build result — success/error metadata only, NOT the output data
|
|
1326
|
-
const result = {
|
|
1327
|
-
success: !toolResult.error,
|
|
1328
|
-
// result.result intentionally NOT stored — inferred from content at read time
|
|
1329
|
-
error: toolResult.error ? String(toolResult.error) : undefined,
|
|
1330
|
-
};
|
|
1331
|
-
const toolResultMessage = {
|
|
1332
|
-
id: randomUUID(),
|
|
1333
|
-
timestamp: toolResult.timestamp?.toISOString() || this.generateTimestamp(),
|
|
1334
|
-
role: "tool_result",
|
|
1335
|
-
content: serializedResult, // Full output (was "")
|
|
1336
|
-
tool: toolName,
|
|
1337
|
-
result,
|
|
1338
|
-
metadata,
|
|
1339
|
-
};
|
|
1340
|
-
conversation.messages.push(toolResultMessage);
|
|
1341
|
-
}
|
|
1342
|
-
logger.debug("[RedisConversationMemoryManager] Successfully flushed pending tool data", {
|
|
1343
|
-
sessionId,
|
|
1344
|
-
userId,
|
|
1345
|
-
toolMessagesAdded: pendingData.toolCalls.length + pendingData.toolResults.length,
|
|
1346
|
-
totalMessages: conversation.messages.length,
|
|
1347
|
-
});
|
|
1348
|
-
}
|
|
1349
|
-
finally {
|
|
1350
|
-
// Always clean up pending data, even on failure, to prevent infinite retry loops
|
|
1351
|
-
this.pendingToolExecutions.delete(pendingKey);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
/**
|
|
1355
|
-
* Update agentic loop report metadata for a conversation session.
|
|
1356
|
-
* Upserts a report entry by reportId — updates existing or adds new.
|
|
1357
|
-
* Follows the read → patch → write pattern (same as title generation).
|
|
1358
|
-
*
|
|
1359
|
-
* @param sessionId The session identifier
|
|
1360
|
-
* @param userId The user identifier (optional)
|
|
1361
|
-
* @param report The report metadata to upsert
|
|
1362
|
-
*/
|
|
1363
|
-
async updateAgenticLoopReport(sessionId, userId, report) {
|
|
1364
|
-
logger.debug("[RedisConversationMemoryManager] Updating agentic loop report", {
|
|
1365
|
-
sessionId,
|
|
1366
|
-
userId,
|
|
1367
|
-
reportId: report.reportId,
|
|
1368
|
-
reportType: report.reportType,
|
|
1369
|
-
reportStatus: report.reportStatus,
|
|
1370
|
-
});
|
|
1371
|
-
await this.ensureInitialized();
|
|
1372
|
-
if (!this.redisClient) {
|
|
1373
|
-
logger.warn("[RedisConversationMemoryManager] Redis client not available for report update", { sessionId, userId });
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
try {
|
|
1377
|
-
const redisKey = getSessionKey(this.redisConfig, sessionId, userId || undefined);
|
|
1378
|
-
const conversationData = await withTimeout(this.redisClient.get(redisKey), 5000);
|
|
1379
|
-
if (!conversationData) {
|
|
1380
|
-
logger.warn("[RedisConversationMemoryManager] No conversation found for report update", { sessionId, userId });
|
|
1381
|
-
return;
|
|
1382
|
-
}
|
|
1383
|
-
const conversation = deserializeConversation(conversationData);
|
|
1384
|
-
if (!conversation) {
|
|
1385
|
-
logger.warn("[RedisConversationMemoryManager] Failed to deserialize conversation for report update", { sessionId, userId });
|
|
1386
|
-
return;
|
|
1387
|
-
}
|
|
1388
|
-
// Initialize additionalMetadata and agenticLoopReports if needed
|
|
1389
|
-
if (!conversation.additionalMetadata) {
|
|
1390
|
-
conversation.additionalMetadata = {};
|
|
1391
|
-
}
|
|
1392
|
-
if (!conversation.additionalMetadata.agenticLoopReports) {
|
|
1393
|
-
conversation.additionalMetadata.agenticLoopReports = [];
|
|
1394
|
-
}
|
|
1395
|
-
// Upsert: find existing report by reportId and update, or push new entry
|
|
1396
|
-
const existingIndex = conversation.additionalMetadata.agenticLoopReports.findIndex((r) => r.reportId === report.reportId);
|
|
1397
|
-
if (existingIndex >= 0) {
|
|
1398
|
-
conversation.additionalMetadata.agenticLoopReports[existingIndex] =
|
|
1399
|
-
report;
|
|
1400
|
-
logger.debug("[RedisConversationMemoryManager] Updated existing agentic loop report", { sessionId, reportId: report.reportId });
|
|
1401
|
-
}
|
|
1402
|
-
else {
|
|
1403
|
-
conversation.additionalMetadata.agenticLoopReports.push(report);
|
|
1404
|
-
logger.debug("[RedisConversationMemoryManager] Added new agentic loop report", { sessionId, reportId: report.reportId });
|
|
1405
|
-
}
|
|
1406
|
-
conversation.updatedAt = new Date().toISOString();
|
|
1407
|
-
// Write back to Redis
|
|
1408
|
-
const serializedData = serializeConversation(conversation);
|
|
1409
|
-
await withTimeout(this.redisClient.set(redisKey, serializedData), 5000);
|
|
1410
|
-
if (this.redisConfig.ttl > 0) {
|
|
1411
|
-
await withTimeout(this.redisClient.expire(redisKey, this.redisConfig.ttl), 5000);
|
|
1412
|
-
}
|
|
1413
|
-
logger.info("[RedisConversationMemoryManager] Successfully updated agentic loop report", {
|
|
1414
|
-
sessionId,
|
|
1415
|
-
userId,
|
|
1416
|
-
reportId: report.reportId,
|
|
1417
|
-
reportStatus: report.reportStatus,
|
|
1418
|
-
});
|
|
1419
|
-
}
|
|
1420
|
-
catch (error) {
|
|
1421
|
-
logger.error("[RedisConversationMemoryManager] Failed to update agentic loop report", {
|
|
1422
|
-
sessionId,
|
|
1423
|
-
userId,
|
|
1424
|
-
reportId: report.reportId,
|
|
1425
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1426
|
-
});
|
|
1427
|
-
throw new ConversationMemoryError("Failed to update agentic loop report", "STORAGE_ERROR", {
|
|
1428
|
-
sessionId,
|
|
1429
|
-
userId,
|
|
1430
|
-
reportId: report.reportId,
|
|
1431
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
}
|