@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.
- package/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +566 -515
- package/dist/lib/models/anthropicModels.d.ts +3 -1
- package/dist/lib/models/anthropicModels.js +37 -1
- package/dist/lib/processors/media/VideoProcessor.d.ts +8 -2
- package/dist/lib/processors/media/VideoProcessor.js +90 -41
- package/dist/lib/providers/anthropic.js +38 -6
- package/dist/lib/server/routes/claudeProxyRoutes.js +187 -13
- package/dist/lib/telemetry/telemetryService.d.ts +1 -1
- package/dist/lib/telemetry/telemetryService.js +27 -13
- package/dist/models/anthropicModels.d.ts +3 -1
- package/dist/models/anthropicModels.js +37 -1
- package/dist/processors/media/VideoProcessor.d.ts +8 -2
- package/dist/processors/media/VideoProcessor.js +90 -41
- package/dist/providers/anthropic.js +38 -6
- package/dist/server/routes/claudeProxyRoutes.js +187 -13
- package/dist/telemetry/telemetryService.d.ts +1 -1
- package/dist/telemetry/telemetryService.js +27 -13
- package/package.json +7 -7
- package/dist/processors/media/ffprobe-static.d.ts +0 -4
|
@@ -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: [
|
|
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
|
|
14
|
-
*
|
|
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
|
|
14
|
-
*
|
|
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
|
|
68
|
-
* Tries ffmpeg-static
|
|
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
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
223
|
-
this.authMethod =
|
|
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;
|