@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 +3 -3
- package/dist/adapters/video/vertexVideoHandler.d.ts +1 -0
- package/dist/adapters/video/vertexVideoHandler.js +97 -14
- package/dist/browser/neurolink.min.js +369 -369
- package/dist/core/baseProvider.js +5 -0
- package/dist/core/resolveRequestKind.d.ts +4 -5
- package/dist/core/resolveRequestKind.js +4 -5
- package/dist/providers/googleVertex/client.js +21 -9
- package/dist/providers/replicate.js +13 -13
- package/dist/types/video.d.ts +5 -0
- package/dist/utils/videoProcessor.js +27 -2
- package/package.json +2 -1
|
@@ -2176,11 +2176,16 @@ export class BaseProvider {
|
|
|
2176
2176
|
// shared timeout helper so standard video gen honors the caller's
|
|
2177
2177
|
// timeout the same way director mode does (see above ~Line 2062).
|
|
2178
2178
|
const videoTimeout = options.timeout ?? 600_000; // 10 min default
|
|
2179
|
+
// Thread the caller's cancellation signal into the handler chain —
|
|
2180
|
+
// output.video.abortSignal (video-scoped) wins over the request-level
|
|
2181
|
+
// options.abortSignal, matching the general per-field precedence.
|
|
2182
|
+
const videoAbortSignal = options.output?.video?.abortSignal ?? options.abortSignal;
|
|
2179
2183
|
const videoResult = await this.executeWithTimeout(() => VideoProcessor.generate(requestedProvider, {
|
|
2180
2184
|
...(options.output?.video ?? {}),
|
|
2181
2185
|
image: imageBuffer,
|
|
2182
2186
|
prompt,
|
|
2183
2187
|
region: options.region,
|
|
2188
|
+
abortSignal: videoAbortSignal,
|
|
2184
2189
|
}), { timeout: videoTimeout, operationType: "generate" });
|
|
2185
2190
|
// Prefer the handler's own model id (more accurate — it knows the exact
|
|
2186
2191
|
// checkpoint that ran). Fall back to the request-time value, and finally
|
|
@@ -7,11 +7,10 @@ import type { RequestKind, RequestKindInput } from "../types/index.js";
|
|
|
7
7
|
* stream()/runGenerateInActiveContext (image/video/tts-direct routing) call
|
|
8
8
|
* this instead of independently re-deriving the decision.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* googleVertex/client.ts's
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* until it lands, an edit to this precedence table does not reach them.
|
|
10
|
+
* Also the only copy at the provider-override level: replicate.ts's
|
|
11
|
+
* generate() override and googleVertex/client.ts's generate()/stream()
|
|
12
|
+
* overrides (which bypass BaseProvider's paths) call this too, so an edit
|
|
13
|
+
* to this precedence table reaches every dispatch site.
|
|
15
14
|
*
|
|
16
15
|
* Precedence, checked in order:
|
|
17
16
|
* 1. output.mode (music/avatar/video/ppt) — an explicit mode always wins.
|
|
@@ -7,11 +7,10 @@ import { isImageGenerationModel } from "./constants.js";
|
|
|
7
7
|
* stream()/runGenerateInActiveContext (image/video/tts-direct routing) call
|
|
8
8
|
* this instead of independently re-deriving the decision.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* googleVertex/client.ts's
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* until it lands, an edit to this precedence table does not reach them.
|
|
10
|
+
* Also the only copy at the provider-override level: replicate.ts's
|
|
11
|
+
* generate() override and googleVertex/client.ts's generate()/stream()
|
|
12
|
+
* overrides (which bypass BaseProvider's paths) call this too, so an edit
|
|
13
|
+
* to this precedence table reaches every dispatch site.
|
|
15
14
|
*
|
|
16
15
|
* Precedence, checked in order:
|
|
17
16
|
* 1. output.mode (music/avatar/video/ppt) — an explicit mode always wins.
|
|
@@ -8,7 +8,8 @@ import { BaseProvider } from "../../core/baseProvider.js";
|
|
|
8
8
|
import { unwrapImagePayload } from "../../adapters/imageFormatSupport.js";
|
|
9
9
|
import { appendNativeAudioParts } from "../googleNativeGemini3/utils.js";
|
|
10
10
|
import { getMimeTypeForExtension } from "../../processors/config/mimeConstants.js";
|
|
11
|
-
import { DEFAULT_GEMINI_STREAM_TIMEOUT_MS, DEFAULT_MAX_STEPS, DEFAULT_TOOL_EXECUTION_TIMEOUT_MS, DEFAULT_TOOL_MAX_RETRIES, GLOBAL_LOCATION_MODELS,
|
|
11
|
+
import { DEFAULT_GEMINI_STREAM_TIMEOUT_MS, DEFAULT_MAX_STEPS, DEFAULT_TOOL_EXECUTION_TIMEOUT_MS, DEFAULT_TOOL_MAX_RETRIES, GLOBAL_LOCATION_MODELS, TOOL_STORAGE_TIMEOUT_MS, } from "../../core/constants.js";
|
|
12
|
+
import { resolveRequestKind } from "../../core/resolveRequestKind.js";
|
|
12
13
|
import { ModelConfigurationManager } from "../../core/modelConfiguration.js";
|
|
13
14
|
import { isSchemaComplexityError } from "../../core/modules/structuredOutputPolicy.js";
|
|
14
15
|
import { redactUrlForError, stringifyContentSafe, } from "../../utils/logSanitize.js";
|
|
@@ -5089,9 +5090,11 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5089
5090
|
? { input: { text: optionsOrPrompt } }
|
|
5090
5091
|
: optionsOrPrompt;
|
|
5091
5092
|
const modelName = options.model || this.modelName || getDefaultVertexModel();
|
|
5092
|
-
//
|
|
5093
|
-
|
|
5094
|
-
|
|
5093
|
+
// Image-generation requests can't stream — fall back to generate.
|
|
5094
|
+
// Same single dispatch decision as generate(): resolveRequestKind
|
|
5095
|
+
// keeps dual-mode models streaming text when the caller explicitly
|
|
5096
|
+
// asked for a non-image output.format.
|
|
5097
|
+
if (resolveRequestKind(options, modelName) === "image") {
|
|
5095
5098
|
logger.warn("[GoogleVertex] Image generation models don't support streaming, falling back to generate", { model: modelName });
|
|
5096
5099
|
// Convert stream options to text generation options
|
|
5097
5100
|
const generateOptions = {
|
|
@@ -5150,13 +5153,20 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5150
5153
|
},
|
|
5151
5154
|
}, async (generateSpan) => {
|
|
5152
5155
|
const generateStartTime = Date.now();
|
|
5156
|
+
// One dispatch decision for the whole override: Vertex's generate()
|
|
5157
|
+
// bypasses BaseProvider.generate(), so it re-runs the same
|
|
5158
|
+
// resolveRequestKind() the core call sites use rather than keeping
|
|
5159
|
+
// a hand-rolled copy of the precedence (which had drifted: tts
|
|
5160
|
+
// checked before image, and a cruder case-insensitive startsWith
|
|
5161
|
+
// image match without boundary awareness).
|
|
5162
|
+
const requestKind = resolveRequestKind(options, modelName);
|
|
5153
5163
|
// Video-mode requests must route through BaseProvider's
|
|
5154
5164
|
// handleVideoGeneration (which loads the Veo 3 adapter). Vertex's
|
|
5155
5165
|
// native @google/genai path is text/image only — without this
|
|
5156
5166
|
// gate, video requests fall through to gemini-2.5-flash and the
|
|
5157
5167
|
// model politely declines ("I cannot create animations") instead
|
|
5158
5168
|
// of producing video bytes.
|
|
5159
|
-
if (
|
|
5169
|
+
if (requestKind === "video") {
|
|
5160
5170
|
logger.info("[GoogleVertex] Routing video-mode generate to handleVideoGeneration", { model: modelName });
|
|
5161
5171
|
const videoResult = await this.handleVideoGeneration(options, generateStartTime);
|
|
5162
5172
|
this.attachUsageAndCostAttributes(generateSpan, modelName, videoResult?.usage);
|
|
@@ -5168,15 +5178,17 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
5168
5178
|
// (synthesise the input text directly; no LLM call). BaseProvider's
|
|
5169
5179
|
// standard generate() does the same dispatch — we replicate it here
|
|
5170
5180
|
// because Vertex's override bypasses that path.
|
|
5171
|
-
if (
|
|
5181
|
+
if (requestKind === "tts-direct") {
|
|
5172
5182
|
logger.info("[GoogleVertex] Routing TTS direct-synthesis to handleDirectTTSSynthesis", { model: modelName });
|
|
5173
5183
|
const ttsResult = await this.handleDirectTTSSynthesis(options, generateStartTime);
|
|
5174
5184
|
this.emitGenerationEnd(modelName, ttsResult, generateStartTime, true);
|
|
5175
5185
|
return ttsResult;
|
|
5176
5186
|
}
|
|
5177
|
-
//
|
|
5178
|
-
|
|
5179
|
-
|
|
5187
|
+
// Image-generation models route to executeImageGeneration without
|
|
5188
|
+
// tools. resolveRequestKind also carries the dual-mode exception:
|
|
5189
|
+
// an explicit non-image output.format keeps models like
|
|
5190
|
+
// gemini-3.1-flash-image-preview on the text path.
|
|
5191
|
+
if (requestKind === "image") {
|
|
5180
5192
|
logger.info("[GoogleVertex] Routing image generation model to executeImageGeneration", { model: modelName });
|
|
5181
5193
|
const imageResult = await this.executeImageGeneration(options);
|
|
5182
5194
|
this.attachUsageAndCostAttributes(generateSpan, modelName, imageResult?.usage);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ErrorCategory, ErrorSeverity, ReplicateModels, } from "../constants/enums.js";
|
|
2
2
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
3
|
+
import { resolveRequestKind } from "../core/resolveRequestKind.js";
|
|
3
4
|
import { getReplicateAuth } from "../adapters/replicate/auth.js";
|
|
4
5
|
import { downloadPredictionOutput, predict, } from "../adapters/replicate/predictionLifecycle.js";
|
|
5
6
|
import { MAX_IMAGE_BYTES } from "../utils/sizeGuard.js";
|
|
@@ -121,19 +122,18 @@ export class ReplicateProvider extends BaseProvider {
|
|
|
121
122
|
const options = typeof optionsOrPrompt === "string"
|
|
122
123
|
? { prompt: optionsOrPrompt }
|
|
123
124
|
: optionsOrPrompt;
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (isImageModel && !requestsNonImageOutput) {
|
|
125
|
+
// Delegate media kinds to the base class (which never calls
|
|
126
|
+
// getAISDKModel for these). resolveRequestKind owns the precedence —
|
|
127
|
+
// including the dual-mode exception where an explicit non-image
|
|
128
|
+
// output.format keeps an image-gen model on the text path. "ppt" and
|
|
129
|
+
// "tts-direct" deliberately stay on the local text path below:
|
|
130
|
+
// super.generate() would hit prepareGenerationContext() →
|
|
131
|
+
// getAISDKModel(), which throws for Replicate.
|
|
132
|
+
const kind = resolveRequestKind(options, this.modelName);
|
|
133
|
+
if (kind === "video" ||
|
|
134
|
+
kind === "avatar" ||
|
|
135
|
+
kind === "music" ||
|
|
136
|
+
kind === "image") {
|
|
137
137
|
return super.generate(options, _analysisSchema);
|
|
138
138
|
}
|
|
139
139
|
// Structured / JSON output is not natively supported by the Replicate
|
package/dist/types/video.d.ts
CHANGED
|
@@ -34,6 +34,11 @@ export type VideoGenerateOptions = VideoOutputOptions & {
|
|
|
34
34
|
* `generateTransition` method on `VideoHandler`.
|
|
35
35
|
*/
|
|
36
36
|
export type VideoTransitionOptions = {
|
|
37
|
+
/**
|
|
38
|
+
* Per-call cancellation signal forwarded to provider requests and polling
|
|
39
|
+
* loops — same contract as `VideoOutputOptions.abortSignal`.
|
|
40
|
+
*/
|
|
41
|
+
abortSignal?: AbortSignal;
|
|
37
42
|
aspectRatio?: "9:16" | "16:9" | "1:1" | string;
|
|
38
43
|
resolution?: "720p" | "1080p";
|
|
39
44
|
audio?: boolean;
|
|
@@ -84,6 +84,16 @@ export class VideoProcessor {
|
|
|
84
84
|
}
|
|
85
85
|
: optionsOrImage;
|
|
86
86
|
const { image, prompt, region, ...videoOptions } = bag;
|
|
87
|
+
// A fired timeout must also cancel the handler's own request/polling
|
|
88
|
+
// loop — otherwise the caller sees the rejection while a ghost
|
|
89
|
+
// generation keeps polling (and possibly billing) for the rest of the
|
|
90
|
+
// render. Chain the internal controller onto any caller-supplied
|
|
91
|
+
// signal so both cancellation sources reach the handler.
|
|
92
|
+
const timeoutAbort = new AbortController();
|
|
93
|
+
const abortSignal = videoOptions.abortSignal
|
|
94
|
+
? AbortSignal.any([videoOptions.abortSignal, timeoutAbort.signal])
|
|
95
|
+
: timeoutAbort.signal;
|
|
96
|
+
const handlerOptions = { ...videoOptions, abortSignal };
|
|
87
97
|
const span = SpanSerializer.createSpan(SpanType.MEDIA_GENERATION, "video.generate", this.buildSpanAttributes(provider, videoOptions));
|
|
88
98
|
try {
|
|
89
99
|
const handler = this.getHandler(provider);
|
|
@@ -111,13 +121,17 @@ export class VideoProcessor {
|
|
|
111
121
|
// Bounded per repo guideline (async provider calls wrap withTimeout):
|
|
112
122
|
// video generation is legitimately slow, so the deadline is generous —
|
|
113
123
|
// but a wedged handler must error, never hang the caller forever.
|
|
114
|
-
const result = await withTimeout(handler.generate(image, prompt,
|
|
124
|
+
const result = await withTimeout(handler.generate(image, prompt, handlerOptions, region), VIDEO_GENERATION_TIMEOUT_MS, `Video generation via "${provider}" timed out after ${VIDEO_GENERATION_TIMEOUT_MS}ms`);
|
|
115
125
|
const ended = SpanSerializer.endSpan(span, SpanStatus.OK);
|
|
116
126
|
getMetricsAggregator().recordSpan(ended);
|
|
117
127
|
logger.info(`[VideoProcessor] Generated ${result.data.length} bytes (${provider})`);
|
|
118
128
|
return result;
|
|
119
129
|
}
|
|
120
130
|
catch (err) {
|
|
131
|
+
// Cancel the ghost: on timeout the handler promise is still pending;
|
|
132
|
+
// aborting here stops its polling loop. On handler-originated errors
|
|
133
|
+
// the promise has already settled, so the abort is a no-op.
|
|
134
|
+
timeoutAbort.abort();
|
|
121
135
|
const ended = SpanSerializer.endSpan(span, SpanStatus.ERROR, err instanceof Error ? err.message : String(err));
|
|
122
136
|
getMetricsAggregator().recordSpan(ended);
|
|
123
137
|
if (err instanceof VideoError) {
|
|
@@ -174,11 +188,22 @@ export class VideoProcessor {
|
|
|
174
188
|
context: { provider },
|
|
175
189
|
});
|
|
176
190
|
}
|
|
191
|
+
// Same ghost-cancellation contract as generate(): a fired timeout
|
|
192
|
+
// aborts the handler's polling loop, chained onto any caller signal.
|
|
193
|
+
const timeoutAbort = new AbortController();
|
|
194
|
+
const abortSignal = options?.abortSignal
|
|
195
|
+
? AbortSignal.any([options.abortSignal, timeoutAbort.signal])
|
|
196
|
+
: timeoutAbort.signal;
|
|
197
|
+
const handlerOptions = {
|
|
198
|
+
...(options ?? {}),
|
|
199
|
+
abortSignal,
|
|
200
|
+
};
|
|
177
201
|
try {
|
|
178
202
|
// Same bound as generate(): a wedged transition must error, not hang.
|
|
179
|
-
return await withTimeout(handler.generateTransition(firstFrame, lastFrame, prompt,
|
|
203
|
+
return await withTimeout(handler.generateTransition(firstFrame, lastFrame, prompt, handlerOptions, region), VIDEO_GENERATION_TIMEOUT_MS, `Video transition via "${provider}" timed out after ${VIDEO_GENERATION_TIMEOUT_MS}ms`);
|
|
180
204
|
}
|
|
181
205
|
catch (err) {
|
|
206
|
+
timeoutAbort.abort();
|
|
182
207
|
if (err instanceof VideoError) {
|
|
183
208
|
throw err;
|
|
184
209
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "11.29.
|
|
3
|
+
"version": "11.29.2",
|
|
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": {
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
"test:skills": "pnpm exec tsx test/continuous-test-suite-skills.ts",
|
|
115
115
|
"test:servers": "pnpm exec tsx test/continuous-test-suite-servers.ts",
|
|
116
116
|
"test:tool-reliability": "pnpm exec tsx test/continuous-test-suite-tool-reliability.ts",
|
|
117
|
+
"test:video-abort": "pnpm exec tsx test/continuous-test-suite-video-abort.ts",
|
|
117
118
|
"test:tts": "pnpm exec tsx test/continuous-test-suite-tts.ts",
|
|
118
119
|
"test:tts:unit": "pnpm exec tsx test/continuous-test-suite-tts-unit.ts",
|
|
119
120
|
"test:stt:unit": "pnpm exec tsx test/continuous-test-suite-stt-unit.ts",
|