@juspay/neurolink 11.29.0 → 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 CHANGED
@@ -1,8 +1,8 @@
1
- ## [11.29.0](https://github.com/juspay/neurolink/compare/v11.28.0...v11.29.0) (2026-08-25)
1
+ ## [11.29.2](https://github.com/juspay/neurolink/compare/v11.29.1...v11.29.2) (2026-08-25)
2
2
 
3
- ### Features
3
+ ### Bug Fixes
4
4
 
5
- - **(core):** one dispatch decision for request kind, wired into the core call sites ([bc99c62](https://github.com/juspay/neurolink/commit/bc99c62438a497f3f6259bdb1e4e90d43e973e6b))
5
+ - **(media):** propagate cancellation through the video generation chain ([3bcaff8](https://github.com/juspay/neurolink/commit/3bcaff8876c85705383a8c59f5b4910138ea608b))
6
6
 
7
7
  ## [11.2.3](https://github.com/juspay/neurolink/compare/v11.2.2...v11.2.3) (2026-08-19)
8
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: controller.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: controller.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(VEO_MODEL, operationName, accessToken, project, location, timeoutMs);
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: controller.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(modelOrEndpoint, operationName, accessToken, project, location, timeoutMs) {
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
- const result = await makePollRequest(pollEndpoint, operationName, accessToken);
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
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
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(VEO_FAST_MODEL, operationName, accessToken, project, location, timeoutMs);
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,