@juspay/neurolink 11.28.0 → 11.29.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 +2 -2
- package/dist/browser/neurolink.min.js +348 -348
- package/dist/core/baseProvider.js +19 -17
- package/dist/core/resolveRequestKind.d.ts +27 -0
- package/dist/core/resolveRequestKind.js +50 -0
- package/dist/neurolink.js +7 -3
- package/dist/types/dispatch.d.ts +22 -0
- package/dist/types/dispatch.js +6 -0
- package/dist/types/generate.d.ts +29 -37
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/video.d.ts +13 -0
- package/dist/utils/videoProcessor.d.ts +11 -5
- package/dist/utils/videoProcessor.js +23 -16
- package/package.json +2 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { context, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
2
2
|
import { directAgentTools } from "../agent/directTools.js";
|
|
3
3
|
import { defaultProviderFor } from "../factories/mediaHandlerCatalog.js";
|
|
4
|
-
import { isImageGenerationModel } from "./constants.js";
|
|
5
4
|
import { MiddlewareFactory } from "../middleware/factory.js";
|
|
6
5
|
import { modelSupports } from "../models/modelRegistry.js";
|
|
6
|
+
import { resolveRequestKind } from "./resolveRequestKind.js";
|
|
7
7
|
import { ATTR, tracers } from "../telemetry/index.js";
|
|
8
8
|
import { ERROR_CODES, isAbortError, NeuroLinkError, } from "../utils/errorHandling.js";
|
|
9
9
|
import { ProviderError } from "../types/index.js";
|
|
@@ -239,14 +239,12 @@ export class BaseProvider {
|
|
|
239
239
|
}
|
|
240
240
|
// CRITICAL: Image generation models don't support real streaming
|
|
241
241
|
// Force fake streaming for image models to ensure image output is yielded.
|
|
242
|
-
//
|
|
243
|
-
// JSON analysis) so dual-mode models like
|
|
244
|
-
// can still perform text/structured
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
options.output?.format === "text";
|
|
249
|
-
if (isImageModel && !requestsNonImageOutput) {
|
|
242
|
+
// resolveRequestKind() skips this path when the caller explicitly requests
|
|
243
|
+
// non-image output (e.g. JSON analysis) so dual-mode models like
|
|
244
|
+
// gemini-3.1-flash-image-preview can still perform text/structured
|
|
245
|
+
// generation — see its doc comment for the full precedence table.
|
|
246
|
+
const requestKind = resolveRequestKind(options, this.modelName);
|
|
247
|
+
if (requestKind === "image") {
|
|
250
248
|
logger.info(`Image model detected, forcing fake streaming`, {
|
|
251
249
|
provider: this.providerName,
|
|
252
250
|
model: this.modelName,
|
|
@@ -1067,14 +1065,13 @@ export class BaseProvider {
|
|
|
1067
1065
|
}
|
|
1068
1066
|
async runGenerateInActiveContext(options, startTime, otelSpan, otelSpanState) {
|
|
1069
1067
|
try {
|
|
1070
|
-
|
|
1068
|
+
// Single source of truth for "what kind of request is this" — see
|
|
1069
|
+
// resolveRequestKind's doc comment for the full precedence table.
|
|
1070
|
+
const requestKind = resolveRequestKind(options, this.modelName);
|
|
1071
|
+
if (requestKind === "video") {
|
|
1071
1072
|
return await this.handleVideoGeneration(options, startTime);
|
|
1072
1073
|
}
|
|
1073
|
-
|
|
1074
|
-
const requestsNonImageOutput = options.output?.format === "json" ||
|
|
1075
|
-
options.output?.format === "structured" ||
|
|
1076
|
-
options.output?.format === "text";
|
|
1077
|
-
if (isImageModel && !requestsNonImageOutput) {
|
|
1074
|
+
if (requestKind === "image") {
|
|
1078
1075
|
logger.info(`Image generation model detected, routing to executeImageGeneration`, {
|
|
1079
1076
|
provider: this.providerName,
|
|
1080
1077
|
model: this.modelName,
|
|
@@ -1082,7 +1079,7 @@ export class BaseProvider {
|
|
|
1082
1079
|
const imageResult = await this.executeImageGeneration(options);
|
|
1083
1080
|
return await this.enhanceResult(imageResult, options, startTime);
|
|
1084
1081
|
}
|
|
1085
|
-
if (
|
|
1082
|
+
if (requestKind === "tts-direct") {
|
|
1086
1083
|
return this.handleDirectTTSSynthesis(options, startTime);
|
|
1087
1084
|
}
|
|
1088
1085
|
const { tools, model } = await this.prepareGenerationContext(options);
|
|
@@ -2179,7 +2176,12 @@ export class BaseProvider {
|
|
|
2179
2176
|
// shared timeout helper so standard video gen honors the caller's
|
|
2180
2177
|
// timeout the same way director mode does (see above ~Line 2062).
|
|
2181
2178
|
const videoTimeout = options.timeout ?? 600_000; // 10 min default
|
|
2182
|
-
const videoResult = await this.executeWithTimeout(() => VideoProcessor.generate(requestedProvider,
|
|
2179
|
+
const videoResult = await this.executeWithTimeout(() => VideoProcessor.generate(requestedProvider, {
|
|
2180
|
+
...(options.output?.video ?? {}),
|
|
2181
|
+
image: imageBuffer,
|
|
2182
|
+
prompt,
|
|
2183
|
+
region: options.region,
|
|
2184
|
+
}), { timeout: videoTimeout, operationType: "generate" });
|
|
2183
2185
|
// Prefer the handler's own model id (more accurate — it knows the exact
|
|
2184
2186
|
// checkpoint that ran). Fall back to the request-time value, and finally
|
|
2185
2187
|
// to the Vertex default only when we're on the Vertex route.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RequestKind, RequestKindInput } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The dispatch decision for "what kind of request is this" — text, image,
|
|
4
|
+
* video, music, avatar, direct TTS synthesis, or PPT generation — at the
|
|
5
|
+
* CORE call sites: neurolink.ts's maybeHandleEarlyGenerateResult
|
|
6
|
+
* (music/avatar/ppt/workflow routing) and baseProvider.ts's
|
|
7
|
+
* stream()/runGenerateInActiveContext (image/video/tts-direct routing) call
|
|
8
|
+
* this instead of independently re-deriving the decision.
|
|
9
|
+
*
|
|
10
|
+
* NOT yet the only copy: replicate.ts's generate() override and
|
|
11
|
+
* googleVertex/client.ts's native dispatch (~6672-6718) still carry their
|
|
12
|
+
* own provider-internal versions — the Vertex one with a cruder
|
|
13
|
+
* startsWith() image match. Migrating those two is queued follow-up work;
|
|
14
|
+
* until it lands, an edit to this precedence table does not reach them.
|
|
15
|
+
*
|
|
16
|
+
* Precedence, checked in order:
|
|
17
|
+
* 1. output.mode (music/avatar/video/ppt) — an explicit mode always wins.
|
|
18
|
+
* 2. an image-generation model, unless the caller explicitly asked for a
|
|
19
|
+
* non-image output.format (json/structured/text) — this lets dual-mode
|
|
20
|
+
* models like gemini-3.1-flash-image-preview still perform text or
|
|
21
|
+
* structured generation when requested.
|
|
22
|
+
* 3. tts.enabled without tts.useAiResponse — direct synthesis, bypassing
|
|
23
|
+
* the LLM turn entirely (useAiResponse means the LLM's own text
|
|
24
|
+
* response gets synthesized afterward, which is NOT this branch).
|
|
25
|
+
* 4. otherwise, "text".
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveRequestKind(options: RequestKindInput, modelName?: string): RequestKind;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isImageGenerationModel } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* The dispatch decision for "what kind of request is this" — text, image,
|
|
4
|
+
* video, music, avatar, direct TTS synthesis, or PPT generation — at the
|
|
5
|
+
* CORE call sites: neurolink.ts's maybeHandleEarlyGenerateResult
|
|
6
|
+
* (music/avatar/ppt/workflow routing) and baseProvider.ts's
|
|
7
|
+
* stream()/runGenerateInActiveContext (image/video/tts-direct routing) call
|
|
8
|
+
* this instead of independently re-deriving the decision.
|
|
9
|
+
*
|
|
10
|
+
* NOT yet the only copy: replicate.ts's generate() override and
|
|
11
|
+
* googleVertex/client.ts's native dispatch (~6672-6718) still carry their
|
|
12
|
+
* own provider-internal versions — the Vertex one with a cruder
|
|
13
|
+
* startsWith() image match. Migrating those two is queued follow-up work;
|
|
14
|
+
* until it lands, an edit to this precedence table does not reach them.
|
|
15
|
+
*
|
|
16
|
+
* Precedence, checked in order:
|
|
17
|
+
* 1. output.mode (music/avatar/video/ppt) — an explicit mode always wins.
|
|
18
|
+
* 2. an image-generation model, unless the caller explicitly asked for a
|
|
19
|
+
* non-image output.format (json/structured/text) — this lets dual-mode
|
|
20
|
+
* models like gemini-3.1-flash-image-preview still perform text or
|
|
21
|
+
* structured generation when requested.
|
|
22
|
+
* 3. tts.enabled without tts.useAiResponse — direct synthesis, bypassing
|
|
23
|
+
* the LLM turn entirely (useAiResponse means the LLM's own text
|
|
24
|
+
* response gets synthesized afterward, which is NOT this branch).
|
|
25
|
+
* 4. otherwise, "text".
|
|
26
|
+
*/
|
|
27
|
+
export function resolveRequestKind(options, modelName) {
|
|
28
|
+
if (options.output?.mode === "music") {
|
|
29
|
+
return "music";
|
|
30
|
+
}
|
|
31
|
+
if (options.output?.mode === "avatar") {
|
|
32
|
+
return "avatar";
|
|
33
|
+
}
|
|
34
|
+
if (options.output?.mode === "video") {
|
|
35
|
+
return "video";
|
|
36
|
+
}
|
|
37
|
+
if (options.output?.mode === "ppt") {
|
|
38
|
+
return "ppt";
|
|
39
|
+
}
|
|
40
|
+
const requestsNonImageOutput = options.output?.format === "json" ||
|
|
41
|
+
options.output?.format === "structured" ||
|
|
42
|
+
options.output?.format === "text";
|
|
43
|
+
if (isImageGenerationModel(modelName) && !requestsNonImageOutput) {
|
|
44
|
+
return "image";
|
|
45
|
+
}
|
|
46
|
+
if (options.tts?.enabled && !options.tts?.useAiResponse) {
|
|
47
|
+
return "tts-direct";
|
|
48
|
+
}
|
|
49
|
+
return "text";
|
|
50
|
+
}
|
package/dist/neurolink.js
CHANGED
|
@@ -34,6 +34,7 @@ import { buildToolRoutingCatalog, buildRoutingQueryFromHistory, resolveToolRouti
|
|
|
34
34
|
import { ToolRoutingCache } from "./core/toolRoutingCache.js";
|
|
35
35
|
import { DEFAULT_RECENT_TURNS, KnowledgeGroundingEngine, } from "./knowledge/index.js";
|
|
36
36
|
import { AIProviderFactory } from "./core/factory.js";
|
|
37
|
+
import { resolveRequestKind } from "./core/resolveRequestKind.js";
|
|
37
38
|
import { createToolEventPayload } from "./core/toolEvents.js";
|
|
38
39
|
import { ProviderFactory } from "./factories/providerFactory.js";
|
|
39
40
|
import { ProviderRegistry } from "./factories/providerRegistry.js";
|
|
@@ -3576,13 +3577,16 @@ Current user's request: ${currentInput}`;
|
|
|
3576
3577
|
}
|
|
3577
3578
|
return this.generateWithWorkflow(options);
|
|
3578
3579
|
}
|
|
3579
|
-
|
|
3580
|
+
// Single source of truth for "what kind of request is this" — see
|
|
3581
|
+
// resolveRequestKind's doc comment for the full precedence table.
|
|
3582
|
+
const requestKind = resolveRequestKind(options, options.model);
|
|
3583
|
+
if (requestKind === "music") {
|
|
3580
3584
|
return this.generateWithMusic(options, generateSpan);
|
|
3581
3585
|
}
|
|
3582
|
-
if (
|
|
3586
|
+
if (requestKind === "avatar") {
|
|
3583
3587
|
return this.generateWithAvatar(options, generateSpan);
|
|
3584
3588
|
}
|
|
3585
|
-
if (
|
|
3589
|
+
if (requestKind !== "ppt") {
|
|
3586
3590
|
return null;
|
|
3587
3591
|
}
|
|
3588
3592
|
if (options.stt?.enabled && options.stt?.audio) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types backing resolveRequestKind() (src/lib/core/resolveRequestKind.ts) —
|
|
3
|
+
* the single function that decides which of NeuroLink's output modes a
|
|
4
|
+
* generate/stream request is asking for.
|
|
5
|
+
*/
|
|
6
|
+
export type RequestKind = "text" | "image" | "video" | "music" | "avatar" | "tts-direct" | "ppt";
|
|
7
|
+
/**
|
|
8
|
+
* Narrow structural subset of TextGenerationOptions/GenerateOptions that
|
|
9
|
+
* resolveRequestKind() actually reads. Kept intentionally minimal (rather
|
|
10
|
+
* than importing the full options type) so this module has no dependency
|
|
11
|
+
* on the wider options type graph.
|
|
12
|
+
*/
|
|
13
|
+
export type RequestKindInput = {
|
|
14
|
+
output?: {
|
|
15
|
+
mode?: string;
|
|
16
|
+
format?: string;
|
|
17
|
+
};
|
|
18
|
+
tts?: {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
useAiResponse?: boolean;
|
|
21
|
+
};
|
|
22
|
+
};
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -802,23 +802,12 @@ export type ToolExecutionCaptureOptions = {
|
|
|
802
802
|
*/
|
|
803
803
|
export type GenerateStopReason = "completed" | "step-cap" | "context-cap" | "time-limit" | "stalled" | "aborted" | "provider-error";
|
|
804
804
|
/**
|
|
805
|
-
*
|
|
806
|
-
*
|
|
805
|
+
* Media generation/processing outputs shared by GenerateResult and
|
|
806
|
+
* TextGenerationResult. Extracted so both result types intersect (&) this
|
|
807
|
+
* single definition instead of each declaring its own drifting copy of the
|
|
808
|
+
* same audio/video/avatar/music/ppt/image/transcription fields.
|
|
807
809
|
*/
|
|
808
|
-
export type
|
|
809
|
-
content: string;
|
|
810
|
-
/** Knowledge-grounding diagnostics for this turn (present only when grounding ran). */
|
|
811
|
-
knowledge?: KnowledgeGroundingMetadata;
|
|
812
|
-
/**
|
|
813
|
-
* Parsed structured object when a `schema` was requested. Populated from
|
|
814
|
-
* AI-SDK experimental_output, or from text-mode coercion (balanced-scan +
|
|
815
|
-
* jsonrepair). Prefer this over JSON.parse(content) — it never requires the
|
|
816
|
-
* caller to re-parse hand-escaped model text.
|
|
817
|
-
*/
|
|
818
|
-
structuredData?: unknown;
|
|
819
|
-
outputs?: {
|
|
820
|
-
text: string;
|
|
821
|
-
};
|
|
810
|
+
export type MediaGenerationOutputs = {
|
|
822
811
|
/**
|
|
823
812
|
* Text-to-Speech audio result
|
|
824
813
|
*
|
|
@@ -911,9 +900,31 @@ export type GenerateResult = {
|
|
|
911
900
|
* ```
|
|
912
901
|
*/
|
|
913
902
|
ppt?: PPTGenerationResult;
|
|
903
|
+
/** Standard format for image generation */
|
|
914
904
|
imageOutput?: {
|
|
915
905
|
base64: string;
|
|
916
906
|
} | null;
|
|
907
|
+
/** STT transcription result (present when stt.enabled is true and audio input was provided) */
|
|
908
|
+
transcription?: STTResult;
|
|
909
|
+
};
|
|
910
|
+
/**
|
|
911
|
+
* Generate function result type - Primary output format
|
|
912
|
+
* Future-ready for multi-modal outputs while maintaining text focus
|
|
913
|
+
*/
|
|
914
|
+
export type GenerateResult = {
|
|
915
|
+
content: string;
|
|
916
|
+
/** Knowledge-grounding diagnostics for this turn (present only when grounding ran). */
|
|
917
|
+
knowledge?: KnowledgeGroundingMetadata;
|
|
918
|
+
/**
|
|
919
|
+
* Parsed structured object when a `schema` was requested. Populated from
|
|
920
|
+
* AI-SDK experimental_output, or from text-mode coercion (balanced-scan +
|
|
921
|
+
* jsonrepair). Prefer this over JSON.parse(content) — it never requires the
|
|
922
|
+
* caller to re-parse hand-escaped model text.
|
|
923
|
+
*/
|
|
924
|
+
structuredData?: unknown;
|
|
925
|
+
outputs?: {
|
|
926
|
+
text: string;
|
|
927
|
+
};
|
|
917
928
|
provider?: string;
|
|
918
929
|
model?: string;
|
|
919
930
|
finishReason?: string;
|
|
@@ -1013,8 +1024,6 @@ export type GenerateResult = {
|
|
|
1013
1024
|
reasoning?: string;
|
|
1014
1025
|
/** Token count for reasoning content */
|
|
1015
1026
|
reasoningTokens?: number;
|
|
1016
|
-
/** STT transcription result (present when stt.enabled is true and audio input was provided) */
|
|
1017
|
-
transcription?: STTResult;
|
|
1018
1027
|
retries?: {
|
|
1019
1028
|
count: number;
|
|
1020
1029
|
errors: Array<{
|
|
@@ -1033,7 +1042,7 @@ export type GenerateResult = {
|
|
|
1033
1042
|
* absolute `requestsRemaining` / `tokensRemaining`.
|
|
1034
1043
|
*/
|
|
1035
1044
|
limits?: ClaudeLimitSnapshot;
|
|
1036
|
-
};
|
|
1045
|
+
} & MediaGenerationOutputs;
|
|
1037
1046
|
/**
|
|
1038
1047
|
* Unified options for both generation and streaming
|
|
1039
1048
|
* Supports factory patterns and domain configuration
|
|
@@ -1467,23 +1476,6 @@ export type TextGenerationResult = {
|
|
|
1467
1476
|
}>;
|
|
1468
1477
|
analytics?: AnalyticsData;
|
|
1469
1478
|
evaluation?: EvaluationData;
|
|
1470
|
-
audio?: TTSResult;
|
|
1471
|
-
/** Outcome of TTS synthesis, including the failure reason. */
|
|
1472
|
-
ttsMetadata?: TTSMetadata;
|
|
1473
|
-
/** STT transcription result (present when stt input was processed) */
|
|
1474
|
-
transcription?: STTResult;
|
|
1475
|
-
/** Video generation result */
|
|
1476
|
-
video?: VideoGenerationResult;
|
|
1477
|
-
/** Avatar (talking-head) generation result */
|
|
1478
|
-
avatar?: AvatarResult;
|
|
1479
|
-
/** Music generation result */
|
|
1480
|
-
music?: MusicResult;
|
|
1481
|
-
/** PowerPoint generation result */
|
|
1482
|
-
ppt?: PPTGenerationResult;
|
|
1483
|
-
/** Image generation output */
|
|
1484
|
-
imageOutput?: {
|
|
1485
|
-
base64: string;
|
|
1486
|
-
} | null;
|
|
1487
1479
|
/** Gemini 3 thought signature for reasoning continuity across turns */
|
|
1488
1480
|
thoughtSignature?: string;
|
|
1489
1481
|
/** Thinking/reasoning text from provider (Anthropic thinking blocks, Gemini thought parts, DeepSeek/NIM reasoning_content) */
|
|
@@ -1497,7 +1489,7 @@ export type TextGenerationResult = {
|
|
|
1497
1489
|
message: string;
|
|
1498
1490
|
}>;
|
|
1499
1491
|
};
|
|
1500
|
-
};
|
|
1492
|
+
} & MediaGenerationOutputs;
|
|
1501
1493
|
/**
|
|
1502
1494
|
* Enhanced result type with optional analytics/evaluation
|
|
1503
1495
|
*/
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
package/dist/types/video.d.ts
CHANGED
|
@@ -13,6 +13,19 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { VideoGenerationResult, VideoOutputOptions } from "./multimodal.js";
|
|
15
15
|
export type { VideoGenerationResult, VideoOutputOptions, } from "./multimodal.js";
|
|
16
|
+
/**
|
|
17
|
+
* Bag-form input to `VideoProcessor.generate()` — the primary data (image,
|
|
18
|
+
* prompt, region) alongside the video-specific output options, collapsed
|
|
19
|
+
* into a single object matching Music/Avatar's existing `generate(provider,
|
|
20
|
+
* options)` shape. `VideoHandler.generate()`'s own 4-positional-argument
|
|
21
|
+
* signature is unchanged; `VideoProcessor.generate()` translates between the
|
|
22
|
+
* two internally.
|
|
23
|
+
*/
|
|
24
|
+
export type VideoGenerateOptions = VideoOutputOptions & {
|
|
25
|
+
image: Buffer;
|
|
26
|
+
prompt: string;
|
|
27
|
+
region?: string;
|
|
28
|
+
};
|
|
16
29
|
/**
|
|
17
30
|
* Director-mode transition options.
|
|
18
31
|
*
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @module utils/videoProcessor
|
|
12
12
|
*/
|
|
13
13
|
import { VIDEO_ERROR_CODES } from "../constants/videoErrors.js";
|
|
14
|
-
import type { VideoGenerationResult, VideoHandler, VideoOutputOptions, VideoTransitionOptions } from "../types/index.js";
|
|
14
|
+
import type { VideoGenerateOptions, VideoGenerationResult, VideoHandler, VideoOutputOptions, VideoTransitionOptions } from "../types/index.js";
|
|
15
15
|
import { VideoError } from "../adapters/video/vertexVideoHandler.js";
|
|
16
16
|
export { VideoError, VIDEO_ERROR_CODES };
|
|
17
17
|
/**
|
|
@@ -45,13 +45,19 @@ export declare class VideoProcessor {
|
|
|
45
45
|
* Generate a single video clip via the registered handler.
|
|
46
46
|
*
|
|
47
47
|
* @param provider - Registered provider name (e.g. "vertex", "kling")
|
|
48
|
-
* @param
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
48
|
+
* @param options - Bag of the source image, prompt, optional region
|
|
49
|
+
* override, and resolution / length / aspect-ratio / audio options.
|
|
50
|
+
* Translated internally into the handler-level 4-positional-argument
|
|
51
|
+
* call — `VideoHandler.generate()`'s own signature is unchanged.
|
|
52
52
|
* @throws VideoError on registry miss, handler-not-configured, or
|
|
53
53
|
* generation failure
|
|
54
54
|
*/
|
|
55
|
+
static generate(provider: string, options: VideoGenerateOptions): Promise<VideoGenerationResult>;
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated Positional form kept for backward compatibility with
|
|
58
|
+
* pre-bag callers (VideoProcessor is a public export). Use the
|
|
59
|
+
* options-bag overload.
|
|
60
|
+
*/
|
|
55
61
|
static generate(provider: string, image: Buffer, prompt: string, options: VideoOutputOptions, region?: string): Promise<VideoGenerationResult>;
|
|
56
62
|
/**
|
|
57
63
|
* Generate a transition clip via the registered handler (Director Mode).
|
|
@@ -14,6 +14,11 @@ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
|
|
|
14
14
|
import { VIDEO_ERROR_CODES } from "../constants/videoErrors.js";
|
|
15
15
|
import { SpanSerializer, SpanStatus, SpanType, getMetricsAggregator, } from "../observability/index.js";
|
|
16
16
|
import { logger } from "./logger.js";
|
|
17
|
+
import { withTimeout } from "./async/withTimeout.js";
|
|
18
|
+
// Video generation is legitimately minutes-long (Kling/Runway render queues),
|
|
19
|
+
// so the bound is generous — its job is to convert a wedged handler into an
|
|
20
|
+
// error rather than an eternal hang, not to police normal latency.
|
|
21
|
+
const VIDEO_GENERATION_TIMEOUT_MS = 600_000;
|
|
17
22
|
// VideoError is canonical in vertexVideoHandler.ts (existing). Re-export
|
|
18
23
|
// here so consumers of `VideoProcessor` can import the typed error from
|
|
19
24
|
// the same module. Both throws and instanceof checks resolve to the same
|
|
@@ -69,19 +74,17 @@ export class VideoProcessor {
|
|
|
69
74
|
"video.audio": options.audio,
|
|
70
75
|
};
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
static async generate(provider, image, prompt, options, region) {
|
|
84
|
-
const span = SpanSerializer.createSpan(SpanType.MEDIA_GENERATION, "video.generate", this.buildSpanAttributes(provider, options));
|
|
77
|
+
static async generate(provider, optionsOrImage, legacyPrompt, legacyOptions, legacyRegion) {
|
|
78
|
+
const bag = Buffer.isBuffer(optionsOrImage)
|
|
79
|
+
? {
|
|
80
|
+
image: optionsOrImage,
|
|
81
|
+
prompt: legacyPrompt ?? "",
|
|
82
|
+
...(legacyRegion !== undefined ? { region: legacyRegion } : {}),
|
|
83
|
+
...(legacyOptions ?? {}),
|
|
84
|
+
}
|
|
85
|
+
: optionsOrImage;
|
|
86
|
+
const { image, prompt, region, ...videoOptions } = bag;
|
|
87
|
+
const span = SpanSerializer.createSpan(SpanType.MEDIA_GENERATION, "video.generate", this.buildSpanAttributes(provider, videoOptions));
|
|
85
88
|
try {
|
|
86
89
|
const handler = this.getHandler(provider);
|
|
87
90
|
if (!handler) {
|
|
@@ -105,7 +108,10 @@ export class VideoProcessor {
|
|
|
105
108
|
});
|
|
106
109
|
}
|
|
107
110
|
logger.debug(`[VideoProcessor] Starting video generation with provider: ${provider}`);
|
|
108
|
-
|
|
111
|
+
// Bounded per repo guideline (async provider calls wrap withTimeout):
|
|
112
|
+
// video generation is legitimately slow, so the deadline is generous —
|
|
113
|
+
// but a wedged handler must error, never hang the caller forever.
|
|
114
|
+
const result = await withTimeout(handler.generate(image, prompt, videoOptions, region), VIDEO_GENERATION_TIMEOUT_MS, `Video generation via "${provider}" timed out after ${VIDEO_GENERATION_TIMEOUT_MS}ms`);
|
|
109
115
|
const ended = SpanSerializer.endSpan(span, SpanStatus.OK);
|
|
110
116
|
getMetricsAggregator().recordSpan(ended);
|
|
111
117
|
logger.info(`[VideoProcessor] Generated ${result.data.length} bytes (${provider})`);
|
|
@@ -124,7 +130,7 @@ export class VideoProcessor {
|
|
|
124
130
|
category: ErrorCategory.EXECUTION,
|
|
125
131
|
severity: ErrorSeverity.HIGH,
|
|
126
132
|
retriable: true,
|
|
127
|
-
context: { provider, options, region },
|
|
133
|
+
context: { provider, options: videoOptions, region },
|
|
128
134
|
originalError: err instanceof Error ? err : undefined,
|
|
129
135
|
});
|
|
130
136
|
}
|
|
@@ -169,7 +175,8 @@ export class VideoProcessor {
|
|
|
169
175
|
});
|
|
170
176
|
}
|
|
171
177
|
try {
|
|
172
|
-
|
|
178
|
+
// Same bound as generate(): a wedged transition must error, not hang.
|
|
179
|
+
return await withTimeout(handler.generateTransition(firstFrame, lastFrame, prompt, options, region), VIDEO_GENERATION_TIMEOUT_MS, `Video transition via "${provider}" timed out after ${VIDEO_GENERATION_TIMEOUT_MS}ms`);
|
|
173
180
|
}
|
|
174
181
|
catch (err) {
|
|
175
182
|
if (err instanceof VideoError) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.29.0",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"test:context": "pnpm exec tsx test/continuous-test-suite-context.ts",
|
|
74
74
|
"test:evaluation": "pnpm exec tsx test/continuous-test-suite-evaluation.ts",
|
|
75
75
|
"test:handler-registry": "pnpm exec tsx test/continuous-test-suite-handler-registry.ts",
|
|
76
|
+
"test:resolve-request-kind": "pnpm exec tsx test/continuous-test-suite-resolve-request-kind.ts",
|
|
76
77
|
"test:mcp": "pnpm exec tsx test/continuous-test-suite-mcp-infra.ts",
|
|
77
78
|
"test:tool-resolution": "pnpm exec tsx test/continuous-test-suite-tool-resolution.ts",
|
|
78
79
|
"test:mcp:http": "pnpm exec tsx test/continuous-test-suite-mcp-http.ts",
|