@juspay/neurolink 12.26.3 → 12.27.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/adapters/videoFormatSupport.d.ts +134 -0
- package/dist/adapters/videoFormatSupport.js +379 -0
- package/dist/browser/neurolink.min.js +376 -376
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/providers/googleAiStudio/client.js +2 -2
- package/dist/providers/googleNativeGemini3/utils.d.ts +24 -2
- package/dist/providers/googleNativeGemini3/utils.js +76 -1
- package/dist/providers/googleVertex/client.js +11 -1
- package/dist/types/file.d.ts +107 -0
- package/dist/types/generate.d.ts +12 -1
- package/dist/types/providers.d.ts +8 -1
- package/dist/utils/fileDetector.js +8 -0
- package/dist/utils/messageBuilder.js +57 -3
- package/docs-site/static/search-index.json +10 -0
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,7 @@ export { AzureTTS, AzureTTSHandler, CartesiaTTS, CartesiaTTSHandler, ElevenLabsT
|
|
|
72
72
|
export { BeatovenMusic, BeatovenMusicHandler, ElevenLabsMusic, ElevenLabsMusicHandler, LyriaMusic, LyriaMusicHandler, registerDefaultMusicHandlers, ReplicateMusic, ReplicateMusicHandler, } from "./music/index.js";
|
|
73
73
|
export { DIDAvatar, DIDAvatarHandler, HeyGenAvatar, HeyGenAvatarHandler, registerDefaultAvatarHandlers, ReplicateAvatar, ReplicateAvatarHandler, } from "./avatar/index.js";
|
|
74
74
|
export { isVertexVideoConfigured, KlingVideoHandler, registerDefaultVideoHandlers, ReplicateVideoHandler, RunwayVideoHandler, VertexVideoHandler, } from "./adapters/video/index.js";
|
|
75
|
+
export { canDeliverVideoNatively, estimateVideoTokens, getVideoProviderConfig, isNativeVideoMimeType, supportsNativeVideo, VIDEO_PROVIDER_CONFIGS, } from "./adapters/videoFormatSupport.js";
|
|
75
76
|
export { ImageGenService } from "./image-gen/ImageGenService.js";
|
|
76
77
|
export { detectImageMimeType, sniffImageMimeType, } from "./utils/imageDetection.js";
|
|
77
78
|
export { HITLManager } from "./hitl/hitlManager.js";
|
package/dist/index.js
CHANGED
|
@@ -145,6 +145,13 @@ export { BeatovenMusic, BeatovenMusicHandler, ElevenLabsMusic, ElevenLabsMusicHa
|
|
|
145
145
|
export { DIDAvatar, DIDAvatarHandler, HeyGenAvatar, HeyGenAvatarHandler, registerDefaultAvatarHandlers, ReplicateAvatar, ReplicateAvatarHandler, } from "./avatar/index.js";
|
|
146
146
|
// Video handlers
|
|
147
147
|
export { isVertexVideoConfigured, KlingVideoHandler, registerDefaultVideoHandlers, ReplicateVideoHandler, RunwayVideoHandler, VertexVideoHandler, } from "./adapters/video/index.js";
|
|
148
|
+
// Video *ingestion* capabilities — what each provider does with an attached
|
|
149
|
+
// clip. Surfaced because the answer changes what a caller should send: a
|
|
150
|
+
// provider on the native path is billed by duration and reads the audio
|
|
151
|
+
// track, one on the frame path is billed per keyframe and hears nothing, and
|
|
152
|
+
// without a way to ask, the only way to find out is to send a video and read
|
|
153
|
+
// the reply.
|
|
154
|
+
export { canDeliverVideoNatively, estimateVideoTokens, getVideoProviderConfig, isNativeVideoMimeType, supportsNativeVideo, VIDEO_PROVIDER_CONFIGS, } from "./adapters/videoFormatSupport.js";
|
|
148
155
|
// Image generation + HITL — surfaced from their dedicated barrels
|
|
149
156
|
export { ImageGenService } from "./image-gen/ImageGenService.js";
|
|
150
157
|
export { detectImageMimeType, sniffImageMimeType, } from "./utils/imageDetection.js";
|
|
@@ -665,7 +665,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
665
665
|
// reported "no image attached" on multimodal calls.
|
|
666
666
|
const currentContents = [];
|
|
667
667
|
prependConversationMessages(currentContents, options.conversationMessages);
|
|
668
|
-
const userParts = await buildUserPartsWithMultimodal(options.input, options.input.text, "[GoogleAIStudio:stream]");
|
|
668
|
+
const userParts = await buildUserPartsWithMultimodal(options.input, options.input.text, "[GoogleAIStudio:stream]", modelName);
|
|
669
669
|
currentContents.push({
|
|
670
670
|
role: "user",
|
|
671
671
|
parts: userParts,
|
|
@@ -1220,7 +1220,7 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
1220
1220
|
// legitimately reported "no image / PDF attached".
|
|
1221
1221
|
const currentContents = [];
|
|
1222
1222
|
prependConversationMessages(currentContents, options.conversationMessages);
|
|
1223
|
-
const userParts = await buildUserPartsWithMultimodal(options.input, promptText, "[GoogleAIStudio:generate]");
|
|
1223
|
+
const userParts = await buildUserPartsWithMultimodal(options.input, promptText, "[GoogleAIStudio:generate]", modelName);
|
|
1224
1224
|
currentContents.push({
|
|
1225
1225
|
role: "user",
|
|
1226
1226
|
parts: userParts,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* This module extracts the functions that are duplicated between the two
|
|
9
9
|
* providers so they can share a single implementation.
|
|
10
10
|
*/
|
|
11
|
-
import type { GenerateStopReason, ThinkingConfig, AgenticLoopOptions, ChatMessage, CollectedChunkResult, MinimalChatMessage, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, StreamChannel, VertexNativePart, GeminiMultimodalInput, MultimodalAudioEntry } from "../../types/index.js";
|
|
11
|
+
import type { GenerateStopReason, ThinkingConfig, AgenticLoopOptions, ChatMessage, CollectedChunkResult, MinimalChatMessage, NativeFunctionCall, NativeFunctionResponse, NativeToolDeclarationsResult, NativeToolsConfig, StreamChannel, VertexNativePart, GeminiMultimodalInput, MultimodalAudioEntry, MultimodalVideoEntry } from "../../types/index.js";
|
|
12
12
|
import type { Tool, ToolExecutionGuards } from "../../types/index.js";
|
|
13
13
|
/**
|
|
14
14
|
* A per-turn tool execute map that deduplicates identical tool calls.
|
|
@@ -466,4 +466,26 @@ export declare function prependConversationMessages(contents: Array<{
|
|
|
466
466
|
* summary in the text part.
|
|
467
467
|
*/
|
|
468
468
|
export declare function appendNativeAudioParts(userParts: VertexNativePart[], audioFiles: MultimodalAudioEntry[] | undefined, logPrefix?: string): Promise<void>;
|
|
469
|
-
|
|
469
|
+
/**
|
|
470
|
+
* Append video to a Gemini request as `inlineData` parts.
|
|
471
|
+
*
|
|
472
|
+
* The native request body is assembled by hand rather than taken from the AI
|
|
473
|
+
* SDK's `file` parts, so — exactly as with audio — a `{ type: "file" }` part
|
|
474
|
+
* built upstream never arrives here. Video has to be added explicitly or it
|
|
475
|
+
* is silently dropped on both Gemini front ends while the capability table
|
|
476
|
+
* says it is supported.
|
|
477
|
+
*
|
|
478
|
+
* A clip the provider will not take inline is skipped rather than sent: an
|
|
479
|
+
* oversized or unsupported `inlineData` part fails the whole request, whereas
|
|
480
|
+
* skipping leaves the caller with the metadata summary and the keyframes,
|
|
481
|
+
* which is what it had before native delivery existed.
|
|
482
|
+
*
|
|
483
|
+
* `model` is forwarded to `canDeliverVideoNatively` so a Claude-on-Vertex
|
|
484
|
+
* request — same `providerName` ("vertex") as a Gemini-on-Vertex one, routed
|
|
485
|
+
* to the native Anthropic SDK instead by `GoogleVertexProvider.isAnthropicModel`
|
|
486
|
+
* — is answered as not natively video-capable rather than inheriting Vertex's
|
|
487
|
+
* Gemini row. Optional because AI Studio's caller never has a Claude model to
|
|
488
|
+
* pass.
|
|
489
|
+
*/
|
|
490
|
+
export declare function appendNativeVideoParts(userParts: VertexNativePart[], videoFiles: MultimodalVideoEntry[] | undefined, providerName: string, model?: string, logPrefix?: string): Promise<void>;
|
|
491
|
+
export declare function buildUserPartsWithMultimodal(input: GeminiMultimodalInput | undefined, textOverride?: string, logPrefix?: string, model?: string): Promise<VertexNativePart[]>;
|
|
@@ -13,6 +13,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
13
13
|
import { extname } from "node:path";
|
|
14
14
|
import { DEFAULT_CONTEXT_GUARD_RATIO, DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, DEFAULT_WRAPUP_TIME_LEAD_MS, } from "../../core/constants.js";
|
|
15
15
|
import { needsAudioTranscode, toProviderCompatibleAudio, } from "../../adapters/audioFormatSupport.js";
|
|
16
|
+
import { canDeliverVideoNatively } from "../../adapters/videoFormatSupport.js";
|
|
16
17
|
import { logger } from "../../utils/logger.js";
|
|
17
18
|
import { guardToolExecutor } from "../../core/toolExecutionGuards.js";
|
|
18
19
|
import { resolveSamplingParams } from "../../models/modelRegistry.js";
|
|
@@ -1376,7 +1377,73 @@ export async function appendNativeAudioParts(userParts, audioFiles, logPrefix =
|
|
|
1376
1377
|
logger.debug(`${logPrefix} Added native audio part for ${base} (${compatible.mimeType})`);
|
|
1377
1378
|
}
|
|
1378
1379
|
}
|
|
1379
|
-
|
|
1380
|
+
/**
|
|
1381
|
+
* Append video to a Gemini request as `inlineData` parts.
|
|
1382
|
+
*
|
|
1383
|
+
* The native request body is assembled by hand rather than taken from the AI
|
|
1384
|
+
* SDK's `file` parts, so — exactly as with audio — a `{ type: "file" }` part
|
|
1385
|
+
* built upstream never arrives here. Video has to be added explicitly or it
|
|
1386
|
+
* is silently dropped on both Gemini front ends while the capability table
|
|
1387
|
+
* says it is supported.
|
|
1388
|
+
*
|
|
1389
|
+
* A clip the provider will not take inline is skipped rather than sent: an
|
|
1390
|
+
* oversized or unsupported `inlineData` part fails the whole request, whereas
|
|
1391
|
+
* skipping leaves the caller with the metadata summary and the keyframes,
|
|
1392
|
+
* which is what it had before native delivery existed.
|
|
1393
|
+
*
|
|
1394
|
+
* `model` is forwarded to `canDeliverVideoNatively` so a Claude-on-Vertex
|
|
1395
|
+
* request — same `providerName` ("vertex") as a Gemini-on-Vertex one, routed
|
|
1396
|
+
* to the native Anthropic SDK instead by `GoogleVertexProvider.isAnthropicModel`
|
|
1397
|
+
* — is answered as not natively video-capable rather than inheriting Vertex's
|
|
1398
|
+
* Gemini row. Optional because AI Studio's caller never has a Claude model to
|
|
1399
|
+
* pass.
|
|
1400
|
+
*/
|
|
1401
|
+
export async function appendNativeVideoParts(userParts, videoFiles, providerName, model, logPrefix = "[GeminiNative]") {
|
|
1402
|
+
if (!videoFiles || videoFiles.length === 0) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
// Tracks source bytes already committed to this request's shared inline
|
|
1406
|
+
// budget. This is the sole place video ceiling enforcement happens: the AI
|
|
1407
|
+
// SDK `file`-part assembly in messageBuilder.ts never delivers video for
|
|
1408
|
+
// either Gemini provider class (both own their request assembly end to
|
|
1409
|
+
// end), so a duplicate of this check used to sit there too — permanently
|
|
1410
|
+
// inert and unreachable by any test — and has been removed rather than
|
|
1411
|
+
// left as a second, silently-driftable copy of the same logic.
|
|
1412
|
+
//
|
|
1413
|
+
// Seeded from the PDF, image and audio `inlineData` parts the caller has
|
|
1414
|
+
// already pushed onto `userParts` before this runs, not just from sibling
|
|
1415
|
+
// video clips: the 20MB Gemini request ceiling is shared by every inline
|
|
1416
|
+
// part, so a clip that would fit alone can still overflow the request
|
|
1417
|
+
// once those other parts are counted. `data` is base64, which costs 4
|
|
1418
|
+
// bytes per 3 of source — decode the length back to source bytes so this
|
|
1419
|
+
// total stays comparable to `video.buffer.length` below.
|
|
1420
|
+
let committedVideoBytes = userParts.reduce((total, part) => {
|
|
1421
|
+
if ("inlineData" in part) {
|
|
1422
|
+
return total + Math.floor((part.inlineData.data.length * 3) / 4);
|
|
1423
|
+
}
|
|
1424
|
+
return total;
|
|
1425
|
+
}, 0);
|
|
1426
|
+
for (const video of videoFiles) {
|
|
1427
|
+
// Split on both separators: a Windows-style name reaching a POSIX host
|
|
1428
|
+
// would otherwise keep its whole path in the log line.
|
|
1429
|
+
const base = video.filename.split(/[\\/]/).pop() ?? video.filename;
|
|
1430
|
+
const decision = canDeliverVideoNatively(providerName, video, committedVideoBytes, model);
|
|
1431
|
+
if (!decision.deliver) {
|
|
1432
|
+
logger.warn(`${logPrefix} Sending keyframes instead of the clip for ${base}: ` +
|
|
1433
|
+
`${decision.reason}.`);
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
committedVideoBytes += video.buffer.length;
|
|
1437
|
+
userParts.push({
|
|
1438
|
+
inlineData: {
|
|
1439
|
+
mimeType: video.mimeType,
|
|
1440
|
+
data: video.buffer.toString("base64"),
|
|
1441
|
+
},
|
|
1442
|
+
});
|
|
1443
|
+
logger.debug(`${logPrefix} Added native video part for ${base} (${video.mimeType})`);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
export async function buildUserPartsWithMultimodal(input, textOverride, logPrefix = "[GeminiNative]", model) {
|
|
1380
1447
|
const text = typeof textOverride === "string" ? textOverride : (input?.text ?? "");
|
|
1381
1448
|
const parts = [{ text }];
|
|
1382
1449
|
if (input?.pdfFiles && input.pdfFiles.length > 0) {
|
|
@@ -1483,5 +1550,13 @@ export async function buildUserPartsWithMultimodal(input, textOverride, logPrefi
|
|
|
1483
1550
|
// Vertex client left this front end advertising native audio via
|
|
1484
1551
|
// NATIVE_AUDIO_PROVIDERS and then dropping the bytes on the floor.
|
|
1485
1552
|
await appendNativeAudioParts(parts, input?.nativeAudioFiles, logPrefix);
|
|
1553
|
+
// `google-ai-studio` rather than the caller's provider string: this helper
|
|
1554
|
+
// is the AI Studio / Gemini 3 assembly path, and the capability table is
|
|
1555
|
+
// keyed by provider name. Passing a name the table does not know would make
|
|
1556
|
+
// every clip fall back to keyframes on the one front end that can watch it.
|
|
1557
|
+
// `model` is threaded through for signature parity with the Vertex call
|
|
1558
|
+
// site; AI Studio never hosts a Claude model, so it makes no delivery
|
|
1559
|
+
// difference here, only in `canDeliverVideoNatively`'s Vertex branch.
|
|
1560
|
+
await appendNativeVideoParts(parts, input?.nativeVideoFiles, "google-ai-studio", model, logPrefix);
|
|
1486
1561
|
return parts;
|
|
1487
1562
|
}
|
|
@@ -6,7 +6,7 @@ import path from "path";
|
|
|
6
6
|
import { ErrorCategory, ErrorSeverity, } from "../../constants/enums.js";
|
|
7
7
|
import { BaseProvider } from "../../core/baseProvider.js";
|
|
8
8
|
import { unwrapImagePayload } from "../../adapters/imageFormatSupport.js";
|
|
9
|
-
import { appendNativeAudioParts } from "../googleNativeGemini3/utils.js";
|
|
9
|
+
import { appendNativeAudioParts, appendNativeVideoParts, } from "../googleNativeGemini3/utils.js";
|
|
10
10
|
import { getMimeTypeForExtension } from "../../processors/config/mimeConstants.js";
|
|
11
11
|
import { DEFAULT_GEMINI_STREAM_TIMEOUT_MS, DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, GLOBAL_LOCATION_MODELS, resolveToolTimeoutMs, TOOL_STORAGE_TIMEOUT_MS, } from "../../core/constants.js";
|
|
12
12
|
import { resolveRequestKind } from "../../core/resolveRequestKind.js";
|
|
@@ -1302,6 +1302,11 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1302
1302
|
});
|
|
1303
1303
|
}
|
|
1304
1304
|
}
|
|
1305
|
+
// Video last, after images: `appendNativeVideoParts` seeds its
|
|
1306
|
+
// request-wide byte budget from whatever `inlineData` parts already sit
|
|
1307
|
+
// in `userParts`, so the images above must be pushed before it runs or
|
|
1308
|
+
// their bytes are invisible to the ceiling check that follows.
|
|
1309
|
+
await appendNativeVideoParts(userParts, multimodalInput?.nativeVideoFiles, "vertex", modelName, "[GoogleVertex]");
|
|
1305
1310
|
// Prepend prior conversation turns before the current user message so
|
|
1306
1311
|
// multi-turn callers (memory, loop REPL, agent flows) actually carry
|
|
1307
1312
|
// context. Without this, the native Vertex Gemini stream rebuilt the
|
|
@@ -2114,6 +2119,11 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2114
2119
|
});
|
|
2115
2120
|
}
|
|
2116
2121
|
}
|
|
2122
|
+
// Video last, after images: `appendNativeVideoParts` seeds its
|
|
2123
|
+
// request-wide byte budget from whatever `inlineData` parts already sit
|
|
2124
|
+
// in `userParts`, so the images above must be pushed before it runs or
|
|
2125
|
+
// their bytes are invisible to the ceiling check that follows.
|
|
2126
|
+
await appendNativeVideoParts(userParts, multimodalInput?.nativeVideoFiles, "vertex", modelName, "[GoogleVertex]");
|
|
2117
2127
|
// Prepend prior conversation turns before the current user message so
|
|
2118
2128
|
// multi-turn callers (memory, loop REPL, agent flows) carry context
|
|
2119
2129
|
// into native Vertex Gemini generate. Without this, every call started
|
package/dist/types/file.d.ts
CHANGED
|
@@ -62,6 +62,105 @@ export type MultimodalAudioEntry = {
|
|
|
62
62
|
/** Detected MIME type of `buffer`. */
|
|
63
63
|
mimeType: string;
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* One video file destined for native delivery to a provider.
|
|
67
|
+
*
|
|
68
|
+
* Mirrors {@link MultimodalAudioEntry}: the bytes travel rather than the path,
|
|
69
|
+
* because whether a video is sent at all is decided per provider, after
|
|
70
|
+
* detection has already read the file.
|
|
71
|
+
*
|
|
72
|
+
* `durationSec` rides along because the native-delivery gate is expressed in
|
|
73
|
+
* seconds as well as bytes, and re-probing the container at dispatch time
|
|
74
|
+
* would mean a second ffprobe run for something the processor already
|
|
75
|
+
* measured. It is optional: probing can fail (no ffmpeg, an exotic container),
|
|
76
|
+
* and an unknown duration must not by itself disqualify a clip that is
|
|
77
|
+
* comfortably under the size ceiling.
|
|
78
|
+
*/
|
|
79
|
+
export type MultimodalVideoEntry = {
|
|
80
|
+
/** Raw video bytes, as detected. */
|
|
81
|
+
buffer: Buffer;
|
|
82
|
+
/** Display name; may be a full path, so log only its basename. */
|
|
83
|
+
filename: string;
|
|
84
|
+
/** Detected MIME type of `buffer`. */
|
|
85
|
+
mimeType: string;
|
|
86
|
+
/** Clip length in seconds, when the processor was able to measure it. */
|
|
87
|
+
durationSec?: number;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* How one provider handles an attached video.
|
|
91
|
+
*
|
|
92
|
+
* The table lives in `adapters/videoFormatSupport.ts`; this is its row shape.
|
|
93
|
+
* `apiType` names the mechanism that is actually implemented, not the one a
|
|
94
|
+
* provider theoretically offers — Gemini also exposes a resumable Files API
|
|
95
|
+
* for clips beyond the inline ceiling, and until that is wired up calling this
|
|
96
|
+
* row "files-api" would misdescribe what happens to a 200 MB upload.
|
|
97
|
+
*/
|
|
98
|
+
export type VideoProviderConfig = {
|
|
99
|
+
/** Whether raw video bytes can be handed to this provider at all. */
|
|
100
|
+
readonly supportsNativeVideo: boolean;
|
|
101
|
+
/** How the video reaches the model. */
|
|
102
|
+
readonly apiType: "inline" | "frame-extraction";
|
|
103
|
+
/**
|
|
104
|
+
* Inline source-byte budget for the whole request, in MB. A clip is
|
|
105
|
+
* delivered natively only while its bytes plus every inline part already in
|
|
106
|
+
* the request stay within it; otherwise it falls back to keyframes.
|
|
107
|
+
* Meaningless when `supportsNativeVideo` is false, and set to 0 there rather
|
|
108
|
+
* than to a number that reads like a real limit.
|
|
109
|
+
*/
|
|
110
|
+
readonly maxSizeMB: number;
|
|
111
|
+
/** Longest clip accepted natively, in seconds. 0 when not applicable. */
|
|
112
|
+
readonly maxDurationSec: number;
|
|
113
|
+
/** Whether the provider hears the video's audio track as well as seeing it. */
|
|
114
|
+
readonly supportsAudio: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Keyframe budget to aim for when this provider gets frames instead of the
|
|
117
|
+
* video. Advisory: an explicit `videoOptions.frames` always wins.
|
|
118
|
+
*/
|
|
119
|
+
readonly recommendedFrameCount: number;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Media collected during file detection that a provider may be able to
|
|
123
|
+
* consume directly, rather than as a text summary.
|
|
124
|
+
*
|
|
125
|
+
* Grouped rather than passed as two more positional parameters: the message
|
|
126
|
+
* converters already take text, images, PDFs, provider and model, and each
|
|
127
|
+
* new modality added one more argument to a call nobody could read. A bag
|
|
128
|
+
* also means the next modality is a field, not another signature change at
|
|
129
|
+
* every call site.
|
|
130
|
+
*/
|
|
131
|
+
export type NativeMediaAttachments = {
|
|
132
|
+
readonly audio?: MultimodalAudioEntry[];
|
|
133
|
+
readonly video?: MultimodalVideoEntry[];
|
|
134
|
+
/**
|
|
135
|
+
* Transcode target for an incompatible image (see
|
|
136
|
+
* `GenerateOptions.imageOptions`). Not itself a native attachment, but
|
|
137
|
+
* bundled here for the same reason audio and video are: it is one more
|
|
138
|
+
* per-request option the message converters need at the same call sites.
|
|
139
|
+
*/
|
|
140
|
+
readonly outputFormat?: VisionImageOutputFormat;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Outcome of asking whether one video may be handed to one provider as bytes.
|
|
144
|
+
*
|
|
145
|
+
* A plain boolean collapsed "this provider never watches video" with "this
|
|
146
|
+
* provider would have, but the clip is 400 MB" — and the two want different
|
|
147
|
+
* log lines and different advice. `reason` is populated exactly when
|
|
148
|
+
* `deliver` is false, and is phrased for a user to read.
|
|
149
|
+
*
|
|
150
|
+
* The accepting arm declares `reason?: undefined` rather than omitting the
|
|
151
|
+
* field. Not decoration: `build:react-hooks` type-checks this graph without
|
|
152
|
+
* `--strict`, and there a negated boolean-literal discriminant does not
|
|
153
|
+
* narrow, so `decision.reason` inside `if (!decision.deliver)` fails to
|
|
154
|
+
* compile unless the property exists on both arms. Declaring it keeps the
|
|
155
|
+
* union exact under strict and compilable under both.
|
|
156
|
+
*/
|
|
157
|
+
export type VideoDeliveryDecision = {
|
|
158
|
+
readonly deliver: true;
|
|
159
|
+
readonly reason?: undefined;
|
|
160
|
+
} | {
|
|
161
|
+
readonly deliver: false;
|
|
162
|
+
readonly reason: string;
|
|
163
|
+
};
|
|
65
164
|
/**
|
|
66
165
|
* Broad category a file format belongs to, as a human would name it.
|
|
67
166
|
*
|
|
@@ -190,6 +289,14 @@ export type FileProcessingResult = {
|
|
|
190
289
|
hasImages?: boolean;
|
|
191
290
|
frameCount?: number;
|
|
192
291
|
hasKeyframes?: boolean;
|
|
292
|
+
/**
|
|
293
|
+
* Clip length in seconds, when the processor could measure it.
|
|
294
|
+
*
|
|
295
|
+
* Surfaced here so the native-delivery gate does not have to re-probe a
|
|
296
|
+
* container the processor has already opened. Absent when probing failed
|
|
297
|
+
* — an unknown duration, not a zero-length clip.
|
|
298
|
+
*/
|
|
299
|
+
durationSec?: number;
|
|
193
300
|
};
|
|
194
301
|
};
|
|
195
302
|
/**
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { AvatarOptions, AvatarResult } from "./avatar.js";
|
|
|
17
17
|
import type { MusicOptions, MusicResult } from "./music.js";
|
|
18
18
|
import type { StandardRecord, ValidationSchema, ZodUnknownSchema } from "./aliases.js";
|
|
19
19
|
import type { NeurolinkCredentials } from "./providers.js";
|
|
20
|
-
import type { CSVProcessorOptions, FileWithMetadata, MultimodalAudioEntry, VisionImageOutputFormat } from "./file.js";
|
|
20
|
+
import type { CSVProcessorOptions, FileWithMetadata, MultimodalAudioEntry, MultimodalVideoEntry, VisionImageOutputFormat } from "./file.js";
|
|
21
21
|
import type { WorkflowConfig } from "./workflow.js";
|
|
22
22
|
import type { Schema, Tool, ToolChoice } from "./tools.js";
|
|
23
23
|
import type { StepResult, LanguageModel } from "./providers.js";
|
|
@@ -68,6 +68,17 @@ export type GenerateOptions = {
|
|
|
68
68
|
*/
|
|
69
69
|
nativeAudioFiles?: MultimodalAudioEntry[];
|
|
70
70
|
videoFiles?: Array<Buffer | string>;
|
|
71
|
+
/**
|
|
72
|
+
* Video collected during file detection, carried through to a provider
|
|
73
|
+
* that can watch it. Populated by detection rather than by callers.
|
|
74
|
+
*
|
|
75
|
+
* Separate from `videoFiles` above, which is the caller-facing input that
|
|
76
|
+
* yields a metadata summary plus extracted keyframes. This one carries the
|
|
77
|
+
* decoded bytes forward so a provider that accepts video receives the clip
|
|
78
|
+
* itself; providers that cannot fall back to the frames and this is
|
|
79
|
+
* ignored.
|
|
80
|
+
*/
|
|
81
|
+
nativeVideoFiles?: MultimodalVideoEntry[];
|
|
71
82
|
files?: Array<Buffer | string | FileWithMetadata>;
|
|
72
83
|
content?: Content[];
|
|
73
84
|
/**
|
|
@@ -7,7 +7,7 @@ import { AIProviderName, AnthropicModels, BedrockModels, DeepSeekModels, GoogleA
|
|
|
7
7
|
import type { ValidationSchema } from "./aliases.js";
|
|
8
8
|
import type { DecisionLimits, DecisionRequest, DecisionResult } from "./decision.js";
|
|
9
9
|
import type { EnhancedGenerateResult, TextGenerationOptions } from "./generate.js";
|
|
10
|
-
import type { MultimodalAudioEntry } from "./file.js";
|
|
10
|
+
import type { MultimodalAudioEntry, MultimodalVideoEntry } from "./file.js";
|
|
11
11
|
import type { StreamOptions, StreamResult } from "./stream.js";
|
|
12
12
|
import type { ProviderError, ProviderErrorRule } from "./errors.js";
|
|
13
13
|
import type { ExternalMCPToolInfo } from "./externalMcp.js";
|
|
@@ -2206,6 +2206,13 @@ export type GeminiMultimodalInput = {
|
|
|
2206
2206
|
* are already-materialised bytes with a resolved mime type.
|
|
2207
2207
|
*/
|
|
2208
2208
|
nativeAudioFiles?: MultimodalAudioEntry[];
|
|
2209
|
+
/**
|
|
2210
|
+
* Video collected during file detection, carried through to the native
|
|
2211
|
+
* request as `inlineData`. Distinct from the user-facing `videoFiles`: these
|
|
2212
|
+
* are already-materialised bytes with a resolved mime type and, where it
|
|
2213
|
+
* could be measured, the clip's duration.
|
|
2214
|
+
*/
|
|
2215
|
+
nativeVideoFiles?: MultimodalVideoEntry[];
|
|
2209
2216
|
};
|
|
2210
2217
|
/**
|
|
2211
2218
|
* Internal helpers used by the conversation-history builder in
|
|
@@ -1132,6 +1132,14 @@ export class FileDetector {
|
|
|
1132
1132
|
...detection.metadata,
|
|
1133
1133
|
frameCount: videoResult.data.frameCount,
|
|
1134
1134
|
hasKeyframes: videoResult.data.hasKeyframes,
|
|
1135
|
+
// Carried so native delivery can apply its duration ceiling
|
|
1136
|
+
// without opening the container a second time. The processor
|
|
1137
|
+
// reports 0 when nothing could probe it; that is "unknown", not a
|
|
1138
|
+
// zero-length clip, so it is left off rather than passed through
|
|
1139
|
+
// as a number a ceiling check would happily accept.
|
|
1140
|
+
...(videoResult.data.metadata.duration > 0
|
|
1141
|
+
? { durationSec: videoResult.data.metadata.duration }
|
|
1142
|
+
: {}),
|
|
1135
1143
|
},
|
|
1136
1144
|
};
|
|
1137
1145
|
}
|
|
@@ -16,6 +16,7 @@ import { ErrorFactory, NeuroLinkError, withTimeout } from "./errorHandling.js";
|
|
|
16
16
|
import { FileDetector } from "./fileDetector.js";
|
|
17
17
|
import { detectIsoBmffImageMimeType } from "./isoBmff.js";
|
|
18
18
|
import { needsAudioTranscode, supportsNativeAudio, toProviderCompatibleAudio, } from "../adapters/audioFormatSupport.js";
|
|
19
|
+
import { supportsNativeVideo } from "../adapters/videoFormatSupport.js";
|
|
19
20
|
import { getImageCache } from "./imageCache.js";
|
|
20
21
|
import { ImageProcessor, imageUtils } from "./imageProcessor.js";
|
|
21
22
|
import { logger } from "./logger.js";
|
|
@@ -697,6 +698,26 @@ async function appendDetectedFileResult(result, file, options) {
|
|
|
697
698
|
];
|
|
698
699
|
logger.info(`[FileDetector] Added ${result.images.length} video keyframes as images`);
|
|
699
700
|
}
|
|
701
|
+
// Carry the bytes forward alongside the summary and the frames, the same
|
|
702
|
+
// way audio does. Whether the clip is sent is decided later, per provider:
|
|
703
|
+
// one that can watch receives it, one that cannot still gets the keyframes
|
|
704
|
+
// and is no worse off than before. This matters most where the frames are
|
|
705
|
+
// absent — ffmpeg is optional, and without it the summary was everything
|
|
706
|
+
// the model saw.
|
|
707
|
+
const videoBytes = await readFileInputBytes(file);
|
|
708
|
+
if (videoBytes) {
|
|
709
|
+
options.input.nativeVideoFiles = [
|
|
710
|
+
...(options.input.nativeVideoFiles || []),
|
|
711
|
+
{
|
|
712
|
+
buffer: videoBytes,
|
|
713
|
+
filename,
|
|
714
|
+
mimeType: result.mimeType,
|
|
715
|
+
...(typeof result.metadata?.durationSec === "number"
|
|
716
|
+
? { durationSec: result.metadata.durationSec }
|
|
717
|
+
: {}),
|
|
718
|
+
},
|
|
719
|
+
];
|
|
720
|
+
}
|
|
700
721
|
logger.info(`[FileDetector] ✅ Video: ${filename}`);
|
|
701
722
|
}
|
|
702
723
|
else if (result.type === "audio") {
|
|
@@ -1376,8 +1397,14 @@ export async function buildMultimodalMessagesArray(options, provider, model) {
|
|
|
1376
1397
|
// summary survives.
|
|
1377
1398
|
const hasNativeAudio = (inp.nativeAudioFiles?.length ?? 0) > 0 &&
|
|
1378
1399
|
supportsNativeAudio(provider, model);
|
|
1379
|
-
//
|
|
1380
|
-
|
|
1400
|
+
// Video that is to be delivered natively is multimodal for exactly the
|
|
1401
|
+
// reason audio is: it becomes a non-text part. Gated on the provider
|
|
1402
|
+
// accepting video so one that only ever sees keyframes keeps the cheaper
|
|
1403
|
+
// shape it already had.
|
|
1404
|
+
const hasNativeVideo = (inp.nativeVideoFiles?.length ?? 0) > 0 &&
|
|
1405
|
+
supportsNativeVideo(provider, model);
|
|
1406
|
+
// If no images, PDFs, audio or video, use standard message building and convert to MultimodalChatMessage[]
|
|
1407
|
+
if (!hasImages && !hasPDFs && !hasNativeAudio && !hasNativeVideo) {
|
|
1381
1408
|
// #289: CSV content[] items don't need vision, so they never reach the
|
|
1382
1409
|
// multimodal converter below — process them into the prompt text here
|
|
1383
1410
|
// (otherwise a `content: [{type:"csv"}]`-only request silently drops it).
|
|
@@ -1495,7 +1522,11 @@ export async function buildMultimodalMessagesArray(options, provider, model) {
|
|
|
1495
1522
|
// audio-only turn fell through to the plain-text branch below, so the
|
|
1496
1523
|
// recording was dropped and only its metadata summary — already folded
|
|
1497
1524
|
// into `text` — ever reached the model.
|
|
1498
|
-
(inp.nativeAudioFiles?.length ?? 0) > 0
|
|
1525
|
+
(inp.nativeAudioFiles?.length ?? 0) > 0 ||
|
|
1526
|
+
// Same for a video-only turn. On a machine without ffmpeg there are no
|
|
1527
|
+
// keyframes to put in `images`, so this clause is the only thing keeping
|
|
1528
|
+
// the clip out of the plain-text branch.
|
|
1529
|
+
(inp.nativeVideoFiles?.length ?? 0) > 0) {
|
|
1499
1530
|
userContent = await convertMultimodalToProviderFormat(inp.text ?? "", inp.images || [], pdfFiles, provider, model, {
|
|
1500
1531
|
audioFiles: inp.nativeAudioFiles ?? [],
|
|
1501
1532
|
outputFormat: options.imageOptions?.outputFormat,
|
|
@@ -1597,6 +1628,13 @@ async function convertContentToProviderFormat(content, provider, model, pdfOptio
|
|
|
1597
1628
|
// change exists to stop, reached through a different branch. Gated on the
|
|
1598
1629
|
// provider accepting audio so one that cannot keeps the cheaper plain-text
|
|
1599
1630
|
// shape rather than an array carrying a part it will ignore.
|
|
1631
|
+
//
|
|
1632
|
+
// Video does not get the same treatment here: native delivery for the two
|
|
1633
|
+
// Gemini provider classes never reaches this function with any consequence
|
|
1634
|
+
// (both fully own their own request assembly — see the module comment on
|
|
1635
|
+
// `appendNativeVideoParts` in googleNativeGemini3/utils.ts, the actual and
|
|
1636
|
+
// only place a video clip is attached), so a `deliversVideo` flag here would
|
|
1637
|
+
// gate nothing but a value nobody reads.
|
|
1600
1638
|
const deliversAudio = audioFiles.length > 0 && supportsNativeAudio(provider, model);
|
|
1601
1639
|
const hasMultimodal = imageContent.length > 0 || pdfContent.length > 0 || deliversAudio;
|
|
1602
1640
|
// Validate that we have at least some content
|
|
@@ -2252,6 +2290,22 @@ mediaOptions = {}) {
|
|
|
2252
2290
|
`${compatible.converted ? ` (converted to ${compatible.mimeType})` : ""}`);
|
|
2253
2291
|
}
|
|
2254
2292
|
}
|
|
2293
|
+
// Video is deliberately not attached here. This function's output — an AI
|
|
2294
|
+
// SDK `content` array — only ever reaches a real request for providers that
|
|
2295
|
+
// call `BaseProvider`'s shared message building unmodified, and neither
|
|
2296
|
+
// Gemini provider class does: `GoogleAIStudioProvider.generate()` and
|
|
2297
|
+
// `GoogleVertexProvider.generate()` are full overrides that never call
|
|
2298
|
+
// `super.generate()`, and `BaseProvider.stream()`'s early multimodal check
|
|
2299
|
+
// builds a `messages` value only to test `hasVideoFrames()` on it before
|
|
2300
|
+
// discarding it — so a video part built here never travels anywhere. The
|
|
2301
|
+
// one delivery mechanism both Gemini front ends actually use is the
|
|
2302
|
+
// hand-built `inlineData` request in `appendNativeVideoParts`
|
|
2303
|
+
// (googleNativeGemini3/utils.ts), which carries its own aggregate-ceiling
|
|
2304
|
+
// enforcement. An earlier version of this function duplicated that
|
|
2305
|
+
// ceiling check here too, permanently inert and with no test able to
|
|
2306
|
+
// reach it — that duplicate has been removed rather than left as a second
|
|
2307
|
+
// place the same bug could be introduced without either delivery or a
|
|
2308
|
+
// failing test ever showing it.
|
|
2255
2309
|
// Check if provider supports native PDF processing
|
|
2256
2310
|
const supportsNativePDF = PDFProcessor.supportsNativePDF(provider);
|
|
2257
2311
|
if (supportsNativePDF) {
|
|
@@ -3874,6 +3874,8 @@
|
|
|
3874
3874
|
{"objectID":"73d7935973c0a686585af87403eac8b065162f05b5a7c956894d5cc17807712d","title":"PDF Documents","url":"/docs/features/multimodal#pdf-documents","content":"| Provider | Supported | Max Size | Max Pages | Processing Mode | Notes |\n| --------------------- | --------- | -------- | --------- | ---------------- | --------------------------------------- |\n| Google Vertex AI | ✅ | 5 MB | 100 | Native PDF | Best for document analysis |\n| Anthropic | ✅ | 5 MB | 100 | Native PDF | Claude excels at document understanding |\n| AWS Bedrock | ✅ | 5 MB | 100 | Native PDF | Via Claude models |\n| Google AI Studio | ✅ | 2000 MB | 100 | Native PDF | Handles very large files |\n| OpenAI | ✅ | 10 MB | 100 | Files API | , , |\n| Azure OpenAI | ✅ | 10 MB | 100 | Files API | Uses OpenAI Files API |\n| LiteLLM | ✅ | 10 MB | 100 | Proxy | Depends on upstream model |\n| OpenAI Compatible | ✅ | 10 MB | 100 | Varies | Server-dependent |\n| Mistral | ✅ | 10 MB | 100 | Native PDF | Native support |\n| Hugging Face | ✅ | 10 MB | 100 | Model-dependent | Varies by model |\n| Ollama | ❌ | - | - | - | Not supported |\n| OpenRouter | ⚠️ | Varies | Varies | Depends on model | Route-dependent |\n| AWS SageMaker | ❌ | - | - | - | Not supported |","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"PDF Documents","lvl3":""}},
|
|
3875
3875
|
{"objectID":"96bf471d5383c56e1f0144483b12e8a6f6c221ab852ed86b42f9ea057e68a91d","title":"CSV/Spreadsheet Data","url":"/docs/features/multimodal#csvspreadsheet-data","content":"| Provider | Supported | Max Rows | Format Options | Notes |\n| ----------------- | --------- | -------- | ------------------- | ------------------------------------- |\n| All Providers | ✅ | 10,000 | raw, json, markdown | Universal support - processed as text |\n\nCSV support works with all providers because files are converted to text before sending to the AI model. The file is parsed and formatted (raw CSV, JSON, or Markdown table) before inclusion in the prompt.\n\nFormat Recommendations:\nRaw format - Best for large files (minimal token usage)\nJSON format - Best for structured data processing\nMarkdown format - Best for small datasets (\\<100 rows), readable tables","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"CSV/Spreadsheet Data","lvl3":""}},
|
|
3876
3876
|
{"objectID":"b52c712e324d5d40df527e8a5877d9ea0ae63d2ae80bd022cfbaffc9c3c91a82","title":"Audio Input","url":"/docs/features/multimodal#audio-input","content":"| Provider | Native Audio | Transcription | Real-time | Max Duration | Notes |\n| -------------------- | ------------ | ------------- | --------- | ------------ | ----------------------------------- |\n| Google AI Studio | ✅ | ✅ | ✅ | 1 hour | Best for real-time voice |\n| Google Vertex AI | ✅ | ✅ | ✅ | 1 hour | Native Gemini audio support |\n| OpenAI | ❌ | ✅ Whisper | ❌ | 25 MB | Excellent transcription accuracy |\n| Azure OpenAI | ❌ | ✅ Whisper | ❌ | 25 MB | Via Whisper integration |\n| Anthropic | ❌ | Via fallback | ❌ | - | Uses transcription approach |\n| AWS Bedrock | ❌ | Via fallback | ❌ | - | Uses transcription approach |\n| Others | ❌ | Via fallback | ❌ | - | Audio transcribed before processing |\n\nFor comprehensive audio documentation, see the Audio Input Guide.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Audio Input","lvl3":""}},
|
|
3877
|
+
{"objectID":"6c75d56315cf5bcec368645e07a84c0b2b067c8052da1028e9deb65d16aab9ee","title":"Video","url":"/docs/features/multimodal#video","content":"Two mechanisms, and which one a provider gets changes what it can answer.\n\nNative (inline) — the video file itself travels in the request. The model\nsees continuous motion and hears the audio track. Only Google's Gemini front\nends accept this today.\n\nFrame extraction — ffmpeg pulls keyframes at intervals and they are\nattached as ordinary images, alongside a metadata summary in the prompt text.\nThe model sees a handful of stills and hears nothing.\n\n| Provider | Mechanism | Inline ceiling | Audio track | Default frame budget |\n| ------------------------------------ | ---------------- | -------------- | ----------- | -------------------- |\n| Google AI Studio | Native (inline) | 14 MB | ✅ heard | 16 (fallback only) |\n| Google Vertex AI (Gemini models) | Native (inline) | 14 MB | ✅ heard | 16 (fallback only) |\n| OpenAI / Azure OpenAI | Frame extraction | – | ❌ | 8 |\n| Anthropic / AWS Bedrock | Frame extraction | – | ❌ | 8 |\n| Mistral | Frame extraction | – | ❌ | 8 |\n| LiteLLM / OpenRouter | Frame extraction | – | ❌ | 8 |\n| Ollama / llama.cpp | Frame extraction | – | ❌ | 4 |\n\nThe 14 MB ceiling is one shared source-byte budget for the whole request,\nnot a separate per-clip limit. Before a clip ships natively, its bytes are\nadded to the source bytes of every inline part already in the request, and\nthe clip is delivered only if the total stays within 14 MB. Those parts\ninclude images (keyframes among them), PDFs, audio and any clip accepted\nearlier. With nothing else attached, that reduces to the clip's own size.\nThe budget is in source bytes because inline data travels as base64 in a\nJSON body, which costs four bytes per th","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Video","lvl3":""}},
|
|
3878
|
+
{"objectID":"623b369433add73c32c5d31532a82b33ea81350ec9e96ead3a7dc6262db5c09f","title":"Supported containers","url":"/docs/features/multimodal#supported-containers","content":", , , , , , , , and\nthe other formats in the detector's video registry are all accepted for frame\nextraction. Inline delivery is narrower — it is limited to the containers\nGemini reads directly (mp4, mpeg, mov, avi, flv, webm, wmv, 3gpp) — and a\ncontainer outside that set falls back to keyframes rather than being\nre-encoded. Transcoding a long recording mid-request costs minutes of CPU for\na payload that would usually breach the inline ceiling anyway.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Supported containers","lvl3":""}},
|
|
3877
3879
|
{"objectID":"0a6aa016f95cc342e4dd2a5dd050bd8ded76685337214426811889d4fdd671b7","title":"Quick Start","url":"/docs/features/multimodal#quick-start","content":"CLI:\n\n`bash","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Quick Start","lvl3":""}},
|
|
3878
3880
|
{"objectID":"de245f012caaeb0388d4424c7d969aed288374590e5c1f58a0016a1ae1996e17","title":"Single image","url":"/docs/features/multimodal#single-image","content":"npx @juspay/neurolink generate \"Describe this interface\" \\\n --image ./designs/dashboard.png --provider google-ai","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Single image","lvl3":""}},
|
|
3879
3881
|
{"objectID":"05b1002029479df0ee1386f3f8434822765351def1e4c18cf8c6349b373418fa","title":"Remote URL","url":"/docs/features/multimodal#remote-url","content":"npx @juspay/neurolink generate \"Analyze this diagram\" \\\n --image https://example.com/architecture.png --provider openai","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Remote URL","lvl3":""}},
|
|
@@ -3892,6 +3894,14 @@
|
|
|
3892
3894
|
{"objectID":"0ffdeaf2a4d433739fb1f96c6487b2e35e254c4459ead6ecbfd3dcb3a98de8b3","title":"Explicit CSV with options","url":"/docs/features/multimodal#explicit-csv-with-options","content":"npx @juspay/neurolink generate \"Summarize data\" \\\n --csv ./data.csv \\\n --csv-max-rows 500 \\\n --csv-format raw\ntypescript\n// Auto-detect (recommended)\nawait neurolink.generate({\n input: {\n text: \"Analyze this sales data\",\n files: [\"./sales.csv\"], // Auto-detected as CSV\n },\n});\n\n// Explicit CSV with options\nawait neurolink.generate({\n input: {\n text: \"Compare quarterly data\",\n csvFiles: [\"./q1.csv\", \"./q2.csv\"],\n },\n csvOptions: {\n maxRows: 1000,\n formatStyle: \"json\", // or \"raw\", \"markdown\"\n },\n});\n`","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Explicit CSV with options","lvl3":""}},
|
|
3893
3895
|
{"objectID":"93535721732536c013e0204b266f2a131be12a7745f6c88a6e61cdeb76704728","title":"CSV Format Options","url":"/docs/features/multimodal#csv-format-options","content":"Three format styles:\nRaw format (default)\nBest for large files\nMinimal token usage\nPreserves original CSV structure\nJSON format\nStructured data processing\nEasier for AI to parse\nHigher token usage\nMarkdown format\nReadable tables\nGood for small datasets (\\<100 rows)\nModerate token usage","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"CSV Format Options","lvl3":""}},
|
|
3894
3896
|
{"objectID":"427b303d134e7e002fe1fbb0ae86243f33506e795e3029cdfb7994c2014c0db4","title":"CSV Best Practices","url":"/docs/features/multimodal#csv-best-practices","content":"Use raw format for large files to minimize token usage\nUse JSON format for structured processing when AI needs to manipulate data\nLimit to 1000 rows by default (configurable up to 10,000)\nCombine CSV with visualization images for comprehensive analysis\nWorks with ALL providers (not just vision-capable models)","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"CSV Best Practices","lvl3":""}},
|
|
3897
|
+
{"objectID":"5f2f649cdd7639b3fc3fb30871df07bf3331ad446ac4d23340aaab8976ccf1a7","title":"Frame-extraction options","url":"/docs/features/multimodal#frame-extraction-options","content":"These control the keyframes and are ignored by a provider on the native path.\n\nWithout an explicit , the interval is chosen from the clip's duration\n— roughly every second for clips under 10s, widening to every three minutes\nfor recordings over half an hour, always capped at 100 frames.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Frame-extraction options","lvl3":""}},
|
|
3898
|
+
{"objectID":"2c4c2bb10b6fea62d177994e6df1345d05606b65f8122b3c969cf214df3363f1","title":"CLI usage","url":"/docs/features/multimodal#cli-usage","content":"`bash","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"CLI usage","lvl3":""}},
|
|
3899
|
+
{"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":""}},
|
|
3900
|
+
{"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":""}},
|
|
3901
|
+
{"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.transcribeAudiotranscribeAudio`.","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Frame-extraction knobs","lvl3":""}},
|
|
3902
|
+
{"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":""}},
|
|
3903
|
+
{"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":""}},
|
|
3904
|
+
{"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 |\n| produced no transcript | Not implemented yet (#433); the flag is a no-op that only warns | None yet — track #1757 |\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":""}},
|
|
3895
3905
|
{"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":""}},
|
|
3896
3906
|
{"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":""}},
|
|
3897
3907
|
{"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.27.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": {
|
|
@@ -240,8 +240,9 @@
|
|
|
240
240
|
"pre-push": "pnpm run check:deps && pnpm run build && pnpm run test:provider-structure && pnpm run test:model-manifests",
|
|
241
241
|
"check:all": "pnpm run lint && pnpm run format --check && pnpm run validate && pnpm run validate:commit",
|
|
242
242
|
"test:file-formats": "pnpm exec tsx test/continuous-test-suite-file-formats.ts",
|
|
243
|
-
"test:multimodal": "pnpm run test:file-formats && pnpm run test:multimodal:sdk",
|
|
243
|
+
"test:multimodal": "pnpm run test:file-formats && pnpm run test:multimodal:sdk && pnpm run test:video-native",
|
|
244
244
|
"test:multimodal:sdk": "pnpm exec tsx test/continuous-test-suite-multimodal-sdk.ts",
|
|
245
|
+
"test:video-native": "pnpm exec tsx test/continuous-test-suite-video-native.ts",
|
|
245
246
|
"test:mcp-result-cache": "pnpm exec tsx test/continuous-test-suite-mcp-result-cache.ts",
|
|
246
247
|
"test:mcp-breaker-resolved-errors": "pnpm exec tsx test/continuous-test-suite-mcp-breaker-resolved-errors.ts",
|
|
247
248
|
"test:archive:security": "pnpm exec tsx test/continuous-test-suite-archive-security.ts",
|