@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.
- package/CHANGELOG.md +33 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +2 -2
- package/dist/lib/mcp/mcpClientFactory.js +0 -3
- package/dist/lib/neurolink.d.ts +4 -0
- package/dist/lib/neurolink.js +357 -390
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +17 -0
- package/dist/lib/services/server/ai/observability/instrumentation.js +54 -5
- package/dist/lib/types/common.d.ts +1 -0
- package/dist/lib/types/observability.d.ts +4 -0
- package/dist/lib/utils/fileDetector.js +3 -3
- package/dist/lib/utils/logger.d.ts +21 -0
- package/dist/lib/utils/logger.js +35 -0
- package/dist/lib/utils/messageBuilder.js +3 -3
- package/dist/mcp/mcpClientFactory.js +0 -3
- package/dist/neurolink.d.ts +4 -0
- package/dist/neurolink.js +357 -390
- package/dist/services/server/ai/observability/instrumentation.d.ts +17 -0
- package/dist/services/server/ai/observability/instrumentation.js +54 -5
- package/dist/types/common.d.ts +1 -0
- package/dist/types/observability.d.ts +4 -0
- package/dist/utils/fileDetector.js +3 -3
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.js +35 -0
- package/dist/utils/messageBuilder.js +3 -3
- package/package.json +5 -5
package/dist/neurolink.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
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
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
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
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
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
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
options.
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
.
|
|
1891
|
-
|
|
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
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
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
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
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
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
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
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
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("
|
|
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
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
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) {
|