@juspay/neurolink 11.29.1 → 11.29.2
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 +5 -1
- package/dist/adapters/video/vertexVideoHandler.d.ts +1 -0
- package/dist/adapters/video/vertexVideoHandler.js +97 -14
- package/dist/browser/neurolink.min.js +34 -34
- package/dist/core/baseProvider.js +5 -0
- package/dist/types/video.d.ts +5 -0
- package/dist/utils/videoProcessor.js +27 -2
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
## [11.29.
|
|
1
|
+
## [11.29.2](https://github.com/juspay/neurolink/compare/v11.29.1...v11.29.2) (2026-08-25)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- **(media):** propagate cancellation through the video generation chain ([3bcaff8](https://github.com/juspay/neurolink/commit/3bcaff8876c85705383a8c59f5b4910138ea608b))
|
|
2
6
|
|
|
3
7
|
## [11.2.3](https://github.com/juspay/neurolink/compare/v11.2.2...v11.2.3) (2026-08-19)
|
|
4
8
|
|
|
@@ -95,6 +95,7 @@ export declare function generateVideoWithVertex(image: Buffer, prompt: string, o
|
|
|
95
95
|
* @throws {VideoError} When API returns an error or polling times out
|
|
96
96
|
*/
|
|
97
97
|
export declare function generateTransitionWithVertex(firstFrame: Buffer, lastFrame: Buffer, prompt: string, options?: {
|
|
98
|
+
abortSignal?: AbortSignal;
|
|
98
99
|
aspectRatio?: "9:16" | "16:9" | "1:1" | string;
|
|
99
100
|
resolution?: "720p" | "1080p";
|
|
100
101
|
audio?: boolean;
|
|
@@ -300,6 +300,7 @@ export async function generateVideoWithVertex(image, prompt, options = {}, regio
|
|
|
300
300
|
const durationSeconds = options.length || 6; // 4, 6, or 8
|
|
301
301
|
const aspectRatio = options.aspectRatio || "16:9";
|
|
302
302
|
const generateAudio = options.audio ?? true;
|
|
303
|
+
const abortSignal = options.abortSignal;
|
|
303
304
|
logger.debug("Starting Vertex video generation", {
|
|
304
305
|
project,
|
|
305
306
|
location,
|
|
@@ -345,9 +346,13 @@ export async function generateVideoWithVertex(image, prompt, options = {}, regio
|
|
|
345
346
|
},
|
|
346
347
|
};
|
|
347
348
|
logger.debug("Sending video generation request", { endpoint });
|
|
348
|
-
// Create abort controller for request timeout
|
|
349
|
+
// Create abort controller for request timeout, chained onto the
|
|
350
|
+
// caller's cancellation signal so either source stops the request.
|
|
349
351
|
const controller = new AbortController();
|
|
350
352
|
const requestTimeout = setTimeout(() => controller.abort(), 30000); // 30s request timeout
|
|
353
|
+
const requestSignal = abortSignal
|
|
354
|
+
? AbortSignal.any([abortSignal, controller.signal])
|
|
355
|
+
: controller.signal;
|
|
351
356
|
// Start long-running operation
|
|
352
357
|
let response;
|
|
353
358
|
try {
|
|
@@ -358,12 +363,22 @@ export async function generateVideoWithVertex(image, prompt, options = {}, regio
|
|
|
358
363
|
"Content-Type": "application/json; charset=utf-8",
|
|
359
364
|
},
|
|
360
365
|
body: JSON.stringify(requestBody),
|
|
361
|
-
signal:
|
|
366
|
+
signal: requestSignal,
|
|
362
367
|
});
|
|
363
368
|
}
|
|
364
369
|
catch (error) {
|
|
365
370
|
clearTimeout(requestTimeout);
|
|
366
371
|
if (isAbortError(error)) {
|
|
372
|
+
if (abortSignal?.aborted) {
|
|
373
|
+
throw new VideoError({
|
|
374
|
+
code: VIDEO_ERROR_CODES.GENERATION_FAILED,
|
|
375
|
+
message: "Vertex video generation aborted by caller",
|
|
376
|
+
category: ErrorCategory.EXECUTION,
|
|
377
|
+
severity: ErrorSeverity.MEDIUM,
|
|
378
|
+
retriable: false,
|
|
379
|
+
context: { provider: "vertex", endpoint },
|
|
380
|
+
});
|
|
381
|
+
}
|
|
367
382
|
throw new VideoError({
|
|
368
383
|
code: VIDEO_ERROR_CODES.GENERATION_FAILED,
|
|
369
384
|
message: "Video generation request timed out after 30 seconds",
|
|
@@ -407,7 +422,8 @@ export async function generateVideoWithVertex(image, prompt, options = {}, regio
|
|
|
407
422
|
logger.debug("Video generation operation started", { operationName });
|
|
408
423
|
// Poll for completion using fetchPredictOperation endpoint
|
|
409
424
|
const remainingTime = VIDEO_GENERATION_TIMEOUT_MS - (Date.now() - startTime);
|
|
410
|
-
const videoBuffer = await pollVideoOperation(operationName, accessToken, project, location, Math.max(1000, remainingTime)
|
|
425
|
+
const videoBuffer = await pollVideoOperation(operationName, accessToken, project, location, Math.max(1000, remainingTime), // Ensure at least 1 second timeout
|
|
426
|
+
abortSignal);
|
|
411
427
|
const processingTime = Date.now() - startTime;
|
|
412
428
|
// Calculate dimensions based on resolution and aspect ratio
|
|
413
429
|
const dimensions = calculateDimensions(resolution, aspectRatio);
|
|
@@ -523,9 +539,12 @@ function extractVideoFromResult(result, operationName) {
|
|
|
523
539
|
* @returns Response JSON from the poll request
|
|
524
540
|
* @throws VideoError on request failure
|
|
525
541
|
*/
|
|
526
|
-
async function makePollRequest(pollEndpoint, operationName, accessToken, timeoutMs = 30000) {
|
|
542
|
+
async function makePollRequest(pollEndpoint, operationName, accessToken, timeoutMs = 30000, abortSignal) {
|
|
527
543
|
const controller = new AbortController();
|
|
528
544
|
const requestTimeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
545
|
+
const requestSignal = abortSignal
|
|
546
|
+
? AbortSignal.any([abortSignal, controller.signal])
|
|
547
|
+
: controller.signal;
|
|
529
548
|
let response;
|
|
530
549
|
try {
|
|
531
550
|
response = await fetch(pollEndpoint, {
|
|
@@ -535,12 +554,22 @@ async function makePollRequest(pollEndpoint, operationName, accessToken, timeout
|
|
|
535
554
|
"Content-Type": "application/json; charset=utf-8",
|
|
536
555
|
},
|
|
537
556
|
body: JSON.stringify({ operationName }), // Pass operation name in body
|
|
538
|
-
signal:
|
|
557
|
+
signal: requestSignal,
|
|
539
558
|
});
|
|
540
559
|
}
|
|
541
560
|
catch (error) {
|
|
542
561
|
clearTimeout(requestTimeout);
|
|
543
562
|
if (isAbortError(error)) {
|
|
563
|
+
if (abortSignal?.aborted) {
|
|
564
|
+
throw new VideoError({
|
|
565
|
+
code: VIDEO_ERROR_CODES.GENERATION_FAILED,
|
|
566
|
+
message: "Vertex poll for operation aborted by caller",
|
|
567
|
+
category: ErrorCategory.EXECUTION,
|
|
568
|
+
severity: ErrorSeverity.MEDIUM,
|
|
569
|
+
retriable: false,
|
|
570
|
+
context: { provider: "vertex", operationName },
|
|
571
|
+
});
|
|
572
|
+
}
|
|
544
573
|
throw new VideoError({
|
|
545
574
|
code: VIDEO_ERROR_CODES.GENERATION_FAILED,
|
|
546
575
|
message: `Poll request timed out after ${timeoutMs}ms`,
|
|
@@ -589,8 +618,16 @@ async function makePollRequest(pollEndpoint, operationName, accessToken, timeout
|
|
|
589
618
|
*
|
|
590
619
|
* @throws {VideoError} On API error, timeout, or missing video data
|
|
591
620
|
*/
|
|
592
|
-
async function pollVideoOperation(operationName, accessToken, project, location, timeoutMs) {
|
|
593
|
-
return pollOperation(
|
|
621
|
+
async function pollVideoOperation(operationName, accessToken, project, location, timeoutMs, abortSignal) {
|
|
622
|
+
return pollOperation({
|
|
623
|
+
modelOrEndpoint: VEO_MODEL,
|
|
624
|
+
operationName,
|
|
625
|
+
accessToken,
|
|
626
|
+
project,
|
|
627
|
+
location,
|
|
628
|
+
timeoutMs,
|
|
629
|
+
abortSignal,
|
|
630
|
+
});
|
|
594
631
|
}
|
|
595
632
|
// ============================================================================
|
|
596
633
|
// TRANSITION GENERATION (Director Mode)
|
|
@@ -619,6 +656,7 @@ export async function generateTransitionWithVertex(firstFrame, lastFrame, prompt
|
|
|
619
656
|
const aspectRatio = options.aspectRatio || "16:9";
|
|
620
657
|
const resolution = options.resolution || "720p";
|
|
621
658
|
const generateAudio = options.audio ?? true;
|
|
659
|
+
const abortSignal = options.abortSignal;
|
|
622
660
|
logger.debug("Starting transition clip generation", {
|
|
623
661
|
model: VEO_FAST_MODEL,
|
|
624
662
|
durationSeconds,
|
|
@@ -658,6 +696,9 @@ export async function generateTransitionWithVertex(firstFrame, lastFrame, prompt
|
|
|
658
696
|
};
|
|
659
697
|
const controller = new AbortController();
|
|
660
698
|
const requestTimeout = setTimeout(() => controller.abort(), 30000);
|
|
699
|
+
const requestSignal = abortSignal
|
|
700
|
+
? AbortSignal.any([abortSignal, controller.signal])
|
|
701
|
+
: controller.signal;
|
|
661
702
|
let response;
|
|
662
703
|
try {
|
|
663
704
|
response = await fetch(endpoint, {
|
|
@@ -667,12 +708,21 @@ export async function generateTransitionWithVertex(firstFrame, lastFrame, prompt
|
|
|
667
708
|
"Content-Type": "application/json; charset=utf-8",
|
|
668
709
|
},
|
|
669
710
|
body: JSON.stringify(requestBody),
|
|
670
|
-
signal:
|
|
711
|
+
signal: requestSignal,
|
|
671
712
|
});
|
|
672
713
|
}
|
|
673
714
|
catch (error) {
|
|
674
715
|
clearTimeout(requestTimeout);
|
|
675
716
|
if (isAbortError(error)) {
|
|
717
|
+
if (abortSignal?.aborted) {
|
|
718
|
+
throw new VideoError({
|
|
719
|
+
code: VIDEO_ERROR_CODES.DIRECTOR_TRANSITION_FAILED,
|
|
720
|
+
message: "Vertex transition generation aborted by caller",
|
|
721
|
+
category: ErrorCategory.EXECUTION,
|
|
722
|
+
severity: ErrorSeverity.MEDIUM,
|
|
723
|
+
retriable: false,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
676
726
|
throw new VideoError({
|
|
677
727
|
code: VIDEO_ERROR_CODES.DIRECTOR_TRANSITION_FAILED,
|
|
678
728
|
message: "Transition generation request timed out after 30 seconds",
|
|
@@ -708,7 +758,7 @@ export async function generateTransitionWithVertex(firstFrame, lastFrame, prompt
|
|
|
708
758
|
}
|
|
709
759
|
// Poll with Veo Fast model endpoint
|
|
710
760
|
const remainingTime = VIDEO_GENERATION_TIMEOUT_MS - (Date.now() - startTime);
|
|
711
|
-
const videoBuffer = await pollTransitionOperation(operationName, accessToken, project, location, Math.max(1000, remainingTime));
|
|
761
|
+
const videoBuffer = await pollTransitionOperation(operationName, accessToken, project, location, Math.max(1000, remainingTime), abortSignal);
|
|
712
762
|
logger.debug("Transition clip generated", {
|
|
713
763
|
processingTime: Date.now() - startTime,
|
|
714
764
|
videoSize: videoBuffer.length,
|
|
@@ -733,8 +783,17 @@ export async function generateTransitionWithVertex(firstFrame, lastFrame, prompt
|
|
|
733
783
|
* Common polling helper that handles both video and transition operations.
|
|
734
784
|
* Accepts a model name to construct the appropriate endpoint.
|
|
735
785
|
*/
|
|
736
|
-
async function pollOperation(
|
|
786
|
+
async function pollOperation(args) {
|
|
787
|
+
const { modelOrEndpoint, operationName, accessToken, project, location, timeoutMs, abortSignal, } = args;
|
|
737
788
|
const startTime = Date.now();
|
|
789
|
+
const makeAbortedError = () => new VideoError({
|
|
790
|
+
code: VIDEO_ERROR_CODES.GENERATION_FAILED,
|
|
791
|
+
message: "Vertex poll for operation aborted by caller",
|
|
792
|
+
category: ErrorCategory.EXECUTION,
|
|
793
|
+
severity: ErrorSeverity.MEDIUM,
|
|
794
|
+
retriable: false,
|
|
795
|
+
context: { provider: "vertex", operationName },
|
|
796
|
+
});
|
|
738
797
|
// Global endpoint uses aiplatform.googleapis.com (no region prefix),
|
|
739
798
|
// same pattern as the predictLongRunning endpoint at line 374
|
|
740
799
|
const pollHost = location === "global"
|
|
@@ -742,7 +801,10 @@ async function pollOperation(modelOrEndpoint, operationName, accessToken, projec
|
|
|
742
801
|
: `${location}-aiplatform.googleapis.com`;
|
|
743
802
|
const pollEndpoint = `https://${pollHost}/v1/projects/${project}/locations/${location}/publishers/google/models/${modelOrEndpoint}:fetchPredictOperation`;
|
|
744
803
|
while (Date.now() - startTime < timeoutMs) {
|
|
745
|
-
|
|
804
|
+
if (abortSignal?.aborted) {
|
|
805
|
+
throw makeAbortedError();
|
|
806
|
+
}
|
|
807
|
+
const result = await makePollRequest(pollEndpoint, operationName, accessToken, undefined, abortSignal);
|
|
746
808
|
if (result.done) {
|
|
747
809
|
return extractVideoFromResult(result, operationName);
|
|
748
810
|
}
|
|
@@ -750,7 +812,19 @@ async function pollOperation(modelOrEndpoint, operationName, accessToken, projec
|
|
|
750
812
|
operationName,
|
|
751
813
|
elapsed: Date.now() - startTime,
|
|
752
814
|
});
|
|
753
|
-
|
|
815
|
+
// Abortable sleep — a caller abort mid-interval must not wait out the
|
|
816
|
+
// full poll interval before being observed.
|
|
817
|
+
await new Promise((resolve, reject) => {
|
|
818
|
+
const onAbort = () => {
|
|
819
|
+
clearTimeout(timer);
|
|
820
|
+
reject(makeAbortedError());
|
|
821
|
+
};
|
|
822
|
+
const timer = setTimeout(() => {
|
|
823
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
824
|
+
resolve();
|
|
825
|
+
}, POLL_INTERVAL_MS);
|
|
826
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
827
|
+
});
|
|
754
828
|
}
|
|
755
829
|
throw new VideoError({
|
|
756
830
|
code: VIDEO_ERROR_CODES.POLL_TIMEOUT,
|
|
@@ -765,8 +839,16 @@ async function pollOperation(modelOrEndpoint, operationName, accessToken, projec
|
|
|
765
839
|
* Poll Vertex AI operation for transition clip completion.
|
|
766
840
|
* Uses the Veo Fast model fetchPredictOperation endpoint.
|
|
767
841
|
*/
|
|
768
|
-
async function pollTransitionOperation(operationName, accessToken, project, location, timeoutMs) {
|
|
769
|
-
return pollOperation(
|
|
842
|
+
async function pollTransitionOperation(operationName, accessToken, project, location, timeoutMs, abortSignal) {
|
|
843
|
+
return pollOperation({
|
|
844
|
+
modelOrEndpoint: VEO_FAST_MODEL,
|
|
845
|
+
operationName,
|
|
846
|
+
accessToken,
|
|
847
|
+
project,
|
|
848
|
+
location,
|
|
849
|
+
timeoutMs,
|
|
850
|
+
abortSignal,
|
|
851
|
+
});
|
|
770
852
|
}
|
|
771
853
|
/**
|
|
772
854
|
* Class wrapper around the standalone Vertex Veo functions, conforming to
|
|
@@ -795,6 +877,7 @@ export class VertexVideoHandler {
|
|
|
795
877
|
}
|
|
796
878
|
generateTransition(firstFrame, lastFrame, prompt, options, region) {
|
|
797
879
|
return generateTransitionWithVertex(firstFrame, lastFrame, prompt, {
|
|
880
|
+
abortSignal: options?.abortSignal,
|
|
798
881
|
aspectRatio: options?.aspectRatio,
|
|
799
882
|
resolution: options?.resolution,
|
|
800
883
|
audio: options?.audio,
|