@juspay/neurolink 9.37.0 → 9.38.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.
@@ -10,8 +10,9 @@
10
10
  * The extracted content is formatted as text + images that can be sent to any
11
11
  * AI provider for analysis.
12
12
  *
13
- * Uses fluent-ffmpeg for video processing and sharp for frame resizing.
14
- * Requires ffmpeg/ffprobe to be available (via ffmpeg-static or system PATH).
13
+ * Uses mediabunny (pure TypeScript) for metadata extraction, with fluent-ffmpeg
14
+ * as a fallback for unsupported formats. Requires ffmpeg for keyframe/subtitle
15
+ * extraction (via ffmpeg-static or system PATH).
15
16
  *
16
17
  * Key features:
17
18
  * - Adaptive keyframe extraction intervals based on video duration
@@ -157,6 +158,11 @@ export declare class VideoProcessor extends BaseFileProcessor<ProcessedVideo> {
157
158
  * @returns Success result with probe data or error message
158
159
  */
159
160
  private probeVideo;
161
+ /**
162
+ * Probe a video file using mediabunny (pure TypeScript, no native binary).
163
+ * Falls back to ffprobe if mediabunny fails or doesn't support the format.
164
+ */
165
+ private probeVideoWithMediabunny;
160
166
  /**
161
167
  * Build a structured metadata object from ffprobe data.
162
168
  *
@@ -10,8 +10,9 @@
10
10
  * The extracted content is formatted as text + images that can be sent to any
11
11
  * AI provider for analysis.
12
12
  *
13
- * Uses fluent-ffmpeg for video processing and sharp for frame resizing.
14
- * Requires ffmpeg/ffprobe to be available (via ffmpeg-static or system PATH).
13
+ * Uses mediabunny (pure TypeScript) for metadata extraction, with fluent-ffmpeg
14
+ * as a fallback for unsupported formats. Requires ffmpeg for keyframe/subtitle
15
+ * extraction (via ffmpeg-static or system PATH).
15
16
  *
16
17
  * Key features:
17
18
  * - Adaptive keyframe extraction intervals based on video duration
@@ -42,10 +43,10 @@
42
43
  * }
43
44
  * ```
44
45
  */
45
- /// <reference path="./ffprobe-static.d.ts" />
46
46
  import { randomUUID } from "crypto";
47
47
  import ffmpegCommand from "fluent-ffmpeg";
48
48
  import { createWriteStream, existsSync, promises as fs } from "fs";
49
+ import { Input, FilePathSource, ALL_FORMATS } from "mediabunny";
49
50
  import { tmpdir } from "os";
50
51
  import { join } from "path";
51
52
  import { Readable } from "stream";
@@ -64,8 +65,12 @@ import { logger } from "../../utils/logger.js";
64
65
  */
65
66
  let ffmpegPathInitialized = false;
66
67
  /**
67
- * Initialize ffmpeg and ffprobe binary paths.
68
- * Tries ffmpeg-static/ffprobe-static first, falls back to system binaries in PATH.
68
+ * Initialize ffmpeg binary paths.
69
+ * Tries ffmpeg-static first, falls back to system binary in PATH.
70
+ *
71
+ * Note: ffprobe-static has been removed. Metadata probing now uses mediabunny
72
+ * (pure TypeScript) as the primary method, with ffprobe as a fallback only when
73
+ * mediabunny cannot handle the format (e.g., AVI, FLV).
69
74
  *
70
75
  * This is called lazily on the first processFile() invocation so that the module
71
76
  * can be imported without side effects.
@@ -91,27 +96,6 @@ async function initFfmpegPaths() {
91
96
  catch {
92
97
  // Use system ffmpeg (already in PATH)
93
98
  }
94
- // Try ffprobe-static first, fall back to system ffprobe
95
- try {
96
- const ffprobeStatic = (await import("ffprobe-static"));
97
- // Direct path property (CommonJS default)
98
- if (typeof ffprobeStatic["path"] === "string" &&
99
- existsSync(ffprobeStatic["path"])) {
100
- ffmpegCommand.setFfprobePath(ffprobeStatic["path"]);
101
- }
102
- else if (ffprobeStatic["default"] &&
103
- typeof ffprobeStatic["default"] === "object" &&
104
- typeof ffprobeStatic["default"]["path"] ===
105
- "string") {
106
- const probePath = ffprobeStatic["default"]["path"];
107
- if (existsSync(probePath)) {
108
- ffmpegCommand.setFfprobePath(probePath);
109
- }
110
- }
111
- }
112
- catch {
113
- // Use system ffprobe (already in PATH)
114
- }
115
99
  }
116
100
  // =============================================================================
117
101
  // CONSTANTS
@@ -372,23 +356,33 @@ export class VideoProcessor extends BaseFileProcessor {
372
356
  const extension = this.getExtensionFromFileInfo(fileInfo);
373
357
  const tempVideoPath = join(tempDir, `input${extension}`);
374
358
  await this.writeBufferToFile(buffer, tempVideoPath);
375
- // Step 4: Extract metadata via ffprobe
376
- const probeResult = await this.probeVideo(tempVideoPath);
377
- if (!probeResult.success) {
378
- const errMsg = probeResult.error || "ffprobe failed";
379
- span.setAttribute(ATTR.FILE_SUCCESS, false);
380
- span.setAttribute(ATTR.FILE_ERROR, errMsg);
381
- logger.warn(`[NEUROLINK] Video skipped/failed: ${filename} — reason: ${errMsg}`);
382
- return {
383
- success: false,
384
- error: this.createError(FileErrorCode.PROCESSING_FAILED, {
385
- fileType: "video",
386
- reason: probeResult.error,
387
- }),
359
+ // Step 4: Extract metadata try mediabunny first (pure TS, no binary),
360
+ // fall back to ffprobe for formats mediabunny doesn't support (AVI, FLV, WMV).
361
+ let metadata;
362
+ const mediabunnyResult = await this.probeVideoWithMediabunny(tempVideoPath);
363
+ if (mediabunnyResult.success && mediabunnyResult.data) {
364
+ metadata = { ...mediabunnyResult.data, fileSize: buffer.length };
365
+ }
366
+ else {
367
+ // Fall back to ffprobe (requires system ffprobe to be available)
368
+ const probeResult = await this.probeVideo(tempVideoPath);
369
+ if (probeResult.success && probeResult.data) {
370
+ metadata = this.buildMetadata(probeResult.data, buffer.length);
371
+ }
372
+ }
373
+ if (!metadata) {
374
+ metadata = {
375
+ duration: 0,
376
+ durationFormatted: "unknown",
377
+ width: 0,
378
+ height: 0,
379
+ codec: "unknown",
380
+ fps: 0,
381
+ bitrate: 0,
382
+ subtitleTracks: 0,
383
+ fileSize: buffer.length,
388
384
  };
389
385
  }
390
- const probeData = probeResult.data;
391
- const metadata = this.buildMetadata(probeData, buffer.length);
392
386
  // Record video-specific metadata on span
393
387
  span.setAttribute(ATTR.VIDEO_DURATION_SEC, metadata.duration);
394
388
  span.setAttribute(ATTR.VIDEO_WIDTH, metadata.width);
@@ -494,6 +488,61 @@ export class VideoProcessor extends BaseFileProcessor {
494
488
  });
495
489
  });
496
490
  }
491
+ /**
492
+ * Probe a video file using mediabunny (pure TypeScript, no native binary).
493
+ * Falls back to ffprobe if mediabunny fails or doesn't support the format.
494
+ */
495
+ async probeVideoWithMediabunny(filePath) {
496
+ let input;
497
+ try {
498
+ input = new Input({
499
+ source: new FilePathSource(filePath),
500
+ formats: [...ALL_FORMATS],
501
+ });
502
+ const duration = await input.computeDuration();
503
+ const videoTrack = await input.getPrimaryVideoTrack();
504
+ const audioTrack = await input.getPrimaryAudioTrack();
505
+ const allTracks = await input.getTracks();
506
+ const subtitleTracks = allTracks.filter((t) => !t.isVideoTrack() && !t.isAudioTrack());
507
+ // Get FPS from video track packet stats (sample a small number of packets)
508
+ let fps = 0;
509
+ if (videoTrack) {
510
+ try {
511
+ const stats = await videoTrack.computePacketStats(120);
512
+ fps = Math.round(stats.averagePacketRate * 100) / 100;
513
+ }
514
+ catch {
515
+ // FPS unavailable — non-fatal
516
+ }
517
+ }
518
+ return {
519
+ success: true,
520
+ data: {
521
+ duration: duration ?? 0,
522
+ durationFormatted: this.formatDuration(duration ?? 0),
523
+ width: videoTrack?.displayWidth ?? 0,
524
+ height: videoTrack?.displayHeight ?? 0,
525
+ codec: videoTrack?.codec ?? "unknown",
526
+ fps,
527
+ bitrate: 0,
528
+ audioCodec: audioTrack?.codec ?? undefined,
529
+ audioChannels: audioTrack?.numberOfChannels,
530
+ audioSampleRate: audioTrack?.sampleRate,
531
+ subtitleTracks: subtitleTracks.length,
532
+ fileSize: 0,
533
+ },
534
+ };
535
+ }
536
+ catch (error) {
537
+ return {
538
+ success: false,
539
+ error: `mediabunny failed: ${error instanceof Error ? error.message : String(error)}`,
540
+ };
541
+ }
542
+ finally {
543
+ input?.dispose();
544
+ }
545
+ }
497
546
  /**
498
547
  * Build a structured metadata object from ffprobe data.
499
548
  *
@@ -8,7 +8,7 @@ export type HealthMetrics = {
8
8
  };
9
9
  export declare class TelemetryService {
10
10
  private static instance;
11
- private sdk?;
11
+ private tracerProvider?;
12
12
  private enabled;
13
13
  private initialized;
14
14
  private meter?;
@@ -1,12 +1,12 @@
1
- import { NodeSDK } from "@opentelemetry/sdk-node";
2
- import { metrics, trace, } from "@opentelemetry/api";
3
- import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
1
+ import { context, metrics, trace, } from "@opentelemetry/api";
2
+ import { BasicTracerProvider, BatchSpanProcessor, } from "@opentelemetry/sdk-trace-base";
3
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
4
4
  import { resourceFromAttributes } from "@opentelemetry/resources";
5
5
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
6
6
  import { logger } from "../utils/logger.js";
7
7
  export class TelemetryService {
8
8
  static instance;
9
- sdk;
9
+ tracerProvider;
10
10
  enabled = false;
11
11
  initialized = false;
12
12
  meter;
@@ -52,11 +52,16 @@ export class TelemetryService {
52
52
  [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
53
53
  [ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || "3.0.1",
54
54
  });
55
- this.sdk = new NodeSDK({
55
+ const exporter = new OTLPTraceExporter({
56
+ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
57
+ ? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`
58
+ : undefined,
59
+ });
60
+ this.tracerProvider = new BasicTracerProvider({
56
61
  resource,
57
- // Note: Metric reader configured separately
58
- instrumentations: [getNodeAutoInstrumentations()],
62
+ spanProcessors: [new BatchSpanProcessor(exporter)],
59
63
  });
64
+ trace.setGlobalTracerProvider(this.tracerProvider);
60
65
  this.meter = metrics.getMeter("neurolink-ai");
61
66
  this.tracer = trace.getTracer("neurolink-ai");
62
67
  this.initializeMetrics();
@@ -101,12 +106,21 @@ export class TelemetryService {
101
106
  return;
102
107
  }
103
108
  try {
104
- await this.sdk?.start();
109
+ // Register AsyncLocalStorage context manager for proper parent-child
110
+ // span relationships across async boundaries (required for startActiveSpan)
111
+ try {
112
+ const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks");
113
+ context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
114
+ }
115
+ catch {
116
+ // context-async-hooks not installed — context propagation
117
+ // will use the default (noop) manager
118
+ }
105
119
  this.initialized = true;
106
- logger.debug("[Telemetry] SDK started successfully");
120
+ logger.debug("[Telemetry] Tracer provider started successfully");
107
121
  }
108
122
  catch (error) {
109
- logger.error("[Telemetry] Failed to start SDK:", error);
123
+ logger.error("[Telemetry] Failed to start:", error);
110
124
  this.enabled = false;
111
125
  this.initialized = false;
112
126
  }
@@ -294,11 +308,11 @@ export class TelemetryService {
294
308
  }
295
309
  // Cleanup
296
310
  async shutdown() {
297
- if (this.enabled && this.sdk) {
311
+ if (this.enabled && this.tracerProvider) {
298
312
  try {
299
- await this.sdk.shutdown();
313
+ await this.tracerProvider.shutdown();
300
314
  this.initialized = false;
301
- logger.debug("[Telemetry] SDK shutdown completed");
315
+ logger.debug("[Telemetry] Tracer provider shutdown completed");
302
316
  }
303
317
  catch (error) {
304
318
  logger.error("[Telemetry] Error during shutdown:", error);
@@ -10,8 +10,9 @@
10
10
  * The extracted content is formatted as text + images that can be sent to any
11
11
  * AI provider for analysis.
12
12
  *
13
- * Uses fluent-ffmpeg for video processing and sharp for frame resizing.
14
- * Requires ffmpeg/ffprobe to be available (via ffmpeg-static or system PATH).
13
+ * Uses mediabunny (pure TypeScript) for metadata extraction, with fluent-ffmpeg
14
+ * as a fallback for unsupported formats. Requires ffmpeg for keyframe/subtitle
15
+ * extraction (via ffmpeg-static or system PATH).
15
16
  *
16
17
  * Key features:
17
18
  * - Adaptive keyframe extraction intervals based on video duration
@@ -157,6 +158,11 @@ export declare class VideoProcessor extends BaseFileProcessor<ProcessedVideo> {
157
158
  * @returns Success result with probe data or error message
158
159
  */
159
160
  private probeVideo;
161
+ /**
162
+ * Probe a video file using mediabunny (pure TypeScript, no native binary).
163
+ * Falls back to ffprobe if mediabunny fails or doesn't support the format.
164
+ */
165
+ private probeVideoWithMediabunny;
160
166
  /**
161
167
  * Build a structured metadata object from ffprobe data.
162
168
  *
@@ -10,8 +10,9 @@
10
10
  * The extracted content is formatted as text + images that can be sent to any
11
11
  * AI provider for analysis.
12
12
  *
13
- * Uses fluent-ffmpeg for video processing and sharp for frame resizing.
14
- * Requires ffmpeg/ffprobe to be available (via ffmpeg-static or system PATH).
13
+ * Uses mediabunny (pure TypeScript) for metadata extraction, with fluent-ffmpeg
14
+ * as a fallback for unsupported formats. Requires ffmpeg for keyframe/subtitle
15
+ * extraction (via ffmpeg-static or system PATH).
15
16
  *
16
17
  * Key features:
17
18
  * - Adaptive keyframe extraction intervals based on video duration
@@ -42,10 +43,10 @@
42
43
  * }
43
44
  * ```
44
45
  */
45
- /// <reference path="./ffprobe-static.d.ts" />
46
46
  import { randomUUID } from "crypto";
47
47
  import ffmpegCommand from "fluent-ffmpeg";
48
48
  import { createWriteStream, existsSync, promises as fs } from "fs";
49
+ import { Input, FilePathSource, ALL_FORMATS } from "mediabunny";
49
50
  import { tmpdir } from "os";
50
51
  import { join } from "path";
51
52
  import { Readable } from "stream";
@@ -64,8 +65,12 @@ import { logger } from "../../utils/logger.js";
64
65
  */
65
66
  let ffmpegPathInitialized = false;
66
67
  /**
67
- * Initialize ffmpeg and ffprobe binary paths.
68
- * Tries ffmpeg-static/ffprobe-static first, falls back to system binaries in PATH.
68
+ * Initialize ffmpeg binary paths.
69
+ * Tries ffmpeg-static first, falls back to system binary in PATH.
70
+ *
71
+ * Note: ffprobe-static has been removed. Metadata probing now uses mediabunny
72
+ * (pure TypeScript) as the primary method, with ffprobe as a fallback only when
73
+ * mediabunny cannot handle the format (e.g., AVI, FLV).
69
74
  *
70
75
  * This is called lazily on the first processFile() invocation so that the module
71
76
  * can be imported without side effects.
@@ -91,27 +96,6 @@ async function initFfmpegPaths() {
91
96
  catch {
92
97
  // Use system ffmpeg (already in PATH)
93
98
  }
94
- // Try ffprobe-static first, fall back to system ffprobe
95
- try {
96
- const ffprobeStatic = (await import("ffprobe-static"));
97
- // Direct path property (CommonJS default)
98
- if (typeof ffprobeStatic["path"] === "string" &&
99
- existsSync(ffprobeStatic["path"])) {
100
- ffmpegCommand.setFfprobePath(ffprobeStatic["path"]);
101
- }
102
- else if (ffprobeStatic["default"] &&
103
- typeof ffprobeStatic["default"] === "object" &&
104
- typeof ffprobeStatic["default"]["path"] ===
105
- "string") {
106
- const probePath = ffprobeStatic["default"]["path"];
107
- if (existsSync(probePath)) {
108
- ffmpegCommand.setFfprobePath(probePath);
109
- }
110
- }
111
- }
112
- catch {
113
- // Use system ffprobe (already in PATH)
114
- }
115
99
  }
116
100
  // =============================================================================
117
101
  // CONSTANTS
@@ -372,23 +356,33 @@ export class VideoProcessor extends BaseFileProcessor {
372
356
  const extension = this.getExtensionFromFileInfo(fileInfo);
373
357
  const tempVideoPath = join(tempDir, `input${extension}`);
374
358
  await this.writeBufferToFile(buffer, tempVideoPath);
375
- // Step 4: Extract metadata via ffprobe
376
- const probeResult = await this.probeVideo(tempVideoPath);
377
- if (!probeResult.success) {
378
- const errMsg = probeResult.error || "ffprobe failed";
379
- span.setAttribute(ATTR.FILE_SUCCESS, false);
380
- span.setAttribute(ATTR.FILE_ERROR, errMsg);
381
- logger.warn(`[NEUROLINK] Video skipped/failed: ${filename} — reason: ${errMsg}`);
382
- return {
383
- success: false,
384
- error: this.createError(FileErrorCode.PROCESSING_FAILED, {
385
- fileType: "video",
386
- reason: probeResult.error,
387
- }),
359
+ // Step 4: Extract metadata try mediabunny first (pure TS, no binary),
360
+ // fall back to ffprobe for formats mediabunny doesn't support (AVI, FLV, WMV).
361
+ let metadata;
362
+ const mediabunnyResult = await this.probeVideoWithMediabunny(tempVideoPath);
363
+ if (mediabunnyResult.success && mediabunnyResult.data) {
364
+ metadata = { ...mediabunnyResult.data, fileSize: buffer.length };
365
+ }
366
+ else {
367
+ // Fall back to ffprobe (requires system ffprobe to be available)
368
+ const probeResult = await this.probeVideo(tempVideoPath);
369
+ if (probeResult.success && probeResult.data) {
370
+ metadata = this.buildMetadata(probeResult.data, buffer.length);
371
+ }
372
+ }
373
+ if (!metadata) {
374
+ metadata = {
375
+ duration: 0,
376
+ durationFormatted: "unknown",
377
+ width: 0,
378
+ height: 0,
379
+ codec: "unknown",
380
+ fps: 0,
381
+ bitrate: 0,
382
+ subtitleTracks: 0,
383
+ fileSize: buffer.length,
388
384
  };
389
385
  }
390
- const probeData = probeResult.data;
391
- const metadata = this.buildMetadata(probeData, buffer.length);
392
386
  // Record video-specific metadata on span
393
387
  span.setAttribute(ATTR.VIDEO_DURATION_SEC, metadata.duration);
394
388
  span.setAttribute(ATTR.VIDEO_WIDTH, metadata.width);
@@ -494,6 +488,61 @@ export class VideoProcessor extends BaseFileProcessor {
494
488
  });
495
489
  });
496
490
  }
491
+ /**
492
+ * Probe a video file using mediabunny (pure TypeScript, no native binary).
493
+ * Falls back to ffprobe if mediabunny fails or doesn't support the format.
494
+ */
495
+ async probeVideoWithMediabunny(filePath) {
496
+ let input;
497
+ try {
498
+ input = new Input({
499
+ source: new FilePathSource(filePath),
500
+ formats: [...ALL_FORMATS],
501
+ });
502
+ const duration = await input.computeDuration();
503
+ const videoTrack = await input.getPrimaryVideoTrack();
504
+ const audioTrack = await input.getPrimaryAudioTrack();
505
+ const allTracks = await input.getTracks();
506
+ const subtitleTracks = allTracks.filter((t) => !t.isVideoTrack() && !t.isAudioTrack());
507
+ // Get FPS from video track packet stats (sample a small number of packets)
508
+ let fps = 0;
509
+ if (videoTrack) {
510
+ try {
511
+ const stats = await videoTrack.computePacketStats(120);
512
+ fps = Math.round(stats.averagePacketRate * 100) / 100;
513
+ }
514
+ catch {
515
+ // FPS unavailable — non-fatal
516
+ }
517
+ }
518
+ return {
519
+ success: true,
520
+ data: {
521
+ duration: duration ?? 0,
522
+ durationFormatted: this.formatDuration(duration ?? 0),
523
+ width: videoTrack?.displayWidth ?? 0,
524
+ height: videoTrack?.displayHeight ?? 0,
525
+ codec: videoTrack?.codec ?? "unknown",
526
+ fps,
527
+ bitrate: 0,
528
+ audioCodec: audioTrack?.codec ?? undefined,
529
+ audioChannels: audioTrack?.numberOfChannels,
530
+ audioSampleRate: audioTrack?.sampleRate,
531
+ subtitleTracks: subtitleTracks.length,
532
+ fileSize: 0,
533
+ },
534
+ };
535
+ }
536
+ catch (error) {
537
+ return {
538
+ success: false,
539
+ error: `mediabunny failed: ${error instanceof Error ? error.message : String(error)}`,
540
+ };
541
+ }
542
+ finally {
543
+ input?.dispose();
544
+ }
545
+ }
497
546
  /**
498
547
  * Build a structured metadata object from ffprobe data.
499
548
  *
@@ -8,7 +8,7 @@ export type HealthMetrics = {
8
8
  };
9
9
  export declare class TelemetryService {
10
10
  private static instance;
11
- private sdk?;
11
+ private tracerProvider?;
12
12
  private enabled;
13
13
  private initialized;
14
14
  private meter?;
@@ -1,12 +1,12 @@
1
- import { NodeSDK } from "@opentelemetry/sdk-node";
2
- import { metrics, trace, } from "@opentelemetry/api";
3
- import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
1
+ import { context, metrics, trace, } from "@opentelemetry/api";
2
+ import { BasicTracerProvider, BatchSpanProcessor, } from "@opentelemetry/sdk-trace-base";
3
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
4
4
  import { resourceFromAttributes } from "@opentelemetry/resources";
5
5
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
6
6
  import { logger } from "../utils/logger.js";
7
7
  export class TelemetryService {
8
8
  static instance;
9
- sdk;
9
+ tracerProvider;
10
10
  enabled = false;
11
11
  initialized = false;
12
12
  meter;
@@ -52,11 +52,16 @@ export class TelemetryService {
52
52
  [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || "neurolink-ai",
53
53
  [ATTR_SERVICE_VERSION]: process.env.OTEL_SERVICE_VERSION || "3.0.1",
54
54
  });
55
- this.sdk = new NodeSDK({
55
+ const exporter = new OTLPTraceExporter({
56
+ url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
57
+ ? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`
58
+ : undefined,
59
+ });
60
+ this.tracerProvider = new BasicTracerProvider({
56
61
  resource,
57
- // Note: Metric reader configured separately
58
- instrumentations: [getNodeAutoInstrumentations()],
62
+ spanProcessors: [new BatchSpanProcessor(exporter)],
59
63
  });
64
+ trace.setGlobalTracerProvider(this.tracerProvider);
60
65
  this.meter = metrics.getMeter("neurolink-ai");
61
66
  this.tracer = trace.getTracer("neurolink-ai");
62
67
  this.initializeMetrics();
@@ -101,12 +106,21 @@ export class TelemetryService {
101
106
  return;
102
107
  }
103
108
  try {
104
- await this.sdk?.start();
109
+ // Register AsyncLocalStorage context manager for proper parent-child
110
+ // span relationships across async boundaries (required for startActiveSpan)
111
+ try {
112
+ const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks");
113
+ context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
114
+ }
115
+ catch {
116
+ // context-async-hooks not installed — context propagation
117
+ // will use the default (noop) manager
118
+ }
105
119
  this.initialized = true;
106
- logger.debug("[Telemetry] SDK started successfully");
120
+ logger.debug("[Telemetry] Tracer provider started successfully");
107
121
  }
108
122
  catch (error) {
109
- logger.error("[Telemetry] Failed to start SDK:", error);
123
+ logger.error("[Telemetry] Failed to start:", error);
110
124
  this.enabled = false;
111
125
  this.initialized = false;
112
126
  }
@@ -294,11 +308,11 @@ export class TelemetryService {
294
308
  }
295
309
  // Cleanup
296
310
  async shutdown() {
297
- if (this.enabled && this.sdk) {
311
+ if (this.enabled && this.tracerProvider) {
298
312
  try {
299
- await this.sdk.shutdown();
313
+ await this.tracerProvider.shutdown();
300
314
  this.initialized = false;
301
- logger.debug("[Telemetry] SDK shutdown completed");
315
+ logger.debug("[Telemetry] Tracer provider shutdown completed");
302
316
  }
303
317
  catch (error) {
304
318
  logger.error("[Telemetry] Error during shutdown:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.37.0",
3
+ "version": "9.38.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 13 providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
5
5
  "author": {
6
6
  "name": "Juspay Technologies",
@@ -31,6 +31,8 @@
31
31
  "build:browser": "node scripts/build-browser.mjs",
32
32
  "build:browser:dev": "node scripts/build-browser.mjs --dev",
33
33
  "build:cli": "echo 'Building CLI...' && svelte-kit sync && tsc --project tsconfig.cli.json",
34
+ "build:cli:bundle": "node scripts/bundle-cli.mjs",
35
+ "build:cli:bundle:minify": "node scripts/bundle-cli.mjs --minify",
34
36
  "build:action": "ncc build src/action/index.ts -o action-dist --source-map",
35
37
  "build:cli:link": "pnpm run build:cli && pnpm link --global",
36
38
  "cli": "node dist/cli/index.js",
@@ -202,11 +204,11 @@
202
204
  "@langfuse/otel": "^5.0.1",
203
205
  "@modelcontextprotocol/sdk": "^1.27.1",
204
206
  "@openrouter/ai-sdk-provider": "^2.2.3",
205
- "@opentelemetry/auto-instrumentations-node": "^0.71.0",
207
+ "@opentelemetry/context-async-hooks": "^2.6.1",
206
208
  "@opentelemetry/core": "^2.6.0",
207
209
  "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
208
210
  "@opentelemetry/resources": "^2.6.0",
209
- "@opentelemetry/sdk-node": "^0.213.0",
211
+ "@opentelemetry/sdk-trace-base": "^2.6.0",
210
212
  "@opentelemetry/semantic-conventions": "^1.40.0",
211
213
  "adm-zip": "^0.5.16",
212
214
  "ai": "^6.0.134",
@@ -222,6 +224,7 @@
222
224
  "json-schema-to-zod": "^2.7.0",
223
225
  "mammoth": "^1.11.0",
224
226
  "mathjs": "^15.1.1",
227
+ "mediabunny": "^1.40.1",
225
228
  "minisearch": "^7.2.0",
226
229
  "music-metadata": "^11.11.2",
227
230
  "nanoid": "^5.1.5",
@@ -244,7 +247,6 @@
244
247
  },
245
248
  "peerDependencies": {
246
249
  "@opentelemetry/api": "^1.9.0",
247
- "@opentelemetry/sdk-trace-base": "^2.0.0",
248
250
  "@opentelemetry/sdk-trace-node": "^2.0.0",
249
251
  "react": ">=18.0.0",
250
252
  "react-dom": ">=18.0.0"
@@ -269,7 +271,6 @@
269
271
  "express-rate-limit": "^8.2.1",
270
272
  "fastify": "^5.7.2",
271
273
  "ffmpeg-static": "^5.3.0",
272
- "ffprobe-static": "^3.1.0",
273
274
  "koa": "^3.1.1",
274
275
  "koa-bodyparser": "^4.4.1",
275
276
  "sharp": "^0.34.5"
@@ -385,8 +386,7 @@
385
386
  "sqlite3",
386
387
  "canvas",
387
388
  "ffmpeg-static",
388
- "sharp",
389
- "ffprobe-static"
389
+ "sharp"
390
390
  ],
391
391
  "overrides": {
392
392
  "esbuild@<=0.24.2": ">=0.25.0",
@@ -1,4 +0,0 @@
1
- declare module "ffprobe-static" {
2
- const ffprobeStatic: { path: string };
3
- export default ffprobeStatic;
4
- }