@juspay/neurolink 7.53.5 → 8.0.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.
@@ -44,7 +44,7 @@ import { directToolsServer } from "./mcp/servers/agent/directToolsServer.js";
44
44
  // Import orchestration components
45
45
  import { ModelRouter } from "./utils/modelRouter.js";
46
46
  import { BinaryTaskClassifier } from "./utils/taskClassifier.js";
47
- import { initializeOpenTelemetry, shutdownOpenTelemetry, flushOpenTelemetry, getLangfuseHealthStatus, } from "./services/server/ai/observability/instrumentation.js";
47
+ import { initializeOpenTelemetry, shutdownOpenTelemetry, flushOpenTelemetry, getLangfuseHealthStatus, setLangfuseContext, } from "./services/server/ai/observability/instrumentation.js";
48
48
  export class NeuroLink {
49
49
  mcpInitialized = false;
50
50
  emitter = new EventEmitter();
@@ -94,6 +94,40 @@ export class NeuroLink {
94
94
  // Mem0 memory instance and config for conversation context
95
95
  mem0Instance;
96
96
  mem0Config;
97
+ /**
98
+ * Extract and set Langfuse context from options with proper async scoping
99
+ */
100
+ async setLangfuseContextFromOptions(options, callback) {
101
+ if (options.context &&
102
+ typeof options.context === "object" &&
103
+ options.context !== null) {
104
+ try {
105
+ const ctx = options.context;
106
+ if (ctx.userId || ctx.sessionId) {
107
+ return await new Promise((resolve, reject) => {
108
+ setLangfuseContext({
109
+ userId: typeof ctx.userId === "string" ? ctx.userId : null,
110
+ sessionId: typeof ctx.sessionId === "string" ? ctx.sessionId : null,
111
+ }, async () => {
112
+ try {
113
+ const result = await callback();
114
+ resolve(result);
115
+ }
116
+ catch (error) {
117
+ reject(error);
118
+ }
119
+ });
120
+ });
121
+ }
122
+ }
123
+ catch (error) {
124
+ logger.warn("Failed to set Langfuse context from options", {
125
+ error: error instanceof Error ? error.message : String(error),
126
+ });
127
+ }
128
+ }
129
+ return await callback();
130
+ }
97
131
  /**
98
132
  * Simple sync config setup for mem0
99
133
  */
@@ -187,6 +221,7 @@ export class NeuroLink {
187
221
  this.observabilityConfig = config?.observability;
188
222
  // Initialize orchestration setting
189
223
  this.enableOrchestration = config?.enableOrchestration ?? false;
224
+ logger.setEventEmitter(this.emitter);
190
225
  // Read tool cache duration from environment variables, with a default
191
226
  const cacheDurationEnv = process.env.NEUROLINK_TOOL_CACHE_DURATION;
192
227
  this.toolCacheDuration = cacheDurationEnv
@@ -517,7 +552,7 @@ export class NeuroLink {
517
552
  langfuseInitStartTimeNs: langfuseInitStartTime.toString(),
518
553
  message: "Starting Langfuse observability initialization",
519
554
  });
520
- // Initialize OpenTelemetry FIRST (required for Langfuse v4)
555
+ // Initialize OpenTelemetry (sets defaults from config)
521
556
  initializeOpenTelemetry(langfuseConfig);
522
557
  const healthStatus = getLangfuseHealthStatus();
523
558
  const langfuseInitDurationNs = process.hrtime.bigint() - langfuseInitStartTime;
@@ -1145,198 +1180,201 @@ export class NeuroLink {
1145
1180
  if (!options.input?.text || typeof options.input.text !== "string") {
1146
1181
  throw new Error("Input text is required and must be a non-empty string");
1147
1182
  }
1148
- if (this.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
1149
- options.context?.userId) {
1150
- try {
1151
- const mem0 = await this.ensureMem0Ready();
1152
- if (!mem0) {
1153
- logger.debug("Mem0 not available, continuing without memory retrieval");
1183
+ // Set session and user IDs from context for Langfuse spans and execute with proper async scoping
1184
+ return await this.setLangfuseContextFromOptions(options, async () => {
1185
+ if (this.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
1186
+ options.context?.userId) {
1187
+ try {
1188
+ const mem0 = await this.ensureMem0Ready();
1189
+ if (!mem0) {
1190
+ logger.debug("Mem0 not available, continuing without memory retrieval");
1191
+ }
1192
+ else {
1193
+ const memories = await mem0.search(options.input.text, {
1194
+ userId: options.context.userId,
1195
+ limit: 5,
1196
+ });
1197
+ if (memories?.results?.length > 0) {
1198
+ // Enhance the input with memory context
1199
+ const memoryContext = memories.results
1200
+ .map((m) => m.memory)
1201
+ .join("\n");
1202
+ options.input.text = this.formatMemoryContext(memoryContext, options.input.text);
1203
+ }
1204
+ }
1154
1205
  }
1155
- else {
1156
- const memories = await mem0.search(options.input.text, {
1157
- userId: options.context.userId,
1158
- limit: 5,
1206
+ catch (error) {
1207
+ logger.warn("Mem0 memory retrieval failed:", error);
1208
+ }
1209
+ }
1210
+ const startTime = Date.now();
1211
+ // Apply orchestration if enabled and no specific provider/model requested
1212
+ if (this.enableOrchestration && !options.provider && !options.model) {
1213
+ try {
1214
+ const orchestratedOptions = await this.applyOrchestration(options);
1215
+ logger.debug("Orchestration applied", {
1216
+ originalProvider: options.provider || "auto",
1217
+ orchestratedProvider: orchestratedOptions.provider,
1218
+ orchestratedModel: orchestratedOptions.model,
1219
+ prompt: options.input.text.substring(0, 100),
1159
1220
  });
1160
- if (memories?.results?.length > 0) {
1161
- // Enhance the input with memory context
1162
- const memoryContext = memories.results
1163
- .map((m) => m.memory)
1164
- .join("\n");
1165
- options.input.text = this.formatMemoryContext(memoryContext, options.input.text);
1166
- }
1221
+ // Use orchestrated options
1222
+ Object.assign(options, orchestratedOptions);
1223
+ }
1224
+ catch (error) {
1225
+ logger.warn("Orchestration failed, continuing with original options", {
1226
+ error: error instanceof Error ? error.message : String(error),
1227
+ originalProvider: options.provider || "auto",
1228
+ });
1229
+ // Continue with original options if orchestration fails
1167
1230
  }
1168
1231
  }
1169
- catch (error) {
1170
- logger.warn("Mem0 memory retrieval failed:", error);
1232
+ // Emit generation start event (NeuroLink format - keep existing)
1233
+ this.emitter.emit("generation:start", {
1234
+ provider: options.provider || "auto",
1235
+ timestamp: startTime,
1236
+ });
1237
+ // ADD: Bedrock-compatible response:start event
1238
+ this.emitter.emit("response:start");
1239
+ // ADD: Bedrock-compatible message event
1240
+ this.emitter.emit("message", `Starting ${options.provider || "auto"} text generation...`);
1241
+ // Process factory configuration
1242
+ const factoryResult = processFactoryOptions(options);
1243
+ // Validate factory configuration if present
1244
+ if (factoryResult.hasFactoryConfig && options.factoryConfig) {
1245
+ const validation = validateFactoryConfig(options.factoryConfig);
1246
+ if (!validation.isValid) {
1247
+ logger.warn("Invalid factory configuration detected", {
1248
+ errors: validation.errors,
1249
+ });
1250
+ // Continue with warning rather than throwing - graceful degradation
1251
+ }
1171
1252
  }
1172
- }
1173
- const startTime = Date.now();
1174
- // Apply orchestration if enabled and no specific provider/model requested
1175
- if (this.enableOrchestration && !options.provider && !options.model) {
1176
- try {
1177
- const orchestratedOptions = await this.applyOrchestration(options);
1178
- logger.debug("Orchestration applied", {
1179
- originalProvider: options.provider || "auto",
1180
- orchestratedProvider: orchestratedOptions.provider,
1181
- orchestratedModel: orchestratedOptions.model,
1182
- prompt: options.input.text.substring(0, 100),
1183
- });
1184
- // Use orchestrated options
1185
- Object.assign(options, orchestratedOptions);
1253
+ // 🔧 CRITICAL FIX: Convert to TextGenerationOptions while preserving the input object for multimodal support
1254
+ const baseOptions = {
1255
+ prompt: options.input.text,
1256
+ provider: options.provider,
1257
+ model: options.model,
1258
+ temperature: options.temperature,
1259
+ maxTokens: options.maxTokens,
1260
+ systemPrompt: options.systemPrompt,
1261
+ schema: options.schema,
1262
+ output: options.output,
1263
+ disableTools: options.disableTools,
1264
+ enableAnalytics: options.enableAnalytics,
1265
+ enableEvaluation: options.enableEvaluation,
1266
+ context: options.context,
1267
+ evaluationDomain: options.evaluationDomain,
1268
+ toolUsageContext: options.toolUsageContext,
1269
+ input: options.input, // This includes text, images, and content arrays
1270
+ region: options.region,
1271
+ };
1272
+ // Apply factory enhancement using centralized utilities
1273
+ const textOptions = enhanceTextGenerationOptions(baseOptions, factoryResult);
1274
+ // Pass conversation memory config if available
1275
+ if (this.conversationMemory) {
1276
+ textOptions.conversationMemoryConfig = this.conversationMemory.config;
1277
+ // Include original prompt for context summarization
1278
+ textOptions.originalPrompt = originalPrompt;
1186
1279
  }
1187
- catch (error) {
1188
- logger.warn("Orchestration failed, continuing with original options", {
1189
- error: error instanceof Error ? error.message : String(error),
1190
- originalProvider: options.provider || "auto",
1280
+ // Detect and execute domain-specific tools
1281
+ const { toolResults, enhancedPrompt } = await this.detectAndExecuteTools(textOptions.prompt || options.input.text, factoryResult.domainType);
1282
+ // Update prompt with tool results if available
1283
+ if (enhancedPrompt !== textOptions.prompt) {
1284
+ textOptions.prompt = enhancedPrompt;
1285
+ logger.debug("Enhanced prompt with tool results", {
1286
+ originalLength: options.input.text.length,
1287
+ enhancedLength: enhancedPrompt.length,
1288
+ toolResults: toolResults.length,
1191
1289
  });
1192
- // Continue with original options if orchestration fails
1193
1290
  }
1194
- }
1195
- // Emit generation start event (NeuroLink format - keep existing)
1196
- this.emitter.emit("generation:start", {
1197
- provider: options.provider || "auto",
1198
- timestamp: startTime,
1199
- });
1200
- // ADD: Bedrock-compatible response:start event
1201
- this.emitter.emit("response:start");
1202
- // ADD: Bedrock-compatible message event
1203
- this.emitter.emit("message", `Starting ${options.provider || "auto"} text generation...`);
1204
- // Process factory configuration
1205
- const factoryResult = processFactoryOptions(options);
1206
- // Validate factory configuration if present
1207
- if (factoryResult.hasFactoryConfig && options.factoryConfig) {
1208
- const validation = validateFactoryConfig(options.factoryConfig);
1209
- if (!validation.isValid) {
1210
- logger.warn("Invalid factory configuration detected", {
1211
- errors: validation.errors,
1291
+ // Use redesigned generation logic
1292
+ const textResult = await this.generateTextInternal(textOptions);
1293
+ // Emit generation completion event (NeuroLink format - enhanced with content)
1294
+ this.emitter.emit("generation:end", {
1295
+ provider: textResult.provider,
1296
+ responseTime: Date.now() - startTime,
1297
+ toolsUsed: textResult.toolsUsed,
1298
+ timestamp: Date.now(),
1299
+ result: textResult, // Enhanced: include full result
1300
+ });
1301
+ // ADD: Bedrock-compatible response:end event with content
1302
+ this.emitter.emit("response:end", textResult.content || "");
1303
+ // ADD: Bedrock-compatible message event
1304
+ this.emitter.emit("message", `Generation completed in ${Date.now() - startTime}ms`);
1305
+ // Convert back to GenerateResult
1306
+ const generateResult = {
1307
+ content: textResult.content,
1308
+ provider: textResult.provider,
1309
+ model: textResult.model,
1310
+ usage: textResult.usage
1311
+ ? {
1312
+ input: textResult.usage.input || 0,
1313
+ output: textResult.usage.output || 0,
1314
+ total: textResult.usage.total || 0,
1315
+ }
1316
+ : undefined,
1317
+ responseTime: textResult.responseTime,
1318
+ toolsUsed: textResult.toolsUsed,
1319
+ toolExecutions: transformToolExecutions(textResult.toolExecutions),
1320
+ enhancedWithTools: textResult.enhancedWithTools,
1321
+ availableTools: transformAvailableTools(textResult.availableTools),
1322
+ analytics: textResult.analytics,
1323
+ evaluation: textResult.evaluation
1324
+ ? {
1325
+ ...textResult.evaluation,
1326
+ isOffTopic: textResult.evaluation
1327
+ .isOffTopic ?? false,
1328
+ alertSeverity: textResult.evaluation
1329
+ .alertSeverity ??
1330
+ "none",
1331
+ reasoning: textResult.evaluation
1332
+ .reasoning ?? "No evaluation provided",
1333
+ evaluationModel: textResult.evaluation
1334
+ .evaluationModel ?? "unknown",
1335
+ evaluationTime: textResult.evaluation
1336
+ .evaluationTime ?? Date.now(),
1337
+ // Include evaluationDomain from original options
1338
+ evaluationDomain: textResult.evaluation
1339
+ .evaluationDomain ??
1340
+ textOptions.evaluationDomain ??
1341
+ factoryResult.domainType,
1342
+ }
1343
+ : undefined,
1344
+ };
1345
+ if (this.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
1346
+ options.context?.userId &&
1347
+ generateResult.content) {
1348
+ // Non-blocking memory storage - run in background
1349
+ setImmediate(async () => {
1350
+ try {
1351
+ const mem0 = await this.ensureMem0Ready();
1352
+ if (mem0) {
1353
+ // Store complete conversation turn (user + AI messages)
1354
+ const conversationTurn = [
1355
+ { role: "user", content: options.input.text },
1356
+ { role: "system", content: generateResult.content },
1357
+ ];
1358
+ await mem0.add(JSON.stringify(conversationTurn), {
1359
+ userId: options.context?.userId,
1360
+ metadata: {
1361
+ timestamp: new Date().toISOString(),
1362
+ provider: generateResult.provider,
1363
+ model: generateResult.model,
1364
+ type: "conversation_turn",
1365
+ async_mode: true,
1366
+ },
1367
+ });
1368
+ }
1369
+ }
1370
+ catch (error) {
1371
+ // Non-blocking: Log error but don't fail the generation
1372
+ logger.warn("Mem0 memory storage failed:", error);
1373
+ }
1212
1374
  });
1213
- // Continue with warning rather than throwing - graceful degradation
1214
1375
  }
1215
- }
1216
- // 🔧 CRITICAL FIX: Convert to TextGenerationOptions while preserving the input object for multimodal support
1217
- const baseOptions = {
1218
- prompt: options.input.text,
1219
- provider: options.provider,
1220
- model: options.model,
1221
- temperature: options.temperature,
1222
- maxTokens: options.maxTokens,
1223
- systemPrompt: options.systemPrompt,
1224
- schema: options.schema,
1225
- output: options.output,
1226
- disableTools: options.disableTools,
1227
- enableAnalytics: options.enableAnalytics,
1228
- enableEvaluation: options.enableEvaluation,
1229
- context: options.context,
1230
- evaluationDomain: options.evaluationDomain,
1231
- toolUsageContext: options.toolUsageContext,
1232
- input: options.input, // This includes text, images, and content arrays
1233
- region: options.region,
1234
- };
1235
- // Apply factory enhancement using centralized utilities
1236
- const textOptions = enhanceTextGenerationOptions(baseOptions, factoryResult);
1237
- // Pass conversation memory config if available
1238
- if (this.conversationMemory) {
1239
- textOptions.conversationMemoryConfig = this.conversationMemory.config;
1240
- // Include original prompt for context summarization
1241
- textOptions.originalPrompt = originalPrompt;
1242
- }
1243
- // Detect and execute domain-specific tools
1244
- const { toolResults, enhancedPrompt } = await this.detectAndExecuteTools(textOptions.prompt || options.input.text, factoryResult.domainType);
1245
- // Update prompt with tool results if available
1246
- if (enhancedPrompt !== textOptions.prompt) {
1247
- textOptions.prompt = enhancedPrompt;
1248
- logger.debug("Enhanced prompt with tool results", {
1249
- originalLength: options.input.text.length,
1250
- enhancedLength: enhancedPrompt.length,
1251
- toolResults: toolResults.length,
1252
- });
1253
- }
1254
- // Use redesigned generation logic
1255
- const textResult = await this.generateTextInternal(textOptions);
1256
- // Emit generation completion event (NeuroLink format - enhanced with content)
1257
- this.emitter.emit("generation:end", {
1258
- provider: textResult.provider,
1259
- responseTime: Date.now() - startTime,
1260
- toolsUsed: textResult.toolsUsed,
1261
- timestamp: Date.now(),
1262
- result: textResult, // Enhanced: include full result
1376
+ return generateResult;
1263
1377
  });
1264
- // ADD: Bedrock-compatible response:end event with content
1265
- this.emitter.emit("response:end", textResult.content || "");
1266
- // ADD: Bedrock-compatible message event
1267
- this.emitter.emit("message", `Generation completed in ${Date.now() - startTime}ms`);
1268
- // Convert back to GenerateResult
1269
- const generateResult = {
1270
- content: textResult.content,
1271
- provider: textResult.provider,
1272
- model: textResult.model,
1273
- usage: textResult.usage
1274
- ? {
1275
- input: textResult.usage.input || 0,
1276
- output: textResult.usage.output || 0,
1277
- total: textResult.usage.total || 0,
1278
- }
1279
- : undefined,
1280
- responseTime: textResult.responseTime,
1281
- toolsUsed: textResult.toolsUsed,
1282
- toolExecutions: transformToolExecutions(textResult.toolExecutions),
1283
- enhancedWithTools: textResult.enhancedWithTools,
1284
- availableTools: transformAvailableTools(textResult.availableTools),
1285
- analytics: textResult.analytics,
1286
- evaluation: textResult.evaluation
1287
- ? {
1288
- ...textResult.evaluation,
1289
- isOffTopic: textResult.evaluation
1290
- .isOffTopic ?? false,
1291
- alertSeverity: textResult.evaluation
1292
- .alertSeverity ??
1293
- "none",
1294
- reasoning: textResult.evaluation
1295
- .reasoning ?? "No evaluation provided",
1296
- evaluationModel: textResult.evaluation
1297
- .evaluationModel ?? "unknown",
1298
- evaluationTime: textResult.evaluation
1299
- .evaluationTime ?? Date.now(),
1300
- // Include evaluationDomain from original options
1301
- evaluationDomain: textResult.evaluation
1302
- .evaluationDomain ??
1303
- textOptions.evaluationDomain ??
1304
- factoryResult.domainType,
1305
- }
1306
- : undefined,
1307
- };
1308
- if (this.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
1309
- options.context?.userId &&
1310
- generateResult.content) {
1311
- // Non-blocking memory storage - run in background
1312
- setImmediate(async () => {
1313
- try {
1314
- const mem0 = await this.ensureMem0Ready();
1315
- if (mem0) {
1316
- // Store complete conversation turn (user + AI messages)
1317
- const conversationTurn = [
1318
- { role: "user", content: options.input.text },
1319
- { role: "system", content: generateResult.content },
1320
- ];
1321
- await mem0.add(JSON.stringify(conversationTurn), {
1322
- userId: options.context?.userId,
1323
- metadata: {
1324
- timestamp: new Date().toISOString(),
1325
- provider: generateResult.provider,
1326
- model: generateResult.model,
1327
- type: "conversation_turn",
1328
- async_mode: true,
1329
- },
1330
- });
1331
- }
1332
- }
1333
- catch (error) {
1334
- // Non-blocking: Log error but don't fail the generation
1335
- logger.warn("Mem0 memory storage failed:", error);
1336
- }
1337
- });
1338
- }
1339
- return generateResult;
1340
1378
  }
1341
1379
  /**
1342
1380
  * BACKWARD COMPATIBILITY: Legacy generateText method
@@ -1862,190 +1900,155 @@ export class NeuroLink {
1862
1900
  const originalPrompt = options.input.text; // Store the original prompt for memory storage
1863
1901
  await this.validateStreamInput(options);
1864
1902
  this.emitStreamStartEvents(options, startTime);
1865
- let enhancedOptions;
1866
- let factoryResult;
1867
- try {
1868
- // Initialize conversation memory if needed (for lazy loading)
1869
- await this.initializeConversationMemoryForGeneration(streamId, startTime, hrTimeStart);
1870
- // Initialize MCP
1871
- await this.initializeMCP();
1872
- const _originalPrompt = options.input.text;
1873
- if (this.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
1874
- options.context?.userId) {
1875
- try {
1876
- const mem0 = await this.ensureMem0Ready();
1877
- if (!mem0) {
1878
- // Continue without memories if mem0 is not available
1879
- logger.debug("Mem0 not available, continuing without memory retrieval");
1880
- }
1881
- else {
1882
- const memories = await mem0.search(options.input.text, {
1883
- userId: options.context.userId,
1884
- limit: 5,
1885
- });
1886
- if (memories?.results?.length > 0) {
1887
- // Enhance the input with memory context
1888
- const memoryContext = memories.results
1889
- .map((m) => m.memory)
1890
- .join("\n");
1891
- options.input.text = this.formatMemoryContext(memoryContext, options.input.text);
1903
+ // Set session and user IDs from context for Langfuse spans and execute with proper async scoping
1904
+ return await this.setLangfuseContextFromOptions(options, async () => {
1905
+ let enhancedOptions;
1906
+ let factoryResult;
1907
+ try {
1908
+ // Initialize conversation memory if needed (for lazy loading)
1909
+ await this.initializeConversationMemoryForGeneration(streamId, startTime, hrTimeStart);
1910
+ // Initialize MCP
1911
+ await this.initializeMCP();
1912
+ const _originalPrompt = options.input.text;
1913
+ if (this.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
1914
+ options.context?.userId) {
1915
+ try {
1916
+ const mem0 = await this.ensureMem0Ready();
1917
+ if (!mem0) {
1918
+ // Continue without memories if mem0 is not available
1919
+ logger.debug("Mem0 not available, continuing without memory retrieval");
1920
+ }
1921
+ else {
1922
+ const memories = await mem0.search(options.input.text, {
1923
+ userId: options.context.userId,
1924
+ limit: 5,
1925
+ });
1926
+ if (memories?.results?.length > 0) {
1927
+ // Enhance the input with memory context
1928
+ const memoryContext = memories.results
1929
+ .map((m) => m.memory)
1930
+ .join("\n");
1931
+ options.input.text = this.formatMemoryContext(memoryContext, options.input.text);
1932
+ }
1892
1933
  }
1893
1934
  }
1935
+ catch (error) {
1936
+ // Non-blocking: Log error but continue with streaming
1937
+ logger.warn("Mem0 memory retrieval failed:", error);
1938
+ }
1894
1939
  }
1895
- catch (error) {
1896
- // Non-blocking: Log error but continue with streaming
1897
- logger.warn("Mem0 memory retrieval failed:", error);
1898
- }
1899
- }
1900
- // Apply orchestration if enabled and no specific provider/model requested
1901
- if (this.enableOrchestration && !options.provider && !options.model) {
1902
- try {
1903
- const orchestratedOptions = await this.applyStreamOrchestration(options);
1904
- logger.debug("Stream orchestration applied", {
1905
- originalProvider: options.provider || "auto",
1906
- orchestratedProvider: orchestratedOptions.provider,
1907
- orchestratedModel: orchestratedOptions.model,
1908
- prompt: options.input.text?.substring(0, 100),
1909
- });
1910
- // Use orchestrated options
1911
- Object.assign(options, orchestratedOptions);
1912
- }
1913
- catch (error) {
1914
- logger.warn("Stream orchestration failed, continuing with original options", {
1915
- error: error instanceof Error ? error.message : String(error),
1916
- originalProvider: options.provider || "auto",
1917
- });
1918
- // Continue with original options if orchestration fails
1940
+ // Apply orchestration if enabled and no specific provider/model requested
1941
+ if (this.enableOrchestration && !options.provider && !options.model) {
1942
+ try {
1943
+ const orchestratedOptions = await this.applyStreamOrchestration(options);
1944
+ logger.debug("Stream orchestration applied", {
1945
+ originalProvider: options.provider || "auto",
1946
+ orchestratedProvider: orchestratedOptions.provider,
1947
+ orchestratedModel: orchestratedOptions.model,
1948
+ prompt: options.input.text?.substring(0, 100),
1949
+ });
1950
+ // Use orchestrated options
1951
+ Object.assign(options, orchestratedOptions);
1952
+ }
1953
+ catch (error) {
1954
+ logger.warn("Stream orchestration failed, continuing with original options", {
1955
+ error: error instanceof Error ? error.message : String(error),
1956
+ originalProvider: options.provider || "auto",
1957
+ });
1958
+ // Continue with original options if orchestration fails
1959
+ }
1919
1960
  }
1920
- }
1921
- factoryResult = processStreamingFactoryOptions(options);
1922
- enhancedOptions = createCleanStreamOptions(options);
1923
- if (options.input?.text) {
1924
- const { toolResults: _toolResults, enhancedPrompt } = await this.detectAndExecuteTools(options.input.text, undefined);
1925
- if (enhancedPrompt !== options.input.text) {
1926
- enhancedOptions.input.text = enhancedPrompt;
1961
+ factoryResult = processStreamingFactoryOptions(options);
1962
+ enhancedOptions = createCleanStreamOptions(options);
1963
+ if (options.input?.text) {
1964
+ const { toolResults: _toolResults, enhancedPrompt } = await this.detectAndExecuteTools(options.input.text, undefined);
1965
+ if (enhancedPrompt !== options.input.text) {
1966
+ enhancedOptions.input.text = enhancedPrompt;
1967
+ }
1927
1968
  }
1928
- }
1929
- const { stream: mcpStream, provider: providerName } = await this.createMCPStream(enhancedOptions);
1930
- // Create a wrapper around the stream that accumulates content
1931
- let accumulatedContent = "";
1932
- const processedStream = (async function* (self) {
1933
- try {
1934
- for await (const chunk of mcpStream) {
1935
- if (chunk &&
1936
- "content" in chunk &&
1937
- typeof chunk.content === "string") {
1938
- accumulatedContent += chunk.content;
1939
- // Emit chunk event for compatibility
1940
- self.emitter.emit("response:chunk", chunk.content);
1969
+ const { stream: mcpStream, provider: providerName } = await this.createMCPStream(enhancedOptions);
1970
+ // Create a wrapper around the stream that accumulates content
1971
+ let accumulatedContent = "";
1972
+ const processedStream = (async function* (self) {
1973
+ try {
1974
+ for await (const chunk of mcpStream) {
1975
+ if (chunk &&
1976
+ "content" in chunk &&
1977
+ typeof chunk.content === "string") {
1978
+ accumulatedContent += chunk.content;
1979
+ // Emit chunk event for compatibility
1980
+ self.emitter.emit("response:chunk", chunk.content);
1981
+ }
1982
+ yield chunk; // Preserve original streaming behavior
1941
1983
  }
1942
- yield chunk; // Preserve original streaming behavior
1943
1984
  }
1944
- }
1945
- finally {
1946
- // Store memory after stream consumption is complete
1947
- if (self.conversationMemory && enhancedOptions.context?.sessionId) {
1948
- const storageStartTime = Date.now();
1949
- const sessionId = enhancedOptions.context?.sessionId;
1950
- const userId = enhancedOptions.context
1951
- ?.userId;
1952
- try {
1953
- self.emitter.emit("log-event", {
1954
- type: "log-event:storage:start",
1955
- data: {
1956
- operation: "storeConversationTurn",
1985
+ finally {
1986
+ // Store memory after stream consumption is complete
1987
+ if (self.conversationMemory && enhancedOptions.context?.sessionId) {
1988
+ const sessionId = enhancedOptions.context?.sessionId;
1989
+ const userId = enhancedOptions.context?.userId;
1990
+ try {
1991
+ await self.conversationMemory.storeConversationTurn(sessionId, userId, originalPrompt ?? "", accumulatedContent, new Date(startTime));
1992
+ logger.debug("Stream conversation turn stored", {
1957
1993
  sessionId,
1958
- userId,
1959
- timestamp: storageStartTime,
1960
- source: "stream-finally-block",
1961
1994
  userInputLength: originalPrompt?.length ?? 0,
1962
1995
  responseLength: accumulatedContent.length,
1963
- },
1964
- });
1965
- await self.conversationMemory.storeConversationTurn(sessionId, userId, originalPrompt ?? "", accumulatedContent, new Date(startTime));
1966
- self.emitter.emit("log-event", {
1967
- type: "log-event:storage:end",
1968
- data: {
1969
- operation: "storeConversationTurn",
1970
- sessionId,
1971
- userId,
1972
- timestamp: Date.now(),
1973
- duration: Date.now() - storageStartTime,
1974
- source: "stream-finally-block",
1975
- success: true,
1976
- },
1977
- });
1978
- logger.debug("Stream conversation turn stored", {
1979
- sessionId,
1980
- userInputLength: originalPrompt?.length ?? 0,
1981
- responseLength: accumulatedContent.length,
1982
- });
1983
- }
1984
- catch (error) {
1985
- self.emitter.emit("log-event", {
1986
- type: "log-event:storage:error",
1987
- data: {
1988
- operation: "storeConversationTurn",
1989
- sessionId,
1990
- userId,
1991
- timestamp: Date.now(),
1992
- duration: Date.now() - storageStartTime,
1993
- source: "stream-finally-block",
1994
- error: error instanceof Error ? error.message : String(error),
1995
- },
1996
- });
1997
- logger.warn("Failed to store stream conversation turn", {
1998
- error: error instanceof Error ? error.message : String(error),
1999
- });
2000
- }
2001
- }
2002
- if (self.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
2003
- enhancedOptions.context?.userId &&
2004
- accumulatedContent.trim()) {
2005
- // Non-blocking memory storage - run in background
2006
- setImmediate(async () => {
2007
- try {
2008
- const mem0 = await self.ensureMem0Ready();
2009
- if (mem0) {
2010
- // Store complete conversation turn (user + AI messages)
2011
- const conversationTurn = [
2012
- { role: "user", content: originalPrompt },
2013
- { role: "system", content: accumulatedContent.trim() },
2014
- ];
2015
- await mem0.add(JSON.stringify(conversationTurn), {
2016
- userId: enhancedOptions.context?.userId,
2017
- metadata: {
2018
- timestamp: new Date().toISOString(),
2019
- type: "conversation_turn_stream",
2020
- userMessage: originalPrompt,
2021
- async_mode: true,
2022
- aiResponse: accumulatedContent.trim(),
2023
- },
2024
- });
2025
- }
1996
+ });
2026
1997
  }
2027
1998
  catch (error) {
2028
- logger.warn("Mem0 memory storage failed:", error);
1999
+ logger.warn("Failed to store stream conversation turn", {
2000
+ error: error instanceof Error ? error.message : String(error),
2001
+ });
2029
2002
  }
2030
- });
2003
+ }
2004
+ if (self.conversationMemoryConfig?.conversationMemory?.mem0Enabled &&
2005
+ enhancedOptions.context?.userId &&
2006
+ accumulatedContent.trim()) {
2007
+ // Non-blocking memory storage - run in background
2008
+ setImmediate(async () => {
2009
+ try {
2010
+ const mem0 = await self.ensureMem0Ready();
2011
+ if (mem0) {
2012
+ // Store complete conversation turn (user + AI messages)
2013
+ const conversationTurn = [
2014
+ { role: "user", content: originalPrompt },
2015
+ { role: "system", content: accumulatedContent.trim() },
2016
+ ];
2017
+ await mem0.add(JSON.stringify(conversationTurn), {
2018
+ userId: enhancedOptions.context?.userId,
2019
+ metadata: {
2020
+ timestamp: new Date().toISOString(),
2021
+ type: "conversation_turn_stream",
2022
+ userMessage: originalPrompt,
2023
+ async_mode: true,
2024
+ aiResponse: accumulatedContent.trim(),
2025
+ },
2026
+ });
2027
+ }
2028
+ }
2029
+ catch (error) {
2030
+ logger.warn("Mem0 memory storage failed:", error);
2031
+ }
2032
+ });
2033
+ }
2031
2034
  }
2032
- }
2033
- })(this);
2034
- const streamResult = await this.processStreamResult(mcpStream, enhancedOptions, factoryResult);
2035
- const responseTime = Date.now() - startTime;
2036
- this.emitStreamEndEvents(streamResult);
2037
- return this.createStreamResponse(streamResult, processedStream, {
2038
- providerName,
2039
- options,
2040
- startTime,
2041
- responseTime,
2042
- streamId,
2043
- fallback: false,
2044
- });
2045
- }
2046
- catch (error) {
2047
- return this.handleStreamError(error, options, startTime, streamId, undefined, undefined);
2048
- }
2035
+ })(this);
2036
+ const streamResult = await this.processStreamResult(mcpStream, enhancedOptions, factoryResult);
2037
+ const responseTime = Date.now() - startTime;
2038
+ this.emitStreamEndEvents(streamResult);
2039
+ return this.createStreamResponse(streamResult, processedStream, {
2040
+ providerName,
2041
+ options,
2042
+ startTime,
2043
+ responseTime,
2044
+ streamId,
2045
+ fallback: false,
2046
+ });
2047
+ }
2048
+ catch (error) {
2049
+ return this.handleStreamError(error, options, startTime, streamId, undefined, undefined);
2050
+ }
2051
+ });
2049
2052
  }
2050
2053
  /**
2051
2054
  * Validate stream input with comprehensive error reporting
@@ -2187,36 +2190,11 @@ export class NeuroLink {
2187
2190
  finally {
2188
2191
  // Store memory after fallback stream consumption is complete
2189
2192
  if (self.conversationMemory && enhancedOptions?.context?.sessionId) {
2190
- const storageStartTime = Date.now();
2191
2193
  const sessionId = enhancedOptions?.context?.sessionId;
2192
2194
  const userId = enhancedOptions?.context
2193
2195
  ?.userId;
2194
2196
  try {
2195
- self.emitter.emit("log", {
2196
- type: "log-event:storage:start",
2197
- data: {
2198
- operation: "storeConversationTurn",
2199
- sessionId,
2200
- userId,
2201
- timestamp: storageStartTime,
2202
- source: "fallback-stream-finally-block",
2203
- userInputLength: originalPrompt?.length ?? 0,
2204
- responseLength: fallbackAccumulatedContent.length,
2205
- },
2206
- });
2207
2197
  await self.conversationMemory.storeConversationTurn(sessionId || options.context?.sessionId, userId || options.context?.userId, originalPrompt ?? "", fallbackAccumulatedContent, new Date(startTime));
2208
- self.emitter.emit("log", {
2209
- type: "log-event:storage:end",
2210
- data: {
2211
- operation: "storeConversationTurn",
2212
- sessionId,
2213
- userId,
2214
- timestamp: Date.now(),
2215
- duration: Date.now() - storageStartTime,
2216
- source: "fallback-stream-finally-block",
2217
- success: true,
2218
- },
2219
- });
2220
2198
  logger.debug("Fallback stream conversation turn stored", {
2221
2199
  sessionId: sessionId || options.context?.sessionId,
2222
2200
  userInputLength: originalPrompt?.length ?? 0,
@@ -2224,18 +2202,6 @@ export class NeuroLink {
2224
2202
  });
2225
2203
  }
2226
2204
  catch (error) {
2227
- self.emitter.emit("log-event", {
2228
- type: "log-event:storage:error",
2229
- data: {
2230
- operation: "storeConversationTurn",
2231
- sessionId,
2232
- userId,
2233
- timestamp: Date.now(),
2234
- duration: Date.now() - storageStartTime,
2235
- source: "fallback-stream-finally-block",
2236
- error: error instanceof Error ? error.message : String(error),
2237
- },
2238
- });
2239
2205
  logger.warn("Failed to store fallback stream conversation turn", {
2240
2206
  error: error instanceof Error ? error.message : String(error),
2241
2207
  });
@@ -4254,6 +4220,7 @@ export class NeuroLink {
4254
4220
  try {
4255
4221
  logger.debug("[NeuroLink] Removing all event listeners...");
4256
4222
  this.emitter.removeAllListeners();
4223
+ logger.clearEventEmitter();
4257
4224
  logger.debug("[NeuroLink] Event listeners removed successfully");
4258
4225
  }
4259
4226
  catch (error) {