@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
|
@@ -55,3 +55,20 @@ export declare function getLangfuseHealthStatus(): {
|
|
|
55
55
|
release: string;
|
|
56
56
|
} | undefined;
|
|
57
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Set user and session context for Langfuse spans in the current async context
|
|
60
|
+
*
|
|
61
|
+
* Merges the provided context with existing AsyncLocalStorage context. If a callback is provided,
|
|
62
|
+
* the context is scoped to that callback execution. Without a callback, the context applies to
|
|
63
|
+
* the current execution context and its children.
|
|
64
|
+
*
|
|
65
|
+
* Uses AsyncLocalStorage to properly scope context per request, avoiding race conditions
|
|
66
|
+
* in concurrent scenarios.
|
|
67
|
+
*
|
|
68
|
+
* @param context - Object containing optional userId and/or sessionId to merge with existing context
|
|
69
|
+
* @param callback - Optional callback to run within the context scope. If omitted, context applies to current execution
|
|
70
|
+
*/
|
|
71
|
+
export declare function setLangfuseContext(context: {
|
|
72
|
+
userId?: string | null;
|
|
73
|
+
sessionId?: string | null;
|
|
74
|
+
}, callback?: () => void | Promise<void>): Promise<void>;
|
|
@@ -10,13 +10,38 @@ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
|
10
10
|
import { LangfuseSpanProcessor } from "@langfuse/otel";
|
|
11
11
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
|
|
12
12
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
13
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
13
14
|
import { logger } from "../../../../utils/logger.js";
|
|
14
15
|
const LOG_PREFIX = "[OpenTelemetry]";
|
|
16
|
+
const contextStorage = new AsyncLocalStorage();
|
|
15
17
|
let tracerProvider = null;
|
|
16
18
|
let langfuseProcessor = null;
|
|
17
19
|
let isInitialized = false;
|
|
18
20
|
let isCredentialsValid = false;
|
|
19
21
|
let currentConfig = null;
|
|
22
|
+
/**
|
|
23
|
+
* Span processor that enriches spans with user and session context from AsyncLocalStorage
|
|
24
|
+
*/
|
|
25
|
+
class ContextEnricher {
|
|
26
|
+
onStart(span) {
|
|
27
|
+
const context = contextStorage.getStore();
|
|
28
|
+
const userId = context?.userId ?? currentConfig?.userId;
|
|
29
|
+
const sessionId = context?.sessionId ?? currentConfig?.sessionId;
|
|
30
|
+
if (userId) {
|
|
31
|
+
span.setAttribute("user.id", userId);
|
|
32
|
+
}
|
|
33
|
+
if (sessionId) {
|
|
34
|
+
span.setAttribute("session.id", sessionId);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
onEnd() { }
|
|
38
|
+
shutdown() {
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
}
|
|
41
|
+
forceFlush() {
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
20
45
|
/**
|
|
21
46
|
* Initialize OpenTelemetry with Langfuse span processor
|
|
22
47
|
*
|
|
@@ -46,7 +71,6 @@ export function initializeOpenTelemetry(config) {
|
|
|
46
71
|
try {
|
|
47
72
|
currentConfig = config;
|
|
48
73
|
isCredentialsValid = true;
|
|
49
|
-
// Create Langfuse span processor with configuration
|
|
50
74
|
langfuseProcessor = new LangfuseSpanProcessor({
|
|
51
75
|
publicKey: config.publicKey,
|
|
52
76
|
secretKey: config.secretKey,
|
|
@@ -54,18 +78,15 @@ export function initializeOpenTelemetry(config) {
|
|
|
54
78
|
environment: config.environment || "dev",
|
|
55
79
|
release: config.release || "v1.0.0",
|
|
56
80
|
});
|
|
57
|
-
// Create resource with service metadata (v2.x API)
|
|
58
81
|
const resource = resourceFromAttributes({
|
|
59
82
|
[ATTR_SERVICE_NAME]: "neurolink",
|
|
60
83
|
[ATTR_SERVICE_VERSION]: config.release || "v1.0.0",
|
|
61
84
|
"deployment.environment": config.environment || "dev",
|
|
62
85
|
});
|
|
63
|
-
// Initialize tracer provider with span processor and resource
|
|
64
86
|
tracerProvider = new NodeTracerProvider({
|
|
65
87
|
resource,
|
|
66
|
-
spanProcessors: [langfuseProcessor],
|
|
88
|
+
spanProcessors: [new ContextEnricher(), langfuseProcessor],
|
|
67
89
|
});
|
|
68
|
-
// Register provider globally so Vercel AI SDK can use it
|
|
69
90
|
tracerProvider.register();
|
|
70
91
|
isInitialized = true;
|
|
71
92
|
logger.info(`${LOG_PREFIX} Initialized with Langfuse span processor`, {
|
|
@@ -168,3 +189,31 @@ export function getLangfuseHealthStatus() {
|
|
|
168
189
|
: undefined,
|
|
169
190
|
};
|
|
170
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Set user and session context for Langfuse spans in the current async context
|
|
194
|
+
*
|
|
195
|
+
* Merges the provided context with existing AsyncLocalStorage context. If a callback is provided,
|
|
196
|
+
* the context is scoped to that callback execution. Without a callback, the context applies to
|
|
197
|
+
* the current execution context and its children.
|
|
198
|
+
*
|
|
199
|
+
* Uses AsyncLocalStorage to properly scope context per request, avoiding race conditions
|
|
200
|
+
* in concurrent scenarios.
|
|
201
|
+
*
|
|
202
|
+
* @param context - Object containing optional userId and/or sessionId to merge with existing context
|
|
203
|
+
* @param callback - Optional callback to run within the context scope. If omitted, context applies to current execution
|
|
204
|
+
*/
|
|
205
|
+
export async function setLangfuseContext(context, callback) {
|
|
206
|
+
const currentContext = contextStorage.getStore() || {};
|
|
207
|
+
const newContext = {
|
|
208
|
+
userId: context.userId !== undefined ? context.userId : currentContext.userId,
|
|
209
|
+
sessionId: context.sessionId !== undefined
|
|
210
|
+
? context.sessionId
|
|
211
|
+
: currentContext.sessionId,
|
|
212
|
+
};
|
|
213
|
+
if (callback) {
|
|
214
|
+
return await contextStorage.run(newContext, callback);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
contextStorage.enterWith(newContext);
|
|
218
|
+
}
|
|
219
|
+
}
|
package/dist/types/common.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ export type LangfuseConfig = {
|
|
|
24
24
|
environment?: string;
|
|
25
25
|
/** Release/version identifier */
|
|
26
26
|
release?: string;
|
|
27
|
+
/** Optional default user id to attach to spans */
|
|
28
|
+
userId?: string;
|
|
29
|
+
/** Optional default session id to attach to spans */
|
|
30
|
+
sessionId?: string;
|
|
27
31
|
};
|
|
28
32
|
/**
|
|
29
33
|
* OpenTelemetry configuration
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Centralized file detection for all multimodal file types
|
|
4
4
|
* Uses multi-strategy approach for reliable type identification
|
|
5
5
|
*/
|
|
6
|
-
import { request } from "undici";
|
|
6
|
+
import { request, getGlobalDispatcher, interceptors } from "undici";
|
|
7
7
|
import { readFile, stat } from "fs/promises";
|
|
8
8
|
import { logger } from "./logger.js";
|
|
9
9
|
import { CSVProcessor } from "./csvProcessor.js";
|
|
@@ -146,10 +146,10 @@ export class FileDetector {
|
|
|
146
146
|
const maxSize = options?.maxSize || 10 * 1024 * 1024;
|
|
147
147
|
const timeout = options?.timeout || 30000;
|
|
148
148
|
const response = await request(url, {
|
|
149
|
+
dispatcher: getGlobalDispatcher().compose(interceptors.redirect({ maxRedirections: 5 })),
|
|
149
150
|
method: "GET",
|
|
150
151
|
headersTimeout: timeout,
|
|
151
152
|
bodyTimeout: timeout,
|
|
152
|
-
maxRedirections: 5,
|
|
153
153
|
});
|
|
154
154
|
if (response.statusCode !== 200) {
|
|
155
155
|
throw new Error(`HTTP ${response.statusCode}`);
|
|
@@ -271,10 +271,10 @@ class MimeTypeStrategy {
|
|
|
271
271
|
}
|
|
272
272
|
try {
|
|
273
273
|
const response = await request(input, {
|
|
274
|
+
dispatcher: getGlobalDispatcher().compose(interceptors.redirect({ maxRedirections: 5 })),
|
|
274
275
|
method: "HEAD",
|
|
275
276
|
headersTimeout: 5000,
|
|
276
277
|
bodyTimeout: 5000,
|
|
277
|
-
maxRedirections: 5,
|
|
278
278
|
});
|
|
279
279
|
const contentType = response.headers["content-type"] || "";
|
|
280
280
|
const type = this.mimeToFileType(contentType);
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -19,7 +19,22 @@ declare class NeuroLinkLogger {
|
|
|
19
19
|
private logs;
|
|
20
20
|
private maxLogs;
|
|
21
21
|
private isDebugMode;
|
|
22
|
+
private eventEmitter?;
|
|
22
23
|
constructor();
|
|
24
|
+
/**
|
|
25
|
+
* Sets the event emitter that will receive log events.
|
|
26
|
+
* When set, all log operations will emit a "log-event" event.
|
|
27
|
+
*
|
|
28
|
+
* @param emitter - The event emitter instance
|
|
29
|
+
*/
|
|
30
|
+
setEventEmitter(emitter: {
|
|
31
|
+
emit: (event: string, ...args: unknown[]) => boolean;
|
|
32
|
+
}): void;
|
|
33
|
+
/**
|
|
34
|
+
* Clears the event emitter reference.
|
|
35
|
+
* Should be called when a NeuroLink instance is disposed to prevent memory leaks.
|
|
36
|
+
*/
|
|
37
|
+
clearEventEmitter(): void;
|
|
23
38
|
/**
|
|
24
39
|
* Sets the minimum log level that will be processed and output.
|
|
25
40
|
* Log messages with a level lower than this will be ignored.
|
|
@@ -63,6 +78,7 @@ declare class NeuroLinkLogger {
|
|
|
63
78
|
* 2. Storing entries in the log history
|
|
64
79
|
* 3. Managing log rotation to prevent memory issues
|
|
65
80
|
* 4. Outputting formatted logs to the console
|
|
81
|
+
* 5. Emitting log events if an event emitter is configured
|
|
66
82
|
*
|
|
67
83
|
* This is the central method called by all specific logging methods (debug, info, etc.)
|
|
68
84
|
*
|
|
@@ -164,6 +180,7 @@ declare class NeuroLinkLogger {
|
|
|
164
180
|
* - Unconditional logging (always, table)
|
|
165
181
|
* - Log level control and configuration
|
|
166
182
|
* - Log history management
|
|
183
|
+
* - Event emission for all log operations (when emitter is configured)
|
|
167
184
|
*/
|
|
168
185
|
export declare const logger: {
|
|
169
186
|
debug: (...args: unknown[]) => void;
|
|
@@ -175,6 +192,10 @@ export declare const logger: {
|
|
|
175
192
|
setLogLevel: (level: LogLevel) => void;
|
|
176
193
|
getLogs: (level?: LogLevel) => LogEntry[];
|
|
177
194
|
clearLogs: () => void;
|
|
195
|
+
setEventEmitter: (emitter: {
|
|
196
|
+
emit: (event: string, ...args: unknown[]) => boolean;
|
|
197
|
+
}) => void;
|
|
198
|
+
clearEventEmitter: () => void;
|
|
178
199
|
};
|
|
179
200
|
/**
|
|
180
201
|
* MCP compatibility exports - all use the same unified logger instance.
|
package/dist/utils/logger.js
CHANGED
|
@@ -25,6 +25,7 @@ class NeuroLinkLogger {
|
|
|
25
25
|
logs = [];
|
|
26
26
|
maxLogs = 1000;
|
|
27
27
|
isDebugMode;
|
|
28
|
+
eventEmitter;
|
|
28
29
|
constructor() {
|
|
29
30
|
// Cache debug mode check to avoid repeated array searches
|
|
30
31
|
this.isDebugMode =
|
|
@@ -36,6 +37,22 @@ class NeuroLinkLogger {
|
|
|
36
37
|
this.logLevel = envLevel;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Sets the event emitter that will receive log events.
|
|
42
|
+
* When set, all log operations will emit a "log-event" event.
|
|
43
|
+
*
|
|
44
|
+
* @param emitter - The event emitter instance
|
|
45
|
+
*/
|
|
46
|
+
setEventEmitter(emitter) {
|
|
47
|
+
this.eventEmitter = emitter;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Clears the event emitter reference.
|
|
51
|
+
* Should be called when a NeuroLink instance is disposed to prevent memory leaks.
|
|
52
|
+
*/
|
|
53
|
+
clearEventEmitter() {
|
|
54
|
+
this.eventEmitter = undefined;
|
|
55
|
+
}
|
|
39
56
|
/**
|
|
40
57
|
* Sets the minimum log level that will be processed and output.
|
|
41
58
|
* Log messages with a level lower than this will be ignored.
|
|
@@ -106,6 +123,7 @@ class NeuroLinkLogger {
|
|
|
106
123
|
* 2. Storing entries in the log history
|
|
107
124
|
* 3. Managing log rotation to prevent memory issues
|
|
108
125
|
* 4. Outputting formatted logs to the console
|
|
126
|
+
* 5. Emitting log events if an event emitter is configured
|
|
109
127
|
*
|
|
110
128
|
* This is the central method called by all specific logging methods (debug, info, etc.)
|
|
111
129
|
*
|
|
@@ -123,6 +141,20 @@ class NeuroLinkLogger {
|
|
|
123
141
|
timestamp: new Date(),
|
|
124
142
|
data,
|
|
125
143
|
};
|
|
144
|
+
// Emit log event if emitter is configured
|
|
145
|
+
if (this.eventEmitter) {
|
|
146
|
+
try {
|
|
147
|
+
this.eventEmitter.emit("log-event", {
|
|
148
|
+
level,
|
|
149
|
+
message,
|
|
150
|
+
timestamp: new Date().getTime(),
|
|
151
|
+
data,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Silently ignore emitter errors to avoid disrupting logging
|
|
156
|
+
}
|
|
157
|
+
}
|
|
126
158
|
// Store log entry
|
|
127
159
|
this.logs.push(entry);
|
|
128
160
|
// Trim old logs
|
|
@@ -277,6 +309,7 @@ function processLoggerArgs(args, logMethod) {
|
|
|
277
309
|
* - Unconditional logging (always, table)
|
|
278
310
|
* - Log level control and configuration
|
|
279
311
|
* - Log history management
|
|
312
|
+
* - Event emission for all log operations (when emitter is configured)
|
|
280
313
|
*/
|
|
281
314
|
export const logger = {
|
|
282
315
|
debug: (...args) => {
|
|
@@ -309,6 +342,8 @@ export const logger = {
|
|
|
309
342
|
setLogLevel: (level) => neuroLinkLogger.setLogLevel(level),
|
|
310
343
|
getLogs: (level) => neuroLinkLogger.getLogs(level),
|
|
311
344
|
clearLogs: () => neuroLinkLogger.clearLogs(),
|
|
345
|
+
setEventEmitter: (emitter) => neuroLinkLogger.setEventEmitter(emitter),
|
|
346
|
+
clearEventEmitter: () => neuroLinkLogger.clearEventEmitter(),
|
|
312
347
|
};
|
|
313
348
|
/**
|
|
314
349
|
* MCP compatibility exports - all use the same unified logger instance.
|
|
@@ -8,7 +8,7 @@ import { ProviderImageAdapter, MultimodalLogger, } from "../adapters/providerIma
|
|
|
8
8
|
import { logger } from "./logger.js";
|
|
9
9
|
import { FileDetector } from "./fileDetector.js";
|
|
10
10
|
import { PDFProcessor } from "./pdfProcessor.js";
|
|
11
|
-
import { request } from "undici";
|
|
11
|
+
import { request, getGlobalDispatcher, interceptors } from "undici";
|
|
12
12
|
import { readFileSync, existsSync } from "fs";
|
|
13
13
|
/**
|
|
14
14
|
* Type guard for validating message roles
|
|
@@ -562,10 +562,10 @@ function isInternetUrl(input) {
|
|
|
562
562
|
async function downloadImageFromUrl(url) {
|
|
563
563
|
try {
|
|
564
564
|
const response = await request(url, {
|
|
565
|
+
dispatcher: getGlobalDispatcher().compose(interceptors.redirect({ maxRedirections: 5 })),
|
|
565
566
|
method: "GET",
|
|
566
567
|
headersTimeout: 10000, // 10 second timeout for headers
|
|
567
|
-
bodyTimeout: 30000, // 30 second timeout for body
|
|
568
|
-
maxRedirections: 5,
|
|
568
|
+
bodyTimeout: 30000, // 30 second timeout for body,
|
|
569
569
|
});
|
|
570
570
|
if (response.statusCode !== 200) {
|
|
571
571
|
throw new Error(`HTTP ${response.statusCode}: Failed to download image from ${url}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Juspay Technologies",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"url": "https://github.com/sponsors/juspay"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
|
-
"node": ">=18.
|
|
25
|
-
"npm": ">=
|
|
24
|
+
"node": ">=20.18.1",
|
|
25
|
+
"npm": ">=10.0.0",
|
|
26
26
|
"pnpm": ">=8.0.0"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
"p-limit": "^6.2.0",
|
|
193
193
|
"reconnecting-eventsource": "^1.6.4",
|
|
194
194
|
"redis": "^5.8.2",
|
|
195
|
-
"undici": "^
|
|
195
|
+
"undici": "^7.5.0",
|
|
196
196
|
"uuid": "^11.1.0",
|
|
197
197
|
"ws": "^8.18.3",
|
|
198
198
|
"yargs": "^17.7.2",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"svelte-check": "^4.0.0",
|
|
241
241
|
"tslib": "^2.4.1",
|
|
242
242
|
"typescript": "^5.0.0",
|
|
243
|
-
"vite": "^6.
|
|
243
|
+
"vite": "^6.4.1",
|
|
244
244
|
"vitest": "^2.0.0",
|
|
245
245
|
"why-is-node-running": "^3.2.2"
|
|
246
246
|
},
|