@juspay/neurolink 12.29.2 → 12.30.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 +3 -3
- package/dist/browser/neurolink.min.js +357 -355
- package/dist/cli/factories/commandFactory.js +2 -1
- package/dist/core/baseProvider.js +1 -0
- package/dist/core/modules/MessageBuilder.js +8 -0
- package/dist/processors/media/VideoProcessor.d.ts +56 -4
- package/dist/processors/media/VideoProcessor.js +263 -19
- package/dist/types/file.d.ts +20 -2
- package/dist/types/generate.d.ts +20 -26
- package/dist/types/processor.d.ts +43 -0
- package/dist/types/stream.d.ts +11 -11
- package/dist/utils/fileDetector.d.ts +15 -0
- package/dist/utils/fileDetector.js +40 -7
- package/dist/utils/mediaDuration.d.ts +19 -0
- package/dist/utils/mediaDuration.js +23 -0
- package/dist/utils/messageBuilder.js +4 -15
- package/dist/utils/multimodalOptionsBuilder.d.ts +2 -0
- package/dist/utils/multimodalOptionsBuilder.js +7 -0
- package/docs-site/static/search-index.json +2 -2
- package/package.json +3 -2
package/dist/types/generate.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { AvatarOptions, AvatarResult } from "./avatar.js";
|
|
|
18
18
|
import type { MusicOptions, MusicResult } from "./music.js";
|
|
19
19
|
import type { StandardRecord, ValidationSchema, ZodUnknownSchema } from "./aliases.js";
|
|
20
20
|
import type { NeurolinkCredentials } from "./providers.js";
|
|
21
|
-
import type { CSVProcessorOptions, FileWithMetadata, MultimodalAudioEntry, MultimodalVideoEntry, VisionImageOutputFormat } from "./file.js";
|
|
21
|
+
import type { CSVProcessorOptions, VideoProcessorOptions, FileWithMetadata, MultimodalAudioEntry, MultimodalVideoEntry, VisionImageOutputFormat } from "./file.js";
|
|
22
22
|
import type { WorkflowConfig } from "./workflow.js";
|
|
23
23
|
import type { Schema, Tool, ToolChoice } from "./tools.js";
|
|
24
24
|
import type { StepResult, LanguageModel } from "./providers.js";
|
|
@@ -180,16 +180,15 @@ export type GenerateOptions = {
|
|
|
180
180
|
*/
|
|
181
181
|
outputFormat?: VisionImageOutputFormat;
|
|
182
182
|
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
183
|
+
/**
|
|
184
|
+
* Video processing options — keyframe budget, encoder settings, and
|
|
185
|
+
* whether to transcribe the clip's spoken audio.
|
|
186
|
+
*
|
|
187
|
+
* The canonical shape, rather than a third structurally-identical copy of
|
|
188
|
+
* it: `StreamOptions` and `TextGenerationOptions` declare the same field,
|
|
189
|
+
* and the inline copies had already drifted apart in what they documented.
|
|
190
|
+
*/
|
|
191
|
+
videoOptions?: VideoProcessorOptions;
|
|
193
192
|
/**
|
|
194
193
|
* Text-to-Speech (TTS) configuration
|
|
195
194
|
*
|
|
@@ -1428,6 +1427,16 @@ export type TextGenerationOptions = {
|
|
|
1428
1427
|
expectedOutcome?: string;
|
|
1429
1428
|
evaluationCriteria?: string[];
|
|
1430
1429
|
csvOptions?: CSVProcessorOptions;
|
|
1430
|
+
/**
|
|
1431
|
+
* Video processing options (#478, #433).
|
|
1432
|
+
*
|
|
1433
|
+
* Reconstructed option objects have to carry this the same way they carry
|
|
1434
|
+
* `csvOptions` and `pdfOptions`: the message builder hands it to the
|
|
1435
|
+
* detector, which hands it to `VideoProcessor`, and anything that rebuilds
|
|
1436
|
+
* the options along the way and omits it restores the defaults without
|
|
1437
|
+
* saying so.
|
|
1438
|
+
*/
|
|
1439
|
+
videoOptions?: VideoProcessorOptions;
|
|
1431
1440
|
/** PDF processing options (#258). */
|
|
1432
1441
|
pdfOptions?: {
|
|
1433
1442
|
/** Password for an encrypted PDF (image-conversion fallback path). */
|
|
@@ -1453,21 +1462,6 @@ export type TextGenerationOptions = {
|
|
|
1453
1462
|
*/
|
|
1454
1463
|
outputFormat?: VisionImageOutputFormat;
|
|
1455
1464
|
};
|
|
1456
|
-
/**
|
|
1457
|
-
* Video processing options (mirrors `GenerateOptions.videoOptions`). Never
|
|
1458
|
-
* declared here before, so `buildGenerateTextOptions` had nowhere to
|
|
1459
|
-
* forward the caller's setting even once the allowlist itself named it.
|
|
1460
|
-
*/
|
|
1461
|
-
videoOptions?: {
|
|
1462
|
-
/** Frames to extract. Unset lets VideoProcessor pick from the clip's duration; clamped to 100. */
|
|
1463
|
-
frames?: number;
|
|
1464
|
-
/** Frame encoder quality, clamped to 1-100. Default 80. */
|
|
1465
|
-
quality?: number;
|
|
1466
|
-
/** Frame encoding. Default jpeg. */
|
|
1467
|
-
format?: "jpeg" | "png";
|
|
1468
|
-
/** Not implemented yet (#433) — warns rather than silently doing nothing. */
|
|
1469
|
-
transcribeAudio?: boolean;
|
|
1470
|
-
};
|
|
1471
1465
|
enableSummarization?: boolean;
|
|
1472
1466
|
/**
|
|
1473
1467
|
* Skip injecting tool schemas into the system prompt.
|
|
@@ -711,6 +711,20 @@ export type ProcessedAudio = ProcessedFileBase & {
|
|
|
711
711
|
transcriptionSkippedReason?: string;
|
|
712
712
|
coverArt?: Buffer;
|
|
713
713
|
};
|
|
714
|
+
/**
|
|
715
|
+
* One extracted keyframe and the moment it came from.
|
|
716
|
+
*
|
|
717
|
+
* Internal to extraction: `ProcessedVideo` splits this back into the parallel
|
|
718
|
+
* `keyframes` / `keyframeTimestampsSec` arrays its existing consumers expect.
|
|
719
|
+
* Keeping the pair together while frames are being read and encoded is what
|
|
720
|
+
* makes a dropped frame impossible to mislabel — the alternative is
|
|
721
|
+
* reconstructing timestamps from an interval after the fact, which is wrong
|
|
722
|
+
* for every frame following a failed encode.
|
|
723
|
+
*/
|
|
724
|
+
export type VideoKeyframe = {
|
|
725
|
+
readonly buffer: Buffer;
|
|
726
|
+
readonly timestampSec: number;
|
|
727
|
+
};
|
|
714
728
|
/**
|
|
715
729
|
* Processed video result.
|
|
716
730
|
* Extends ProcessedFileBase with video-specific fields including metadata,
|
|
@@ -720,6 +734,19 @@ export type ProcessedAudio = ProcessedFileBase & {
|
|
|
720
734
|
export type ProcessedVideo = ProcessedFileBase & {
|
|
721
735
|
textContent: string;
|
|
722
736
|
keyframes: Buffer[];
|
|
737
|
+
/**
|
|
738
|
+
* When each entry of `keyframes` was sampled, in seconds from the start of
|
|
739
|
+
* the clip. Same length, same order, ascending.
|
|
740
|
+
*
|
|
741
|
+
* A parallel array rather than a richer `keyframes` element type: that
|
|
742
|
+
* field is read by `fileReferenceRegistry` and by the detector as a plain
|
|
743
|
+
* `Buffer[]`, and reshaping it would break them for a value they do not
|
|
744
|
+
* need. Extraction can also drop an individual frame when its encode
|
|
745
|
+
* fails, so the timestamps are recorded as frames are kept — never
|
|
746
|
+
* reconstructed from an interval, which would silently mislabel every
|
|
747
|
+
* frame after a dropped one.
|
|
748
|
+
*/
|
|
749
|
+
keyframeTimestampsSec: number[];
|
|
723
750
|
metadata: {
|
|
724
751
|
duration: number;
|
|
725
752
|
durationFormatted: string;
|
|
@@ -735,6 +762,22 @@ export type ProcessedVideo = ProcessedFileBase & {
|
|
|
735
762
|
fileSize: number;
|
|
736
763
|
};
|
|
737
764
|
subtitleText?: string;
|
|
765
|
+
/**
|
|
766
|
+
* Speech transcribed from the clip's audio track, when
|
|
767
|
+
* `VideoProcessorOptions.transcribeAudio` asked for it and it worked.
|
|
768
|
+
*
|
|
769
|
+
* Distinct from `subtitleText`, which is an embedded subtitle stream the
|
|
770
|
+
* file already carried. A recording can have one, both or neither.
|
|
771
|
+
*/
|
|
772
|
+
transcript?: string;
|
|
773
|
+
hasTranscript: boolean;
|
|
774
|
+
/**
|
|
775
|
+
* Why no transcript was produced. Follows `ProcessedAudio`'s precedent:
|
|
776
|
+
* "nobody asked", "there is no audio track", "no API key", "the call
|
|
777
|
+
* failed" and "nobody was speaking" are five different situations that
|
|
778
|
+
* otherwise all present as an absent transcript.
|
|
779
|
+
*/
|
|
780
|
+
transcriptionSkippedReason?: string;
|
|
738
781
|
hasKeyframes: boolean;
|
|
739
782
|
frameCount: number;
|
|
740
783
|
};
|
package/dist/types/stream.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { AIModelProviderConfig, NeurolinkCredentials } from "./providers.js
|
|
|
16
16
|
import type { TTSChunk, TTSOptions, TTSResult } from "./tts.js";
|
|
17
17
|
import type { STTOptions, STTResult } from "./stt.js";
|
|
18
18
|
import type { StandardRecord, ValidationSchema } from "./aliases.js";
|
|
19
|
-
import type { CSVProcessorOptions, FileWithMetadata, VisionImageOutputFormat } from "./file.js";
|
|
19
|
+
import type { CSVProcessorOptions, FileWithMetadata, VideoProcessorOptions, VisionImageOutputFormat } from "./file.js";
|
|
20
20
|
import type { WorkflowConfig } from "./workflow.js";
|
|
21
21
|
import type { LanguageModel, StepResult } from "./providers.js";
|
|
22
22
|
import type { Tool, ToolChoice } from "./tools.js";
|
|
@@ -356,6 +356,16 @@ export type StreamOptions = {
|
|
|
356
356
|
};
|
|
357
357
|
};
|
|
358
358
|
csvOptions?: CSVProcessorOptions;
|
|
359
|
+
/**
|
|
360
|
+
* Video processing options (#478, #433).
|
|
361
|
+
*
|
|
362
|
+
* Reconstructed option objects have to carry this the same way they carry
|
|
363
|
+
* `csvOptions` and `pdfOptions`: the message builder hands it to the
|
|
364
|
+
* detector, which hands it to `VideoProcessor`, and anything that rebuilds
|
|
365
|
+
* the options along the way and omits it restores the defaults without
|
|
366
|
+
* saying so.
|
|
367
|
+
*/
|
|
368
|
+
videoOptions?: VideoProcessorOptions;
|
|
359
369
|
/** PDF processing options (#258). */
|
|
360
370
|
pdfOptions?: {
|
|
361
371
|
/** Password for an encrypted PDF (image-conversion fallback path). */
|
|
@@ -386,16 +396,6 @@ export type StreamOptions = {
|
|
|
386
396
|
*/
|
|
387
397
|
outputFormat?: VisionImageOutputFormat;
|
|
388
398
|
};
|
|
389
|
-
videoOptions?: {
|
|
390
|
-
/** Frames to extract. Unset lets VideoProcessor pick from the clip's duration; clamped to 100. */
|
|
391
|
-
frames?: number;
|
|
392
|
-
/** Frame encoder quality, clamped to 1-100. Default 80. */
|
|
393
|
-
quality?: number;
|
|
394
|
-
/** Frame encoding. Default jpeg. */
|
|
395
|
-
format?: "jpeg" | "png";
|
|
396
|
-
/** Not implemented yet (#433) — warns rather than silently doing nothing. */
|
|
397
|
-
transcribeAudio?: boolean;
|
|
398
|
-
};
|
|
399
399
|
/**
|
|
400
400
|
* Text-to-Speech (TTS) configuration for streaming
|
|
401
401
|
*
|
|
@@ -114,6 +114,21 @@ export declare class FileDetector {
|
|
|
114
114
|
/**
|
|
115
115
|
* SDK-8: Format an informative placeholder when a file processor fails.
|
|
116
116
|
* Instead of bare "[Video file: name]" strings, include size, format, and
|
|
117
|
+
* Describe one keyframe well enough for a model to place it in time.
|
|
118
|
+
*
|
|
119
|
+
* The clip's total length rides along because the eighth frame of a
|
|
120
|
+
* recording means nothing without knowing how long the recording is.
|
|
121
|
+
*
|
|
122
|
+
* Explicit units rather than a clock — see `formatKeyframeTimestamp`. An
|
|
123
|
+
* earlier draft used "0:03" here and the model reported the frame as
|
|
124
|
+
* appearing at "3:00".
|
|
125
|
+
*
|
|
126
|
+
* A timestamp can be missing if extraction and its schedule ever disagree
|
|
127
|
+
* in length. That should not happen, but an unlabelled frame is better
|
|
128
|
+
* than one labelled "NaN".
|
|
129
|
+
*/
|
|
130
|
+
private static describeKeyframe;
|
|
131
|
+
/**
|
|
117
132
|
* the reason for failure so the LLM can acknowledge the attachment.
|
|
118
133
|
*/
|
|
119
134
|
private static formatInformativePlaceholder;
|
|
@@ -37,6 +37,7 @@ import { withTimeout } from "./errorHandling.js";
|
|
|
37
37
|
import { normalizeUrlForCache, redactUrlForError, sanitizeErrorCause, } from "./logSanitize.js";
|
|
38
38
|
import { mimeHintToExtension, mimeHintToFileType, normalizeMimeHint, } from "./mimeTypeHints.js";
|
|
39
39
|
import { PDFProcessor } from "./pdfProcessor.js";
|
|
40
|
+
import { formatKeyframeTimestamp } from "./mediaDuration.js";
|
|
40
41
|
/**
|
|
41
42
|
* Short-TTL cache of URL → Content-Type (#323). A URL is commonly detected more
|
|
42
43
|
* than once (repeated multimodal prompts reuse the same asset URL); caching the
|
|
@@ -797,6 +798,26 @@ export class FileDetector {
|
|
|
797
798
|
/**
|
|
798
799
|
* SDK-8: Format an informative placeholder when a file processor fails.
|
|
799
800
|
* Instead of bare "[Video file: name]" strings, include size, format, and
|
|
801
|
+
* Describe one keyframe well enough for a model to place it in time.
|
|
802
|
+
*
|
|
803
|
+
* The clip's total length rides along because the eighth frame of a
|
|
804
|
+
* recording means nothing without knowing how long the recording is.
|
|
805
|
+
*
|
|
806
|
+
* Explicit units rather than a clock — see `formatKeyframeTimestamp`. An
|
|
807
|
+
* earlier draft used "0:03" here and the model reported the frame as
|
|
808
|
+
* appearing at "3:00".
|
|
809
|
+
*
|
|
810
|
+
* A timestamp can be missing if extraction and its schedule ever disagree
|
|
811
|
+
* in length. That should not happen, but an unlabelled frame is better
|
|
812
|
+
* than one labelled "NaN".
|
|
813
|
+
*/
|
|
814
|
+
static describeKeyframe(timestampSec, durationFormatted) {
|
|
815
|
+
if (typeof timestampSec !== "number" || !Number.isFinite(timestampSec)) {
|
|
816
|
+
return undefined;
|
|
817
|
+
}
|
|
818
|
+
return `Video keyframe at ${formatKeyframeTimestamp(timestampSec)} into a ${durationFormatted} video`;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
800
821
|
* the reason for failure so the LLM can acknowledge the attachment.
|
|
801
822
|
*/
|
|
802
823
|
static formatInformativePlaceholder(typeName, filename, content, detection, error) {
|
|
@@ -1120,25 +1141,37 @@ export class FileDetector {
|
|
|
1120
1141
|
// the processor; previously these stopped at the CLI layer.
|
|
1121
1142
|
videoOptions);
|
|
1122
1143
|
if (videoResult.success && videoResult.data) {
|
|
1144
|
+
// Bound to a local because the keyframe map below is a closure, and
|
|
1145
|
+
// TypeScript drops the `videoResult.data` narrowing across one.
|
|
1146
|
+
const video = videoResult.data;
|
|
1123
1147
|
return {
|
|
1124
1148
|
type: "video",
|
|
1125
|
-
content:
|
|
1149
|
+
content: video.textContent ||
|
|
1126
1150
|
FileDetector.formatInformativePlaceholder("Video", videoFilename, content, detection),
|
|
1127
1151
|
mimeType: detection.mimeType,
|
|
1128
|
-
|
|
1129
|
-
|
|
1152
|
+
// Each keyframe carries the moment it was sampled at. Alt text is
|
|
1153
|
+
// the only per-image channel that survives to the model — the
|
|
1154
|
+
// message builder folds it into the prompt as
|
|
1155
|
+
// "[Image N: <alt>]" — so without it the frames arrive as an
|
|
1156
|
+
// unlabelled pile and "what happens at 0:30?" has no answer even
|
|
1157
|
+
// though the frame at 0:30 is right there.
|
|
1158
|
+
images: video.keyframes && video.keyframes.length > 0
|
|
1159
|
+
? video.keyframes.map((frame, index) => ({
|
|
1160
|
+
data: frame,
|
|
1161
|
+
altText: FileDetector.describeKeyframe(video.keyframeTimestampsSec[index], video.metadata.durationFormatted),
|
|
1162
|
+
}))
|
|
1130
1163
|
: undefined,
|
|
1131
1164
|
metadata: {
|
|
1132
1165
|
...detection.metadata,
|
|
1133
|
-
frameCount:
|
|
1134
|
-
hasKeyframes:
|
|
1166
|
+
frameCount: video.frameCount,
|
|
1167
|
+
hasKeyframes: video.hasKeyframes,
|
|
1135
1168
|
// Carried so native delivery can apply its duration ceiling
|
|
1136
1169
|
// without opening the container a second time. The processor
|
|
1137
1170
|
// reports 0 when nothing could probe it; that is "unknown", not a
|
|
1138
1171
|
// zero-length clip, so it is left off rather than passed through
|
|
1139
1172
|
// as a number a ceiling check would happily accept.
|
|
1140
|
-
...(
|
|
1141
|
-
? { durationSec:
|
|
1173
|
+
...(video.metadata.duration > 0
|
|
1174
|
+
? { durationSec: video.metadata.duration }
|
|
1142
1175
|
: {}),
|
|
1143
1176
|
},
|
|
1144
1177
|
};
|
|
@@ -21,3 +21,22 @@
|
|
|
21
21
|
* fabricated number would be worse than an obvious zero.
|
|
22
22
|
*/
|
|
23
23
|
export declare function formatMediaDuration(seconds: number): string;
|
|
24
|
+
/**
|
|
25
|
+
* Format the moment a video keyframe was sampled at — "3.00s",
|
|
26
|
+
* "1234.00s (20m 34s)".
|
|
27
|
+
*
|
|
28
|
+
* Deliberately not the clock form, for the reason in this module's header:
|
|
29
|
+
* the string is read by a language model, and "0:03" is ambiguous. That is
|
|
30
|
+
* not theoretical here — an early draft labelled frames "0:03" and the model
|
|
31
|
+
* reported the green frame as appearing at "3:00".
|
|
32
|
+
*
|
|
33
|
+
* Deliberately not `formatMediaDuration` alone either. That rounds to whole
|
|
34
|
+
* seconds, and an explicit `videoOptions.frames` spreads frames evenly rather
|
|
35
|
+
* than on the duration tier: sixteen frames across a 3.6s clip are 0.225s
|
|
36
|
+
* apart, and every one of them would render as "0s". The precise value is
|
|
37
|
+
* therefore always present, with the readable form added only past a minute,
|
|
38
|
+
* where "1234.00s" stops meaning anything to a reader.
|
|
39
|
+
*
|
|
40
|
+
* Non-finite and negative inputs clamp to zero rather than emitting "NaNs".
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatKeyframeTimestamp(seconds: number): string;
|
|
@@ -42,3 +42,26 @@ export function formatMediaDuration(seconds) {
|
|
|
42
42
|
}
|
|
43
43
|
return parts.join(" ");
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Format the moment a video keyframe was sampled at — "3.00s",
|
|
47
|
+
* "1234.00s (20m 34s)".
|
|
48
|
+
*
|
|
49
|
+
* Deliberately not the clock form, for the reason in this module's header:
|
|
50
|
+
* the string is read by a language model, and "0:03" is ambiguous. That is
|
|
51
|
+
* not theoretical here — an early draft labelled frames "0:03" and the model
|
|
52
|
+
* reported the green frame as appearing at "3:00".
|
|
53
|
+
*
|
|
54
|
+
* Deliberately not `formatMediaDuration` alone either. That rounds to whole
|
|
55
|
+
* seconds, and an explicit `videoOptions.frames` spreads frames evenly rather
|
|
56
|
+
* than on the duration tier: sixteen frames across a 3.6s clip are 0.225s
|
|
57
|
+
* apart, and every one of them would render as "0s". The precise value is
|
|
58
|
+
* therefore always present, with the readable form added only past a minute,
|
|
59
|
+
* where "1234.00s" stops meaning anything to a reader.
|
|
60
|
+
*
|
|
61
|
+
* Non-finite and negative inputs clamp to zero rather than emitting "NaNs".
|
|
62
|
+
*/
|
|
63
|
+
export function formatKeyframeTimestamp(seconds) {
|
|
64
|
+
const safe = Number.isFinite(seconds) && seconds > 0 ? seconds : 0;
|
|
65
|
+
const precise = `${safe.toFixed(2)}s`;
|
|
66
|
+
return safe >= 60 ? `${precise} (${formatMediaDuration(safe)})` : precise;
|
|
67
|
+
}
|
|
@@ -832,20 +832,6 @@ export function mergeMediaFileAliases(input) {
|
|
|
832
832
|
input.audioFiles = undefined;
|
|
833
833
|
input.videoFiles = undefined;
|
|
834
834
|
}
|
|
835
|
-
/**
|
|
836
|
-
* #478: `transcribeAudio` (CLI `--transcribe-audio`) is accepted by the options
|
|
837
|
-
* surface but no video-audio transcription exists yet — VideoProcessor extracts
|
|
838
|
-
* keyframes and embedded subtitle tracks only, and the transcription step is
|
|
839
|
-
* still open as #433. Say so once per request rather than letting the caller
|
|
840
|
-
* believe a transcript was produced and silently omitted.
|
|
841
|
-
*/
|
|
842
|
-
function warnIfVideoTranscriptionRequested(videoOptions) {
|
|
843
|
-
if (videoOptions?.transcribeAudio) {
|
|
844
|
-
logger.warn("[NEUROLINK] Video audio transcription was requested but is not implemented yet " +
|
|
845
|
-
"(tracked as #433). Keyframes and any embedded subtitle tracks are still extracted; " +
|
|
846
|
-
"spoken audio will not be transcribed.");
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
835
|
/**
|
|
850
836
|
* Process the unified files array with auto-detection.
|
|
851
837
|
* Handles lazy file registration, full processing, and preview injection.
|
|
@@ -916,6 +902,10 @@ async function detectFileForUnifiedArray(file, options, provider, genericFileMax
|
|
|
916
902
|
frames: options.videoOptions.frames,
|
|
917
903
|
quality: options.videoOptions.quality,
|
|
918
904
|
format: options.videoOptions.format,
|
|
905
|
+
// #433: `--transcribe-audio` stopped here until now — the flag was
|
|
906
|
+
// accepted, warned about, and never handed to the processor that
|
|
907
|
+
// would have to act on it.
|
|
908
|
+
transcribeAudio: options.videoOptions.transcribeAudio,
|
|
919
909
|
}
|
|
920
910
|
: undefined,
|
|
921
911
|
provider: provider,
|
|
@@ -952,7 +942,6 @@ export async function processUnifiedFilesArray(options, maxSize, provider) {
|
|
|
952
942
|
}
|
|
953
943
|
const totalFiles = pending.length;
|
|
954
944
|
const files = pending;
|
|
955
|
-
warnIfVideoTranscriptionRequested(options.videoOptions);
|
|
956
945
|
return withSpan({
|
|
957
946
|
name: "neurolink.file.process_all",
|
|
958
947
|
tracer: tracers.file,
|
|
@@ -15,6 +15,7 @@ import type { StreamOptions } from "../types/index.js";
|
|
|
15
15
|
* - input.audioFiles: Audio files (Buffer | string paths)
|
|
16
16
|
* - input.videoFiles: Video files (Buffer | string paths)
|
|
17
17
|
* - csvOptions: CSV parsing options
|
|
18
|
+
* - videoOptions: Video frame/quality/format/transcription options
|
|
18
19
|
* - systemPrompt: System-level instructions
|
|
19
20
|
* - conversationMessages: Chat history
|
|
20
21
|
* - temperature: Model temperature (0-1)
|
|
@@ -64,6 +65,7 @@ export declare function buildMultimodalOptions(options: StreamOptions, providerN
|
|
|
64
65
|
imageOptions: {
|
|
65
66
|
outputFormat?: import("../index.js").VisionImageOutputFormat;
|
|
66
67
|
} | undefined;
|
|
68
|
+
videoOptions: import("../index.js").VideoProcessorOptions | undefined;
|
|
67
69
|
systemPrompt: string | undefined;
|
|
68
70
|
conversationHistory: import("../index.js").ChatMessage[] | undefined;
|
|
69
71
|
provider: string;
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* - input.audioFiles: Audio files (Buffer | string paths)
|
|
15
15
|
* - input.videoFiles: Video files (Buffer | string paths)
|
|
16
16
|
* - csvOptions: CSV parsing options
|
|
17
|
+
* - videoOptions: Video frame/quality/format/transcription options
|
|
17
18
|
* - systemPrompt: System-level instructions
|
|
18
19
|
* - conversationMessages: Chat history
|
|
19
20
|
* - temperature: Model temperature (0-1)
|
|
@@ -60,6 +61,12 @@ export function buildMultimodalOptions(options, providerName, modelName) {
|
|
|
60
61
|
csvOptions: options.csvOptions,
|
|
61
62
|
pdfOptions: options.pdfOptions,
|
|
62
63
|
imageOptions: options.imageOptions,
|
|
64
|
+
// Same whitelist hazard as above, one level up: `videoFiles` reaching
|
|
65
|
+
// the processor is not enough on its own — without `videoOptions` too,
|
|
66
|
+
// the frame/quality/format/transcribeAudio knobs a caller set on
|
|
67
|
+
// `StreamOptions` never reach VideoProcessor on the Bedrock branch, and
|
|
68
|
+
// it silently falls back to the duration-tier default.
|
|
69
|
+
videoOptions: options.videoOptions,
|
|
63
70
|
systemPrompt: options.systemPrompt,
|
|
64
71
|
conversationHistory: options.conversationMessages,
|
|
65
72
|
provider: providerName,
|
|
@@ -3899,10 +3899,10 @@
|
|
|
3899
3899
|
{"objectID":"2c4c2bb10b6fea62d177994e6df1345d05606b65f8122b3c969cf214df3363f1","title":"CLI usage","url":"/docs/features/multimodal#cli-usage","content":"`bash","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"CLI usage","lvl3":""}},
|
|
3900
3900
|
{"objectID":"915a9e9701a9e6b02890e744550ed66754549288fa044c90bd2dd873e6b48c2a","title":"Auto-detect","url":"/docs/features/multimodal#auto-detect","content":"neurolink generate \"Describe this video\" --file demo.mp4 --provider google-ai","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Auto-detect","lvl3":""}},
|
|
3901
3901
|
{"objectID":"12ce623ba884b34886674b24daff6ce9e58465c68dc25f4f66c01229c6855513","title":"Explicit flag","url":"/docs/features/multimodal#explicit-flag","content":"neurolink generate \"Summarise this\" --video standup.mp4 --provider vertex","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Explicit flag","lvl3":""}},
|
|
3902
|
-
{"objectID":"08774b3c1d8fd456cb7d64ad98281449b9271854310a9a713048ae2112572d5c","title":"Frame-extraction knobs","url":"/docs/features/multimodal#frame-extraction-knobs","content":"neurolink generate \"Describe each scene\" --file demo.mp4 \\\n --provider openai --video-frames 16 --video-quality 90 --video-format jpeg\n--transcribe-audiovideoOptions.
|
|
3902
|
+
{"objectID":"08774b3c1d8fd456cb7d64ad98281449b9271854310a9a713048ae2112572d5c","title":"Frame-extraction knobs","url":"/docs/features/multimodal#frame-extraction-knobs","content":"neurolink generate \"Describe each scene\" --file demo.mp4 \\\n --provider openai --video-frames 16 --video-quality 90 --video-format jpeg\n--transcribe-audiovideoOptions.transcribeAudiowhisper-1OPENAIAPIKEYOPENAIBASEURLSpoken Audio (transcribed)OPENAIAPIKEYNo transcript for : --transcribe-audiotranscribeAudio`.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Frame-extraction knobs","lvl3":""}},
|
|
3903
3903
|
{"objectID":"d0faa964a225271bc41d6c279e3e925829ac8e8828518387c39ba60d97cd363f","title":"Asking which mechanism applies","url":"/docs/features/multimodal#asking-which-mechanism-applies","content":"The capability table is exported, so a caller can ask before sending — useful\nfor choosing a provider, or for budgeting.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Asking which mechanism applies","lvl3":""}},
|
|
3904
3904
|
{"objectID":"10a8f5bf0ea5129e242eb09d5db285a4d91aac091c171050471ebd7eb528cb72","title":"Best practices","url":"/docs/features/multimodal#best-practices","content":"Ask an audio question of a Gemini provider only. Nothing on the frame\n path can hear the clip, so \"what was said\" has no answer there.\nKeep clips under 14 MB when you want native delivery. Re-encoding a\n screen recording at a lower bitrate usually gets a long clip under the\n ceiling without losing anything a model would read.\nRaise rather than for detail over time. Frames cost\n roughly 250 tokens each regardless of quality; more of them is what buys\n coverage of a clip where things change.\nLower for local runtimes. Ollama and llama.cpp hold every\n frame in the same memory as the model; four is the practical default.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Best practices","lvl3":""}},
|
|
3905
|
-
{"objectID":"7bd6ebb59bff22b19f26c8c358eb7012f8a73d4f90eed575ca136f632c6f9bc6","title":"Troubleshooting","url":"/docs/features/multimodal#troubleshooting","content":"| Symptom | Cause
|
|
3905
|
+
{"objectID":"7bd6ebb59bff22b19f26c8c358eb7012f8a73d4f90eed575ca136f632c6f9bc6","title":"Troubleshooting","url":"/docs/features/multimodal#troubleshooting","content":"| Symptom | Cause | Fix |\n| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| Model describes the file but not its content | No keyframes extracted — ffmpeg missing — and the provider takes frames | Install ffmpeg, or use a Gemini provider, which needs none |\n| in the logs | The clip failed the inline gate; the reason is on the same line | Shorten or re-encode it, or accept the frames |\n| Model cannot hear speech on a Gemini provider | The clip exceeded the inline ceiling and fell back to frames | Get the source under 14 MB, or pass |\n| produced no transcript | The warning names the reason: no audio track, unset, or audio over Whisper's 25 MB limit | Set , or split the recording |\n| Only the first seconds of a long clip are described | The frame budget was hit before the end | Raise ; the interval then spreads evenly across the whole clip |","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Troubleshooting","lvl3":""}},
|
|
3906
3906
|
{"objectID":"79318378a7df7cc816465eb447fa4bd2ba9e6344f17b0a9cf6661377bb03e7e2","title":"Combining Multiple Input Types","url":"/docs/features/multimodal#combining-multiple-input-types","content":"NeuroLink excels at combining different media types in a single request.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Combining Multiple Input Types","lvl3":""}},
|
|
3907
3907
|
{"objectID":"412f09080a5801a35250df393e2e0694602d5312e2b3d1b017fe469bf9dc0474","title":"Batch with Multimodal (CLI)","url":"/docs/features/multimodal#batch-with-multimodal-cli","content":"The command supports , , , and . The file(s) are attached identically to every prompt in the batch (a one-line notice is printed to stderr, unconditionally — it is not suppressed by , so it never corrupts output written to stdout):\n\n(auto-detect) is not available in , because it collides with the positional (the prompts-list path). Use the explicit / / / flags instead.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Batch with Multimodal (CLI)","lvl3":""}},
|
|
3908
3908
|
{"objectID":"66d907236327d676dbe6e05193c2793f7c5990dce2ad113cf078eb1ff4c73216","title":"File validation & troubleshooting (CLI)","url":"/docs/features/multimodal#file-validation-troubleshooting-cli","content":"Before any provider call, the CLI validates local file inputs across , , and :\nA path that points at a directory, doesn't exist, or can't be read (e.g. a permissions error) is rejected up front with a clear error and troubleshooting hints — no cryptic / deep in processing. This also applies to the prompts-list positional itself.\nA large file (images > 10 MB, CSV/PDF > 50 MB) prints a non-blocking warning to stderr. Like the batch attachment notice, this is unconditional — visible without and regardless of — and never mixes into stdout, so output stays valid JSON even when large-file warnings fire.\n\nThis runs even under and without API keys configured.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"File validation & troubleshooting (CLI)","lvl3":""}},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.30.0",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|
|
@@ -248,8 +248,9 @@
|
|
|
248
248
|
"pre-push": "pnpm run check:deps && pnpm run build && pnpm run test:provider-structure && pnpm run test:model-manifests",
|
|
249
249
|
"check:all": "pnpm run lint && pnpm run format --check && pnpm run validate && pnpm run validate:commit",
|
|
250
250
|
"test:file-formats": "pnpm exec tsx test/continuous-test-suite-file-formats.ts",
|
|
251
|
-
"test:multimodal": "pnpm run test:file-formats && pnpm run test:multimodal:sdk && pnpm run test:video-native",
|
|
251
|
+
"test:multimodal": "pnpm run test:file-formats && pnpm run test:multimodal:sdk && pnpm run test:video-native && pnpm run test:video-frames",
|
|
252
252
|
"test:multimodal:sdk": "pnpm exec tsx test/continuous-test-suite-multimodal-sdk.ts",
|
|
253
|
+
"test:video-frames": "pnpm exec tsx test/continuous-test-suite-video-frames.ts",
|
|
253
254
|
"test:video-native": "pnpm exec tsx test/continuous-test-suite-video-native.ts",
|
|
254
255
|
"test:mcp-result-cache": "pnpm exec tsx test/continuous-test-suite-mcp-result-cache.ts",
|
|
255
256
|
"test:mcp-breaker-resolved-errors": "pnpm exec tsx test/continuous-test-suite-mcp-breaker-resolved-errors.ts",
|