@juspay/neurolink 9.37.0 → 9.39.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.
@@ -20,8 +20,10 @@ export declare enum AnthropicModel {
20
20
  CLAUDE_3_5_SONNET = "claude-3-5-sonnet-20241022",
21
21
  CLAUDE_3_5_SONNET_V2 = "claude-3-5-sonnet-v2-20241022",
22
22
  CLAUDE_SONNET_4 = "claude-sonnet-4-20250514",
23
+ CLAUDE_SONNET_4_6 = "claude-sonnet-4-6",
23
24
  CLAUDE_3_OPUS = "claude-3-opus-20240229",
24
- CLAUDE_OPUS_4 = "claude-opus-4-20250514"
25
+ CLAUDE_OPUS_4 = "claude-opus-4-20250514",
26
+ CLAUDE_OPUS_4_6 = "claude-opus-4-6"
25
27
  }
26
28
  /**
27
29
  * Model access mapping by subscription tier
@@ -27,10 +27,14 @@ export var AnthropicModel;
27
27
  AnthropicModel["CLAUDE_3_5_SONNET_V2"] = "claude-3-5-sonnet-v2-20241022";
28
28
  // Claude Sonnet 4 (Latest Sonnet)
29
29
  AnthropicModel["CLAUDE_SONNET_4"] = "claude-sonnet-4-20250514";
30
+ // Claude Sonnet 4.6
31
+ AnthropicModel["CLAUDE_SONNET_4_6"] = "claude-sonnet-4-6";
30
32
  // Claude 3 Opus (Legacy flagship)
31
33
  AnthropicModel["CLAUDE_3_OPUS"] = "claude-3-opus-20240229";
32
34
  // Claude Opus 4 (Latest flagship)
33
35
  AnthropicModel["CLAUDE_OPUS_4"] = "claude-opus-4-20250514";
36
+ // Claude Opus 4.6
37
+ AnthropicModel["CLAUDE_OPUS_4_6"] = "claude-opus-4-6";
34
38
  })(AnthropicModel || (AnthropicModel = {}));
35
39
  // ============================================================================
36
40
  // MODEL TIER ACCESS DEFINITIONS
@@ -56,6 +60,7 @@ export const MODEL_TIER_ACCESS = {
56
60
  AnthropicModel.CLAUDE_3_5_SONNET,
57
61
  AnthropicModel.CLAUDE_3_5_SONNET_V2,
58
62
  AnthropicModel.CLAUDE_SONNET_4,
63
+ AnthropicModel.CLAUDE_SONNET_4_6,
59
64
  ],
60
65
  // Max tier: All models including Opus
61
66
  max: ["*"], // All models
@@ -167,6 +172,32 @@ export const MODEL_METADATA = {
167
172
  family: "opus",
168
173
  description: "Latest flagship model with advanced reasoning",
169
174
  },
175
+ // Claude Sonnet 4.6
176
+ [AnthropicModel.CLAUDE_SONNET_4_6]: {
177
+ displayName: "Claude Sonnet 4.6",
178
+ contextWindow: 1000000,
179
+ maxOutputTokens: 64000,
180
+ supportsVision: true,
181
+ supportsExtendedThinking: true,
182
+ supportsToolUse: true,
183
+ supportsStreaming: true,
184
+ deprecated: false,
185
+ family: "sonnet",
186
+ description: "Claude 4.6 Sonnet with 1M context window",
187
+ },
188
+ // Claude Opus 4.6
189
+ [AnthropicModel.CLAUDE_OPUS_4_6]: {
190
+ displayName: "Claude Opus 4.6",
191
+ contextWindow: 1000000,
192
+ maxOutputTokens: 64000,
193
+ supportsVision: true,
194
+ supportsExtendedThinking: true,
195
+ supportsToolUse: true,
196
+ supportsStreaming: true,
197
+ deprecated: false,
198
+ family: "opus",
199
+ description: "Claude 4.6 Opus flagship with 1M context window",
200
+ },
170
201
  };
171
202
  // ============================================================================
172
203
  // DEFAULT MODELS BY TIER
@@ -417,11 +448,16 @@ export function getLatestModelsByFamily() {
417
448
  const familyPriority = {
418
449
  haiku: [AnthropicModel.CLAUDE_3_5_HAIKU, AnthropicModel.CLAUDE_3_HAIKU],
419
450
  sonnet: [
451
+ AnthropicModel.CLAUDE_SONNET_4_6,
420
452
  AnthropicModel.CLAUDE_SONNET_4,
421
453
  AnthropicModel.CLAUDE_3_5_SONNET_V2,
422
454
  AnthropicModel.CLAUDE_3_5_SONNET,
423
455
  ],
424
- opus: [AnthropicModel.CLAUDE_OPUS_4, AnthropicModel.CLAUDE_3_OPUS],
456
+ opus: [
457
+ AnthropicModel.CLAUDE_OPUS_4_6,
458
+ AnthropicModel.CLAUDE_OPUS_4,
459
+ AnthropicModel.CLAUDE_3_OPUS,
460
+ ],
425
461
  };
426
462
  for (const family of Object.keys(familyPriority)) {
427
463
  for (const model of familyPriority[family]) {
@@ -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
  *
@@ -140,7 +140,24 @@ const detectSubscriptionTier = (oauthToken) => {
140
140
  * OAuth takes precedence over API key if both are available.
141
141
  */
