@vanillaskyai/video 0.7.1 → 0.8.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 +59 -0
- package/PUBLIC-API.md +83 -79
- package/README.md +16 -15
- package/dist/{builtin-server-KB7FKF6A.js → builtin-server-CUYPIDE7.js} +2 -2
- package/dist/check-runtime.js +1 -1
- package/dist/{chunk-2PYO6VAC.js → chunk-2DOLF4QN.js} +1 -43
- package/dist/{chunk-GAIOHGNR.js → chunk-2OKB27TG.js} +42 -45
- package/dist/{chunk-K746NUIP.js → chunk-2USVB3CY.js} +80 -29
- package/dist/{chunk-RE4IMWJR.js → chunk-3VRMKFM7.js} +87 -14
- package/dist/{chunk-JNN3EYVP.js → chunk-4UNKZEAT.js} +46 -46
- package/dist/{chunk-2XT4MZ76.js → chunk-APQKY6P3.js} +36 -3
- package/dist/{chunk-LLPMVDJZ.js → chunk-L7QGGKJ2.js} +69 -38
- package/dist/{chunk-RGF452LL.js → chunk-OWBT45EN.js} +1 -1
- package/dist/{chunk-LKBZX7GV.js → chunk-PP3MCPN4.js} +4 -4
- package/dist/chunk-ZD2VTUYR.js +28 -0
- package/dist/cli.js +131 -24
- package/dist/{compose-video-PD6LKKRK.js → compose-video-2NFH4DZY.js} +3 -3
- package/dist/{events-B6qS1Lsb.d.ts → events-BQ6z264n.d.ts} +1 -1
- package/dist/index.d.ts +5 -67
- package/dist/index.js +2 -4
- package/dist/{kit-8g46H2RZ.d.ts → kit-tQnCPMOM.d.ts} +1 -1
- package/dist/react.d.ts +33 -53
- package/dist/react.js +642 -536
- package/dist/server.d.ts +11 -13
- package/dist/server.js +170 -81
- package/dist/template-catalog.js +1 -1
- package/dist/templates.d.ts +3 -3
- package/dist/test.d.ts +2 -2
- package/dist/test.js +3 -3
- package/dist/{text-stream-ROTNGSD4.js → text-stream-54LXABJW.js} +1 -1
- package/dist/{types-2wHBqtg8.d.ts → types-CEh9eUFu.d.ts} +1 -1
- package/dist/{types-DrABlRa7.d.ts → types-DWFO6qca.d.ts} +1 -1
- package/docs/agent-integration.md +9 -3
- package/docs/architecture.md +8 -10
- package/docs/concepts.md +16 -40
- package/docs/custom-templates.md +27 -23
- package/docs/customization.md +12 -49
- package/docs/errors.md +58 -68
- package/docs/getting-started.md +21 -14
- package/docs/media-and-audio.md +2 -2
- package/docs/performance.md +69 -0
- package/docs/persistence.md +12 -27
- package/docs/production.md +2 -2
- package/docs/provider-integration.md +11 -5
- package/docs/reference/provider-adapters.md +37 -89
- package/docs/security.md +1 -1
- package/docs/streaming-protocol.md +2 -2
- package/examples/custom-template/README.md +6 -6
- package/package.json +6 -7
- package/registry/items/barChart.json +2 -2
- package/registry/items/bigNumber.json +2 -2
- package/registry/items/brandMessage.json +2 -2
- package/registry/items/cardList.json +2 -2
- package/registry/items/codeEditor.json +2 -2
- package/registry/items/ctaLogo.json +2 -2
- package/registry/items/ctaMedia.json +2 -2
- package/registry/items/incomingCall.json +2 -2
- package/registry/items/media.json +2 -2
- package/registry/items/milestone.json +2 -2
- package/registry/items/notification.json +2 -2
- package/registry/items/phoneMockup.json +2 -2
- package/registry/items/problemSolution.json +2 -2
- package/registry/items/progressRing.json +2 -2
- package/registry/items/promptInput.json +2 -2
- package/registry/items/reaction.json +2 -2
- package/registry/items/reviewStack.json +2 -2
- package/registry/items/steps.json +2 -2
- package/registry/items/terminal.json +2 -2
- package/registry/items/testimonial.json +2 -2
- package/registry/items/tripleStats.json +2 -2
- package/registry/items/tweet.json +2 -2
- package/registry/items/webMockup.json +2 -2
- package/starters/video-chat/.env.example +4 -2
- package/starters/video-chat/README.md +17 -5
- package/starters/video-chat/package.json +2 -4
- package/starters/video-chat/providers/speech.ts +20 -0
- package/starters/video-chat/providers/video.ts +66 -0
- package/starters/video-chat/providers.ts +3 -0
- package/starters/video-chat/server.ts +4 -80
- package/starters/video-chat/stock.ts +114 -99
- package/dist/chunk-SKRGRKHY.js +0 -142
- package/dist/state-DZcKuS32.d.ts +0 -3
- package/docs/branding-and-personalization.md +0 -87
- package/examples/custom-template/supplied-media.tsx +0 -92
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as VideoFinishReason, V as VideoWarning, b as VideoEvent } from './events-
|
|
3
|
-
export { c as VideoWarningCategory } from './events-
|
|
1
|
+
import { m as VideoGenerationContext, n as VideoPlanner, o as VideoRequest, a as VideoCapabilities, g as VideoInput, d as VideoAudio, p as VideoSceneValidator, j as VideoTemplatePacing, q as VideoSnapshotRetention, r as VideoResumeCursor, s as VideoSceneValidationContext, e as VideoScene, b as VideoOrientation } from './types-CEh9eUFu.js';
|
|
2
|
+
import { a as VideoFinishReason, V as VideoWarning, b as VideoEvent } from './events-BQ6z264n.js';
|
|
3
|
+
export { c as VideoWarningCategory } from './events-BQ6z264n.js';
|
|
4
4
|
import { S as SceneTemplateMetadata } from './catalog-types-BIhSpOWK.js';
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
5
|
+
import { f as VideoChatWelcomeOptions } from './types-DWFO6qca.js';
|
|
6
|
+
export { d as VideoChatCapabilities, g as VideoChatConversationTurn, V as VideoChatMode, h as VideoChatWelcomePrompt } from './types-DWFO6qca.js';
|
|
7
7
|
|
|
8
8
|
interface VideoProviderUsage {
|
|
9
9
|
inputTokens?: number;
|
|
@@ -104,7 +104,6 @@ interface VideoStreamHandlerOptions {
|
|
|
104
104
|
signal: AbortSignal;
|
|
105
105
|
}) => AsyncIterable<VideoEvent>;
|
|
106
106
|
}
|
|
107
|
-
type VideoStreamHandler = (request: Request) => Promise<Response>;
|
|
108
107
|
|
|
109
108
|
interface ServerTemplateRegistry {
|
|
110
109
|
readonly capabilities: VideoCapabilities;
|
|
@@ -185,11 +184,6 @@ interface VideoHandlerOptions extends Omit<VideoStreamHandlerOptions, "generate"
|
|
|
185
184
|
*/
|
|
186
185
|
narrate?: boolean;
|
|
187
186
|
}
|
|
188
|
-
/**
|
|
189
|
-
* Build the secure route for a template kit while leaving provider, model,
|
|
190
|
-
* credentials, authentication, and deployment ownership with the app.
|
|
191
|
-
*/
|
|
192
|
-
declare function createVideoHandler(options: VideoHandlerOptions): VideoStreamHandler;
|
|
193
187
|
|
|
194
188
|
type VideoChatTextTask = "narration" | "suggestions";
|
|
195
189
|
interface VideoChatTextContext {
|
|
@@ -224,9 +218,11 @@ interface VideoChatMediaContext {
|
|
|
224
218
|
scene?: Readonly<VideoScene>;
|
|
225
219
|
templateId?: string;
|
|
226
220
|
preferredType?: "image" | "video" | "any";
|
|
221
|
+
/** Optional broader subject for atmospheric opening stock, not instructional footage. */
|
|
222
|
+
fallbackQuery?: string;
|
|
227
223
|
}
|
|
228
224
|
type VideoChatMediaResolver = (query: string, context: VideoChatMediaContext) => ResolvedMedia | null | Promise<ResolvedMedia | null>;
|
|
229
|
-
interface VideoChatHandlerOptions extends
|
|
225
|
+
interface VideoChatHandlerOptions extends Pick<VideoHandlerOptions, "templates" | "streamText" | "includeRawProviderData" | "mediaConcurrency" | "allowedOrigins" | "authorize" | "maxBodyBytes" | "heartbeatMs" | "onError" | "onWarning" | "onComplete" | "invalidPartBehavior" | "requireCloser" | "allowCredentials"> {
|
|
230
226
|
/** Generate the small non-streaming text tasks around the visual plan. */
|
|
231
227
|
generateText: VideoChatTextGenerator;
|
|
232
228
|
/** Optional generated speech. Browsers can speak locally when absent. */
|
|
@@ -237,6 +233,8 @@ interface VideoChatHandlerOptions extends Omit<VideoHandlerOptions, "allowMediaU
|
|
|
237
233
|
searchMedia?: VideoChatMediaResolver;
|
|
238
234
|
/** Optional generated-video provider. Its presence enables paid visual modes. */
|
|
239
235
|
generateVideo?: VideoChatMediaResolver;
|
|
236
|
+
/** Maximum generated-video attempts per response, including failures. Defaults to 5. */
|
|
237
|
+
maxGeneratedVideos?: number;
|
|
240
238
|
/** Trusted application guidance appended to the general-purpose response brief. */
|
|
241
239
|
instructions?: string;
|
|
242
240
|
/** Application-owned prompts and visual searches shown before the first turn. */
|
|
@@ -255,4 +253,4 @@ type VideoChatHandler = (request: Request) => Promise<Response>;
|
|
|
255
253
|
*/
|
|
256
254
|
declare function createVideoChatHandler(options: VideoChatHandlerOptions): VideoChatHandler;
|
|
257
255
|
|
|
258
|
-
export {
|
|
256
|
+
export { SceneTemplateMetadata as ServerTemplateMetadata, type ServerTemplateRegistry, type VideoChatHandlerOptions, VideoChatWelcomeOptions, VideoFinishReason, type VideoGenerationSummary, type VideoProviderUsage, VideoWarning, createServerTemplateRegistry, createVideoChatHandler };
|
package/dist/server.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createTextDeltaVideoPlanner
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-L7QGGKJ2.js";
|
|
4
4
|
import {
|
|
5
|
-
createVideo
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
createVideo,
|
|
6
|
+
invokeIsolated
|
|
7
|
+
} from "./chunk-3VRMKFM7.js";
|
|
8
|
+
import {
|
|
9
|
+
getGenerationLifecycleSink
|
|
10
|
+
} from "./chunk-E7CL7UPB.js";
|
|
8
11
|
import {
|
|
9
12
|
BUILTIN_SERVER_TEMPLATE_KIT,
|
|
10
13
|
createServerTemplateRegistry,
|
|
11
14
|
overlayServerTemplateRegistry
|
|
12
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-OWBT45EN.js";
|
|
13
16
|
import {
|
|
14
17
|
createTemplateSceneValidator
|
|
15
18
|
} from "./chunk-LMG3GHLL.js";
|
|
@@ -17,9 +20,10 @@ import {
|
|
|
17
20
|
decodeVideoSse,
|
|
18
21
|
encodeVideoSseEvent,
|
|
19
22
|
sanitizeVideoChatMedia,
|
|
20
|
-
videoSseHeaders
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
videoSseHeaders,
|
|
24
|
+
withDeadline
|
|
25
|
+
} from "./chunk-APQKY6P3.js";
|
|
26
|
+
import "./chunk-2OKB27TG.js";
|
|
23
27
|
import {
|
|
24
28
|
allowedKeys,
|
|
25
29
|
createClippedVariableWarning,
|
|
@@ -33,7 +37,7 @@ import {
|
|
|
33
37
|
} from "./chunk-MMUXVA47.js";
|
|
34
38
|
import {
|
|
35
39
|
MAX_RETAINED_MEDIA_URL_LENGTH
|
|
36
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-2DOLF4QN.js";
|
|
37
41
|
import {
|
|
38
42
|
VIDEO_PROTOCOL_VERSION
|
|
39
43
|
} from "./chunk-AOWSXU2K.js";
|
|
@@ -43,7 +47,7 @@ import {
|
|
|
43
47
|
} from "./chunk-AITKH6QT.js";
|
|
44
48
|
import "./chunk-5SLENAJW.js";
|
|
45
49
|
import "./chunk-352ZZCET.js";
|
|
46
|
-
import "./chunk-
|
|
50
|
+
import "./chunk-4UNKZEAT.js";
|
|
47
51
|
import "./chunk-3O7OMMMF.js";
|
|
48
52
|
import "./chunk-2E6T633S.js";
|
|
49
53
|
import {
|
|
@@ -471,9 +475,6 @@ function fallbackVariables(variables) {
|
|
|
471
475
|
fallback.mediaType = "gradient";
|
|
472
476
|
return fallback;
|
|
473
477
|
}
|
|
474
|
-
function isAbortError(error) {
|
|
475
|
-
return error instanceof Error && error.name === "AbortError";
|
|
476
|
-
}
|
|
477
478
|
async function resolveVariables(options) {
|
|
478
479
|
if (!Object.hasOwn(options.variables, "mediaKeyword")) return options.variables;
|
|
479
480
|
const variables = { ...options.variables };
|
|
@@ -484,6 +485,10 @@ async function resolveVariables(options) {
|
|
|
484
485
|
return fallbackVariables(variables);
|
|
485
486
|
}
|
|
486
487
|
if (options.claimBudget && !options.claimBudget()) return fallbackVariables(variables);
|
|
488
|
+
const fallback = () => {
|
|
489
|
+
options.onFallback?.();
|
|
490
|
+
return fallbackVariables(variables);
|
|
491
|
+
};
|
|
487
492
|
let resolved;
|
|
488
493
|
try {
|
|
489
494
|
resolved = await options.resolveMedia(rawQuery, {
|
|
@@ -495,28 +500,27 @@ async function resolveVariables(options) {
|
|
|
495
500
|
generatedLook: options.input.style?.generatedLook,
|
|
496
501
|
signal: options.signal
|
|
497
502
|
});
|
|
503
|
+
if (options.signal.aborted) {
|
|
504
|
+
throw options.signal.reason ?? new DOMException("The media request was aborted", "AbortError");
|
|
505
|
+
}
|
|
506
|
+
if (!resolved) return fallback();
|
|
507
|
+
if (resolved.type !== "image" && resolved.type !== "video") {
|
|
508
|
+
throw new Error("Resolved media type is invalid");
|
|
509
|
+
}
|
|
510
|
+
const url = cleanUrl(resolved.url, "URL");
|
|
511
|
+
if (!url) throw new Error("Resolved media URL is required");
|
|
512
|
+
const posterUrl = options.contract.acceptsPoster ? cleanUrl(resolved.posterUrl, "poster URL") : void 0;
|
|
513
|
+
options.approveUrl(options.input, url);
|
|
514
|
+
if (posterUrl) options.approveUrl(options.input, posterUrl);
|
|
515
|
+
variables.mediaUrl = url;
|
|
516
|
+
variables.mediaType = resolved.type === "image" ? "photo" : "video";
|
|
517
|
+
if (posterUrl) variables.mediaPoster = posterUrl;
|
|
518
|
+
else delete variables.mediaPoster;
|
|
519
|
+
return variables;
|
|
498
520
|
} catch (error) {
|
|
499
521
|
if (options.signal.aborted) throw options.signal.reason ?? error;
|
|
500
|
-
|
|
501
|
-
return fallbackVariables(variables);
|
|
502
|
-
}
|
|
503
|
-
if (options.signal.aborted) {
|
|
504
|
-
throw options.signal.reason ?? new DOMException("The media request was aborted", "AbortError");
|
|
505
|
-
}
|
|
506
|
-
if (!resolved) return fallbackVariables(variables);
|
|
507
|
-
if (resolved.type !== "image" && resolved.type !== "video") {
|
|
508
|
-
throw new Error("Resolved media type is invalid");
|
|
522
|
+
return fallback();
|
|
509
523
|
}
|
|
510
|
-
const url = cleanUrl(resolved.url, "URL");
|
|
511
|
-
if (!url) throw new Error("Resolved media URL is required");
|
|
512
|
-
const posterUrl = options.contract.acceptsPoster ? cleanUrl(resolved.posterUrl, "poster URL") : void 0;
|
|
513
|
-
options.approveUrl(options.input, url);
|
|
514
|
-
if (posterUrl) options.approveUrl(options.input, posterUrl);
|
|
515
|
-
variables.mediaUrl = url;
|
|
516
|
-
variables.mediaType = resolved.type === "image" ? "photo" : "video";
|
|
517
|
-
if (posterUrl) variables.mediaPoster = posterUrl;
|
|
518
|
-
else delete variables.mediaPoster;
|
|
519
|
-
return variables;
|
|
520
524
|
}
|
|
521
525
|
function templateContract(templates, templateId) {
|
|
522
526
|
const metadata = templates.getTemplateMetadata(templateId);
|
|
@@ -544,7 +548,8 @@ async function resolvePartVariables(options) {
|
|
|
544
548
|
resolveMedia: options.resolveMedia,
|
|
545
549
|
approveUrl: options.approveUrl,
|
|
546
550
|
openingReady: options.openingReady,
|
|
547
|
-
claimBudget: options.claimBudget
|
|
551
|
+
claimBudget: options.claimBudget,
|
|
552
|
+
onFallback: options.onFallback
|
|
548
553
|
};
|
|
549
554
|
if (options.part.type === "scene.add") {
|
|
550
555
|
const variables = await resolveVariables({
|
|
@@ -583,7 +588,13 @@ function createMediaResolvingPlanner(options) {
|
|
|
583
588
|
resolveMedia: options.resolveMedia,
|
|
584
589
|
approveUrl: options.approveUrl,
|
|
585
590
|
openingReady: openingReady2 ?? options.isOpeningReady(context.request.input),
|
|
586
|
-
claimBudget
|
|
591
|
+
claimBudget,
|
|
592
|
+
onFallback: () => getGenerationLifecycleSink(context)?.reportWarning?.({
|
|
593
|
+
code: "provider_warning",
|
|
594
|
+
category: "provider",
|
|
595
|
+
message: "Some visuals use a simple background.",
|
|
596
|
+
recoverable: true
|
|
597
|
+
})
|
|
587
598
|
});
|
|
588
599
|
if (limit === 1) {
|
|
589
600
|
for await (const part of options.planner(context)) yield await resolveOne(part);
|
|
@@ -688,22 +699,36 @@ function boundSceneVariables(templates, templateId, variables) {
|
|
|
688
699
|
}
|
|
689
700
|
function createBoundedVariablePlanner(options) {
|
|
690
701
|
return async function* boundPlan(context) {
|
|
691
|
-
for await (
|
|
702
|
+
for await (let part of options.planner(context)) {
|
|
692
703
|
if (part.type !== "scene.add") {
|
|
693
704
|
yield part;
|
|
694
705
|
continue;
|
|
695
706
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
part.scene.templateId
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
707
|
+
try {
|
|
708
|
+
const lifecycle = getGenerationLifecycleSink(context);
|
|
709
|
+
if (!options.templates.getTemplateMetadata(part.scene.templateId) && lifecycle?.recoverGeneratedParts && (context.request.capabilities?.templates == null || context.request.capabilities.templates.includes("media"))) {
|
|
710
|
+
const copy = [part.scene.variables.texts, part.scene.variables.title, part.scene.variables.message, part.scene.narration].find((value) => typeof value === "string" && Boolean(value.trim()) && !/https?:\/\/|\b(?:api[_-]?key|authorization|secret|token|password)\s*[:=]/i.test(value));
|
|
711
|
+
if (copy) {
|
|
712
|
+
part = { ...part, scene: { ...part.scene, templateId: "media", variables: { texts: copy, mediaType: "gradient" } } };
|
|
713
|
+
lifecycle.reportWarning?.({ code: "provider_warning", category: "provider", message: "Some scenes use a simpler layout.", recoverable: true });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const { variables, clipped } = boundSceneVariables(
|
|
717
|
+
options.templates,
|
|
718
|
+
part.scene.templateId,
|
|
719
|
+
part.scene.variables
|
|
720
|
+
);
|
|
721
|
+
if (clipped.length === 0) {
|
|
722
|
+
yield part;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const templateId = part.scene.templateId;
|
|
726
|
+
invokeIsolated(options.onClipped && (() => options.onClipped(templateId, clipped)), void 0);
|
|
727
|
+
yield { ...part, scene: { ...part.scene, variables } };
|
|
728
|
+
} catch (cause) {
|
|
729
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
730
|
+
if (!getGenerationLifecycleSink(context)?.rejectPart?.(error)) throw error;
|
|
704
731
|
}
|
|
705
|
-
options.onClipped?.(part.scene.templateId, clipped);
|
|
706
|
-
yield { ...part, scene: { ...part.scene, variables } };
|
|
707
732
|
}
|
|
708
733
|
};
|
|
709
734
|
}
|
|
@@ -740,7 +765,8 @@ function createVideoHandler(options) {
|
|
|
740
765
|
const planner = createBoundedVariablePlanner({
|
|
741
766
|
planner: createTextDeltaVideoPlanner({ streamText, includeRawProviderData }),
|
|
742
767
|
templates,
|
|
743
|
-
onClipped: (templateId, fields) =>
|
|
768
|
+
onClipped: (templateId, fields) => invokeIsolated(
|
|
769
|
+
handlerOptions.onWarning,
|
|
744
770
|
createClippedVariableWarning(templateId, fields)
|
|
745
771
|
)
|
|
746
772
|
});
|
|
@@ -768,7 +794,7 @@ function createVideoHandler(options) {
|
|
|
768
794
|
// The ceiling is a spend policy, so it is reported to the application
|
|
769
795
|
// that set it rather than to the browser, which cannot act on it.
|
|
770
796
|
onBudgetReached: maxResolvedMedia === void 0 ? void 0 : () => {
|
|
771
|
-
|
|
797
|
+
invokeIsolated(handlerOptions.onWarning, createMediaBudgetWarning(maxResolvedMedia));
|
|
772
798
|
},
|
|
773
799
|
// The same rule the scene validator applies, answered from the part
|
|
774
800
|
// rather than from what the validator has already seen.
|
|
@@ -804,13 +830,15 @@ function createVideoHandler(options) {
|
|
|
804
830
|
|
|
805
831
|
// src/server/video-chat-prompts.ts
|
|
806
832
|
var RESPONSE_SCENES = 5;
|
|
807
|
-
function createVideoChatResponseInstructions(fullAiVideo, openingAlreadyProvided = false) {
|
|
833
|
+
function createVideoChatResponseInstructions(fullAiVideo, openingAlreadyProvided = false, maxGeneratedVideos = 5) {
|
|
808
834
|
return [
|
|
809
835
|
"The input is a prompt from a user. Respond as a short, coherent video.",
|
|
810
836
|
"Honor the requested purpose and tone. A story should feel like a story, a recommendation should be useful, an explanation should be clear, and a creative request should not be turned into a lecture.",
|
|
811
|
-
fullAiVideo ? 'Your first output line must be exactly one host-consumed JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"2-4 concrete words","firstShot":{"text":"2-7 words","narration":"10-16 spoken words","mediaKeyword":"2-8 concrete words"}}. This leading object is the one explicit exception to the standard plan-part list and is consumed before plan validation.' : openingAlreadyProvided ? "The host has already started the supplied OPENING ALREADY SPOKEN. Start directly with scene.add plan parts; do not emit a video-chat.opening object." : 'Your first output line must be exactly one host-consumed JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"2-4 concrete words"}. This leading object is the one explicit exception to the standard plan-part list and is consumed before plan validation.',
|
|
837
|
+
fullAiVideo ? 'Your first output line must be exactly one host-consumed JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"2-4 concrete words","fallbackKeyword":"2-4 broader concrete words","firstShot":{"text":"2-7 words","narration":"10-16 spoken words","mediaKeyword":"2-8 concrete words"}}. This leading object is the one explicit exception to the standard plan-part list and is consumed before plan validation.' : openingAlreadyProvided ? "The host has already started the supplied OPENING ALREADY SPOKEN. Start directly with scene.add plan parts; do not emit a video-chat.opening object." : 'Your first output line must be exactly one host-consumed JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"2-4 concrete words","fallbackKeyword":"2-4 broader concrete words"}. This leading object is the one explicit exception to the standard plan-part list and is consumed before plan validation.',
|
|
838
|
+
"For the opening only, fallbackKeyword names a broader real-world subject that can provide atmosphere if the specific stock search misses. It must not pretend to demonstrate specialized instructions. For example, Minecraft castle can broaden to medieval castle for atmosphere. Do not broaden body-scene subjects into misleading demonstrations.",
|
|
812
839
|
fullAiVideo && openingAlreadyProvided ? "Copy the supplied OPENING ALREADY SPOKEN exactly into spokenHook; do not replace or paraphrase it." : void 0,
|
|
813
840
|
fullAiVideo ? "The host inserts firstShot as the first body scene and begins generating it immediately. After the opening object, emit exactly four additional scenes that continue it. Never emit, repeat, paraphrase, or replace firstShot as a scene." : `After the opening instruction above, use exactly ${RESPONSE_SCENES} scenes and build a progression that fits the request:`,
|
|
841
|
+
fullAiVideo ? `The response has a budget of ${maxGeneratedVideos} generated-video attempts, including the host-inserted firstShot. The first ${maxGeneratedVideos} media requests may generate footage; all later requests use stock footage. Plan later subjects that stock footage can honestly illustrate. Keep five total scenes even when the budget is smaller; do not request extra generated scenes.` : void 0,
|
|
814
842
|
fullAiVideo ? void 0 : "1. Open directly on the strongest image, action, claim, or idea.",
|
|
815
843
|
"2. Develop it with new information or movement.",
|
|
816
844
|
"3. Deepen, complicate, or advance the response.",
|
|
@@ -996,6 +1024,7 @@ function readOpeningPlanLine(line) {
|
|
|
996
1024
|
return {
|
|
997
1025
|
line: boundedWords(parsed.spokenHook, 9, 300),
|
|
998
1026
|
keyword: boundedWords(parsed.mediaKeyword, 4, 80),
|
|
1027
|
+
...boundedWords(parsed.fallbackKeyword, 4, 80) ? { fallbackKeyword: boundedWords(parsed.fallbackKeyword, 4, 80) } : {},
|
|
999
1028
|
...firstShot ? { firstShot } : {}
|
|
1000
1029
|
};
|
|
1001
1030
|
} catch {
|
|
@@ -1144,7 +1173,8 @@ function streamVideoChatOpening(response, openingReady) {
|
|
|
1144
1173
|
type: VIDEO_CHAT_OPENING_EVENT_TYPE,
|
|
1145
1174
|
data: {
|
|
1146
1175
|
line: opening.line,
|
|
1147
|
-
...opening.keyword ? { keyword: opening.keyword } : {}
|
|
1176
|
+
...opening.keyword ? { keyword: opening.keyword } : {},
|
|
1177
|
+
...opening.fallbackKeyword ? { fallbackKeyword: opening.fallbackKeyword } : {}
|
|
1148
1178
|
}
|
|
1149
1179
|
});
|
|
1150
1180
|
sequence += 1;
|
|
@@ -1239,10 +1269,22 @@ function createVideoChatHandler(options) {
|
|
|
1239
1269
|
allowedOrigins,
|
|
1240
1270
|
allowCredentials,
|
|
1241
1271
|
mediaConcurrency = 5,
|
|
1242
|
-
|
|
1272
|
+
maxGeneratedVideos = 5
|
|
1243
1273
|
} = options;
|
|
1274
|
+
const videoOptions = {
|
|
1275
|
+
templates: options.templates,
|
|
1276
|
+
streamText: options.streamText,
|
|
1277
|
+
includeRawProviderData: options.includeRawProviderData,
|
|
1278
|
+
heartbeatMs: options.heartbeatMs,
|
|
1279
|
+
onError: options.onError,
|
|
1280
|
+
onWarning: options.onWarning,
|
|
1281
|
+
onComplete: options.onComplete,
|
|
1282
|
+
invalidPartBehavior: options.invalidPartBehavior,
|
|
1283
|
+
requireCloser: options.requireCloser
|
|
1284
|
+
};
|
|
1244
1285
|
if (!Number.isFinite(maxAudioBytes) || maxAudioBytes <= 0) throw new Error("maxAudioBytes must be positive");
|
|
1245
1286
|
if (!Number.isFinite(maxBodyBytes) || maxBodyBytes <= 0) throw new Error("maxBodyBytes must be positive");
|
|
1287
|
+
if (!Number.isSafeInteger(maxGeneratedVideos) || maxGeneratedVideos < 0) throw new Error("maxGeneratedVideos must be a nonnegative safe integer");
|
|
1246
1288
|
const capabilities2 = {
|
|
1247
1289
|
templates: true,
|
|
1248
1290
|
generatedSpeech: generateSpeech != null,
|
|
@@ -1258,20 +1300,68 @@ function createVideoChatHandler(options) {
|
|
|
1258
1300
|
const responseHandler = (requestedMode, requestId, openingProvided, openingChannel) => {
|
|
1259
1301
|
const mode = requestedMode !== "templates" && !generateVideo ? "templates" : requestedMode;
|
|
1260
1302
|
const fullAiVideo = mode === "full";
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1303
|
+
let lifecycle;
|
|
1304
|
+
let generatedAttempts = 0;
|
|
1305
|
+
const completedVideos = /* @__PURE__ */ new Map();
|
|
1306
|
+
const reusedUrls = /* @__PURE__ */ new Set();
|
|
1307
|
+
const resolveSelected = generateVideo || searchMedia ? async (query, context) => {
|
|
1308
|
+
const reuseKey = JSON.stringify([query, context.generatedLook ?? ""]);
|
|
1309
|
+
const remember = (media) => {
|
|
1310
|
+
if (media?.type === "video") completedVideos.set(reuseKey, media);
|
|
1311
|
+
return media;
|
|
1312
|
+
};
|
|
1313
|
+
const mediaContext = {
|
|
1314
|
+
purpose: "response",
|
|
1315
|
+
orientation: context.input.orientation ?? "landscape",
|
|
1316
|
+
generatedLook: context.generatedLook,
|
|
1317
|
+
signal: context.signal,
|
|
1318
|
+
requestId: context.requestId,
|
|
1319
|
+
scene: context.scene,
|
|
1320
|
+
templateId: context.templateId,
|
|
1321
|
+
preferredType: context.preferredType
|
|
1322
|
+
};
|
|
1323
|
+
const attempt = async (resolver, timeoutMs) => {
|
|
1324
|
+
context.signal.throwIfAborted();
|
|
1325
|
+
if (!resolver) return null;
|
|
1326
|
+
try {
|
|
1327
|
+
const result = sanitizeVideoChatMedia(await withDeadline(
|
|
1328
|
+
(signal) => resolver(query, { ...mediaContext, signal }),
|
|
1329
|
+
timeoutMs,
|
|
1330
|
+
context.signal
|
|
1331
|
+
));
|
|
1332
|
+
context.signal.throwIfAborted();
|
|
1333
|
+
return result;
|
|
1334
|
+
} catch (cause) {
|
|
1335
|
+
context.signal.throwIfAborted();
|
|
1336
|
+
reportError(cause);
|
|
1337
|
+
return null;
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
if (fullAiVideo) {
|
|
1341
|
+
const generated = generatedAttempts < maxGeneratedVideos ? (generatedAttempts++, await attempt(generateVideo, 15e3)) : null;
|
|
1342
|
+
if (generated) return remember(generated);
|
|
1343
|
+
lifecycle?.reportWarning?.({
|
|
1344
|
+
code: "provider_warning",
|
|
1345
|
+
category: "provider",
|
|
1346
|
+
message: "Some visuals were replaced so your response can continue.",
|
|
1347
|
+
recoverable: true
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
const stock = await attempt(searchMedia, 3e3);
|
|
1351
|
+
if (stock) return remember(stock);
|
|
1352
|
+
if (fullAiVideo) {
|
|
1353
|
+
const previous = completedVideos.get(reuseKey);
|
|
1354
|
+
if (previous && !reusedUrls.has(previous.url)) {
|
|
1355
|
+
reusedUrls.add(previous.url);
|
|
1356
|
+
return previous;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return null;
|
|
1360
|
+
} : void 0;
|
|
1272
1361
|
const handler = createVideoHandler({
|
|
1273
1362
|
...videoOptions,
|
|
1274
1363
|
streamText: (context) => {
|
|
1364
|
+
lifecycle = getGenerationLifecycleSink(context);
|
|
1275
1365
|
try {
|
|
1276
1366
|
return interceptOpeningPlan(videoOptions.streamText(context), {
|
|
1277
1367
|
expectOpening: fullAiVideo || !openingProvided,
|
|
@@ -1290,9 +1380,8 @@ function createVideoChatHandler(options) {
|
|
|
1290
1380
|
allowCredentials,
|
|
1291
1381
|
maxBodyBytes,
|
|
1292
1382
|
mediaConcurrency,
|
|
1293
|
-
basePrompt: [createVideoChatResponseInstructions(fullAiVideo, openingProvided), instructions?.trim()].filter(Boolean).join("\n\nAPPLICATION GUIDANCE\n"),
|
|
1383
|
+
basePrompt: [createVideoChatResponseInstructions(fullAiVideo, openingProvided, maxGeneratedVideos), instructions?.trim()].filter(Boolean).join("\n\nAPPLICATION GUIDANCE\n"),
|
|
1294
1384
|
narrate: true,
|
|
1295
|
-
...fullAiVideo ? { maxResolvedMedia: 5 } : {},
|
|
1296
1385
|
resolveMedia: resolveSelected
|
|
1297
1386
|
});
|
|
1298
1387
|
return handler;
|
|
@@ -1343,15 +1432,14 @@ function createVideoChatHandler(options) {
|
|
|
1343
1432
|
const mediaResolver = searchMedia;
|
|
1344
1433
|
const resolve = async (query) => {
|
|
1345
1434
|
if (!mediaResolver || !query) return null;
|
|
1346
|
-
const signal = AbortSignal.any([request.signal, AbortSignal.timeout(1e4)]);
|
|
1347
1435
|
try {
|
|
1348
|
-
const raw = await
|
|
1436
|
+
const raw = await withDeadline((signal) => mediaResolver(query, {
|
|
1349
1437
|
purpose: "welcome",
|
|
1350
1438
|
orientation: "landscape",
|
|
1351
1439
|
signal
|
|
1352
|
-
}));
|
|
1440
|
+
}), 3e3, request.signal);
|
|
1353
1441
|
const media = sanitizeVideoChatMedia(raw);
|
|
1354
|
-
if (signal.aborted || raw != null && !media) failed = true;
|
|
1442
|
+
if (request.signal.aborted || raw != null && !media) failed = true;
|
|
1355
1443
|
return media;
|
|
1356
1444
|
} catch (cause) {
|
|
1357
1445
|
failed = true;
|
|
@@ -1459,19 +1547,21 @@ function createVideoChatHandler(options) {
|
|
|
1459
1547
|
try {
|
|
1460
1548
|
if (action === "opening-media") {
|
|
1461
1549
|
const value = record2(body, "request");
|
|
1462
|
-
allowedKeys2(value, ["keyword", "orientation"], "request");
|
|
1550
|
+
allowedKeys2(value, ["keyword", "fallbackKeyword", "orientation"], "request");
|
|
1463
1551
|
const keyword = boundedString2(value.keyword, "request.keyword", 80);
|
|
1552
|
+
const fallbackQuery = value.fallbackKeyword == null ? void 0 : boundedString2(value.fallbackKeyword, "request.fallbackKeyword", 80);
|
|
1464
1553
|
const orientation = value.orientation ?? "landscape";
|
|
1465
1554
|
if (orientation !== "portrait" && orientation !== "landscape") {
|
|
1466
1555
|
throw new Error("request.orientation must be portrait or landscape");
|
|
1467
1556
|
}
|
|
1468
1557
|
if (!searchMedia) return Response.json({ media: null }, { headers });
|
|
1469
1558
|
try {
|
|
1470
|
-
const raw = await
|
|
1559
|
+
const raw = await withDeadline((signal) => searchMedia(keyword, {
|
|
1471
1560
|
purpose: "response",
|
|
1561
|
+
...fallbackQuery ? { fallbackQuery } : {},
|
|
1472
1562
|
orientation,
|
|
1473
|
-
signal
|
|
1474
|
-
}));
|
|
1563
|
+
signal
|
|
1564
|
+
}), 3e3, request.signal);
|
|
1475
1565
|
return Response.json({ media: sanitizeVideoChatMedia(raw) }, { headers });
|
|
1476
1566
|
} catch (cause) {
|
|
1477
1567
|
if (!request.signal.aborted) reportError(cause);
|
|
@@ -1502,7 +1592,7 @@ function createVideoChatHandler(options) {
|
|
|
1502
1592
|
const prompt = boundedString2(value.prompt, "request.prompt");
|
|
1503
1593
|
const lines = Array.isArray(value.lines) ? value.lines.slice(-8).map((line, index) => boundedString2(line, `request.lines[${index}]`, 2e3)) : [];
|
|
1504
1594
|
try {
|
|
1505
|
-
const text = await callText(
|
|
1595
|
+
const text = await withDeadline((signal) => callText(
|
|
1506
1596
|
"suggestions",
|
|
1507
1597
|
VIDEO_CHAT_SUGGESTIONS_PROMPT,
|
|
1508
1598
|
`USER PROMPT: ${prompt}
|
|
@@ -1510,18 +1600,18 @@ function createVideoChatHandler(options) {
|
|
|
1510
1600
|
VIDEO RESPONSE:
|
|
1511
1601
|
${lines.join("\n")}`,
|
|
1512
1602
|
512,
|
|
1513
|
-
|
|
1514
|
-
);
|
|
1603
|
+
signal
|
|
1604
|
+
), 3e3, request.signal);
|
|
1515
1605
|
const subjects = readSuggestionSubjects(text);
|
|
1516
1606
|
const mediaResolver = searchMedia;
|
|
1517
1607
|
const media = await Promise.all(subjects.map(async (subject) => {
|
|
1518
1608
|
if (!mediaResolver || !subject.keyword) return null;
|
|
1519
1609
|
try {
|
|
1520
|
-
const raw = await
|
|
1610
|
+
const raw = await withDeadline((signal) => mediaResolver(subject.keyword, {
|
|
1521
1611
|
purpose: "suggestion",
|
|
1522
1612
|
orientation: "landscape",
|
|
1523
|
-
signal
|
|
1524
|
-
}));
|
|
1613
|
+
signal
|
|
1614
|
+
}), 3e3, request.signal);
|
|
1525
1615
|
return sanitizeVideoChatMedia(raw);
|
|
1526
1616
|
} catch (cause) {
|
|
1527
1617
|
if (!request.signal.aborted) reportError(cause);
|
|
@@ -1545,7 +1635,7 @@ ${lines.join("\n")}`,
|
|
|
1545
1635
|
allowedKeys2(value, ["text"], "request");
|
|
1546
1636
|
const text = boundedString2(value.text, "request.text", 4e3);
|
|
1547
1637
|
try {
|
|
1548
|
-
const result = await generateSpeech({ text, signal
|
|
1638
|
+
const result = await withDeadline((signal) => generateSpeech({ text, signal }), 3e3, request.signal);
|
|
1549
1639
|
return new Response(audioBody(result.audio), {
|
|
1550
1640
|
headers: {
|
|
1551
1641
|
...Object.fromEntries(headers),
|
|
@@ -1569,6 +1659,5 @@ ${lines.join("\n")}`,
|
|
|
1569
1659
|
}
|
|
1570
1660
|
export {
|
|
1571
1661
|
createServerTemplateRegistry,
|
|
1572
|
-
createVideoChatHandler
|
|
1573
|
-
createVideoHandler
|
|
1662
|
+
createVideoChatHandler
|
|
1574
1663
|
};
|
package/dist/template-catalog.js
CHANGED
package/dist/templates.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { I as InferTemplateJsonSchema, S as SceneTemplateProps, a as SceneTemplate } from './kit-
|
|
2
|
-
export { T as TemplateRegistry, c as createTemplateRegistry } from './kit-
|
|
1
|
+
import { I as InferTemplateJsonSchema, S as SceneTemplateProps, a as SceneTemplate } from './kit-tQnCPMOM.js';
|
|
2
|
+
export { T as TemplateRegistry, c as createTemplateRegistry } from './kit-tQnCPMOM.js';
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
import { d as TemplateJsonSchema, S as SceneTemplateMetadata } from './catalog-types-BIhSpOWK.js';
|
|
5
5
|
export { T as TemplateFamily, c as TemplateTimingMetadata, e as TemplateTransitionTiming } from './catalog-types-BIhSpOWK.js';
|
|
6
|
-
import './types-
|
|
6
|
+
import './types-CEh9eUFu.js';
|
|
7
7
|
|
|
8
8
|
interface TemplateExample<Variables extends Record<string, unknown> = Record<string, unknown>> {
|
|
9
9
|
name: string;
|
package/dist/test.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { e as VideoScene, b as VideoOrientation, V as VideoStyle, c as Video, d as VideoAudio, g as VideoInput } from './types-
|
|
2
|
-
import { V as VideoWarning, a as VideoFinishReason } from './events-
|
|
1
|
+
import { e as VideoScene, b as VideoOrientation, V as VideoStyle, c as Video, d as VideoAudio, g as VideoInput } from './types-CEh9eUFu.js';
|
|
2
|
+
import { V as VideoWarning, a as VideoFinishReason } from './events-BQ6z264n.js';
|
|
3
3
|
|
|
4
4
|
type MockVideoStreamPart = {
|
|
5
5
|
type: "scene.add";
|
package/dist/test.js
CHANGED
|
@@ -233,9 +233,9 @@ async function* simulateVideoStream(parts, options = {}) {
|
|
|
233
233
|
if (timeoutMs != null && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
|
|
234
234
|
throw new Error("Simulation timeoutMs must be a non-negative finite number");
|
|
235
235
|
}
|
|
236
|
-
const { createVideo } = await import("./compose-video-
|
|
237
|
-
const { createTextDeltaVideoPlanner } = await import("./text-stream-
|
|
238
|
-
const { BUILTIN_SERVER_TEMPLATE_KIT } = await import("./builtin-server-
|
|
236
|
+
const { createVideo } = await import("./compose-video-2NFH4DZY.js");
|
|
237
|
+
const { createTextDeltaVideoPlanner } = await import("./text-stream-54LXABJW.js");
|
|
238
|
+
const { BUILTIN_SERVER_TEMPLATE_KIT } = await import("./builtin-server-CUYPIDE7.js");
|
|
239
239
|
const { createTemplateSceneValidator } = await import("./validate-T7GBU2YF.js");
|
|
240
240
|
const controller = new AbortController();
|
|
241
241
|
const forwardAbort = () => controller.abort(options.signal?.reason);
|
|
@@ -222,4 +222,4 @@ interface VideoSnapshotRetention {
|
|
|
222
222
|
suppliedMediaUrls?: boolean;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
export { type VideoStyle as V, type VideoCapabilities as a, type VideoOrientation as b, type Video as c, type VideoAudio as d, type VideoScene as e, VIDEO_PROTOCOL_VERSION as f, type VideoInput as g, type VideoBrandInput as h, type VideoBrand as i, type VideoTemplatePacing as j, type VideoBackground as k, type
|
|
225
|
+
export { type VideoStyle as V, type VideoCapabilities as a, type VideoOrientation as b, type Video as c, type VideoAudio as d, type VideoScene as e, VIDEO_PROTOCOL_VERSION as f, type VideoInput as g, type VideoBrandInput as h, type VideoBrand as i, type VideoTemplatePacing as j, type VideoBackground as k, type VideoStyleOptions as l, type VideoGenerationContext as m, type VideoPlanner as n, type VideoRequest as o, type VideoSceneValidator as p, type VideoSnapshotRetention as q, type VideoResumeCursor as r, type VideoSceneValidationContext as s };
|
|
@@ -43,4 +43,4 @@ interface VideoChatWelcomeOptions {
|
|
|
43
43
|
prompts?: readonly VideoChatWelcomePrompt[];
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export type {
|
|
46
|
+
export type { VideoChatMode as V, VideoChatAskOptions as a, VideoChatMedia as b, VideoChatSuggestion as c, VideoChatCapabilities as d, VideoChatWelcome as e, VideoChatWelcomeOptions as f, VideoChatConversationTurn as g, VideoChatWelcomePrompt as h };
|
|
@@ -22,14 +22,20 @@ npm run dev
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Init generates the thin application-owned shell around the SDK's complete
|
|
25
|
-
`VideoChat` experience. The default uses packaged
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
`VideoChat` experience and runs doctor automatically. The default uses packaged
|
|
26
|
+
templates + browser voice and installs no optional provider packages.
|
|
27
|
+
`ANTHROPIC_API_KEY` is the only required key. Rerun init after an interrupted
|
|
28
|
+
installation.
|
|
28
29
|
|
|
29
30
|
If doctor reports a missing key, add the named key to the ignored `.env.local`
|
|
30
31
|
yourself. Never read or print a secret value, and never paste one into the chat.
|
|
31
32
|
Doctor is the safe capability interface: it reports names and readiness only.
|
|
32
33
|
|
|
34
|
+
For requested upgrades, run `npx vanillasky providers add speech` for xAI speech
|
|
35
|
+
or `npx vanillasky providers add video` for FAL video and transcription. Have the
|
|
36
|
+
developer add `XAI_API_KEY` or `FAL_KEY` locally, then restart the server. Stock
|
|
37
|
+
media needs only `PEXELS_API_KEY`. The client does not change.
|
|
38
|
+
|
|
33
39
|
## Verify, do not merely scaffold
|
|
34
40
|
|
|
35
41
|
The agent should leave the dev server running and open its reported localhost
|