142
142
  const detectAuthMethod = (oauthToken) => {
143
- // OAuth takes precedence if available
143
+ // Explicit env var takes highest precedence allows forcing api_key mode
144
+ // even when OAuth credentials exist (e.g., when using a proxy that handles auth)
145
+ const explicit = process.env.ANTHROPIC_AUTH_METHOD?.toLowerCase();
146
+ if (explicit === "api_key" || explicit === "apikey") {
147
+ logger.debug("[detectAuthMethod] Forced to api_key by ANTHROPIC_AUTH_METHOD env var");
148
+ return "api_key";
149
+ }
150
+ if (explicit === "oauth") {
151
+ if (oauthToken) {
152
+ logger.debug("[detectAuthMethod] Forced to oauth by ANTHROPIC_AUTH_METHOD env var");
153
+ return "oauth";
154
+ }
155
+ logger.warn("[detectAuthMethod] ANTHROPIC_AUTH_METHOD=oauth but no OAuth token found; falling through to auto-detection");
156
+ }
157
+ else if (explicit) {
158
+ logger.warn("[detectAuthMethod] Unrecognized ANTHROPIC_AUTH_METHOD value; falling through to auto-detection", { value: explicit });
159
+ }
160
+ // Auto-detect: OAuth takes precedence if available
144
161
  const method = oauthToken ? "oauth" : "api_key";
145
162
  logger.debug("[detectAuthMethod] Auth method resolved", {
146
163
  method,
@@ -200,11 +217,22 @@ export class AnthropicProvider extends BaseProvider {
200
217
  constructor(modelName, sdk, config) {
201
218
  // Pre-compute effective model with tier validation before calling super
202
219
  const oauthToken = config?.oauthToken ?? getOAuthToken();
203
- const subscriptionTier = config?.subscriptionTier ?? detectSubscriptionTier(oauthToken);
220
+ // Resolve auth method FIRST so that tier detection uses the chosen method.
221
+ // If ANTHROPIC_AUTH_METHOD=api_key wins over an existing OAuth token, the
222
+ // tier must reflect api_key mode (full model access) rather than the OAuth
223
+ // token's subscription level.
224
+ const authMethod = config?.authMethod ?? detectAuthMethod(oauthToken);
225
+ const subscriptionTier = config?.subscriptionTier ??
226
+ (authMethod === "oauth" ? detectSubscriptionTier(oauthToken) : "api");
204
227
  const targetModel = modelName || getDefaultAnthropicModel();
205
- // Determine effective model based on tier access
228
+ // Determine effective model based on tier access.
229
+ // Skip tier validation when a proxy is in use (ANTHROPIC_BASE_URL is set)
230
+ // — the proxy handles model access and auth, so the SDK should pass
231
+ // the requested model through without downgrading.
206
232
  let effectiveModel = targetModel;
207
- if (subscriptionTier !== "api" &&
233
+ const usingProxy = !!process.env.ANTHROPIC_BASE_URL;
234
+ if (!usingProxy &&
235
+ subscriptionTier !== "api" &&
208
236
  !isModelAvailableForTier(targetModel, subscriptionTier)) {
209
237
  effectiveModel = getRecommendedModelForTier(subscriptionTier);
210
238
  logger.warn("Model not available for subscription tier, using recommended model", {
@@ -219,8 +247,8 @@ export class AnthropicProvider extends BaseProvider {
219
247
  // Store computed values
220
248
  this.oauthToken = oauthToken;
221
249
  this.subscriptionTier = subscriptionTier;
222
- // Determine auth method - config takes precedence, then auto-detect
223
- this.authMethod = config?.authMethod ?? detectAuthMethod(this.oauthToken);
250
+ // Use the auth method already resolved above (before tier computation)
251
+ this.authMethod = authMethod;
224
252
  // Build headers based on auth method and subscription tier
225
253
  const headers = this.getAuthHeaders();
226
254
  // Create Anthropic instance based on auth method
@@ -348,6 +376,10 @@ export class AnthropicProvider extends BaseProvider {
348
376
  * ```
349
377
  */
350
378
  validateModelAccess(model) {
379
+ // Proxy mode: bypass tier validation entirely — the proxy handles model access
380
+ if (process.env.ANTHROPIC_BASE_URL) {
381
+ return true;
382
+ }
351
383
  // API tier has access to all models
352
384
  if (this.subscriptionTier === "api") {
353
385
  return true;