@vanillaskyai/video 0.1.1 → 0.3.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 +35 -1
- package/PUBLIC-API.md +20 -2
- package/README.md +14 -6
- package/dist/{builtin-server-F7YXRVCH.js → builtin-server-BRTZN4Q7.js} +2 -2
- package/dist/check-runtime.js +1 -1
- package/dist/{chunk-EVG2ZYIY.js → chunk-3O7OMMMF.js} +7 -4
- package/dist/{chunk-GRMUV7QY.js → chunk-BKF3A357.js} +1 -1
- package/dist/{chunk-YC5MFXA5.js → chunk-GR4I3MN2.js} +37 -15
- package/dist/{chunk-A2X45FET.js → chunk-I5YWVQ5R.js} +1 -1
- package/dist/{chunk-ERY4S6QV.js → chunk-JW47XCRL.js} +6 -1
- package/dist/{chunk-XB7LH4J2.js → chunk-QTMHS7JD.js} +2 -1
- package/dist/{chunk-W2XJKG6C.js → chunk-RBQN3VSY.js} +2 -2
- package/dist/{chunk-OOPIETLW.js → chunk-S5ZU5WUU.js} +1 -1
- package/dist/{chunk-ABPKM4GK.js → chunk-STIFILQG.js} +1 -1
- package/dist/chunk-WHPZED7X.js +57 -0
- package/dist/{chunk-CHLGW22F.js → chunk-XAMUOSX7.js} +3 -3
- package/dist/{chunk-IXCUY3B3.js → chunk-XYZOJ5NZ.js} +195 -52
- package/dist/cli.js +5 -5
- package/dist/{compose-video-CN3VSWST.js → compose-video-TQOHDXNW.js} +4 -4
- package/dist/{events-_wfjbmEp.d.ts → events-CTIsANzz.d.ts} +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/{kit-BMNcHqa9.d.ts → kit-DA2cfJ96.d.ts} +1 -1
- package/dist/react.d.ts +7 -4
- package/dist/react.js +226 -30
- package/dist/server.d.ts +21 -4
- package/dist/server.js +205 -18
- package/dist/system-prompt-NMYHLAMJ.js +10 -0
- package/dist/template-catalog.js +1 -1
- package/dist/templates.d.ts +3 -3
- package/dist/test.d.ts +3 -2
- package/dist/test.js +3 -3
- package/dist/{text-stream-6XHC7J47.js → text-stream-UPGUD2TD.js} +2 -2
- package/dist/{types-B-PZR6G_.d.ts → types-CkO2EYr4.d.ts} +8 -4
- package/docs/concepts.md +11 -1
- package/docs/custom-templates.md +34 -6
- package/docs/customization.md +2 -1
- package/docs/errors.md +3 -1
- package/docs/getting-started.md +13 -10
- package/docs/input-and-first-scene.md +17 -6
- package/docs/integrate-nextjs.md +6 -3
- package/docs/media-and-audio.md +73 -14
- package/docs/prompt-and-input.md +27 -5
- package/docs/provider-integration.md +45 -2
- package/docs/reference/protocol.md +9 -0
- package/examples/nextjs-quickstart/README.md +3 -1
- package/examples/nextjs-quickstart/package.json +3 -2
- package/examples/nextjs-quickstart/src/app/api/video/route.ts +1 -1
- package/package.json +1 -1
- package/registry/items/brandMessage.json +1 -2
- package/registry/items/media.json +2 -1
- package/dist/chunk-FQ47WXAJ.js +0 -38
- package/dist/system-prompt-A6UXNTPK.js +0 -8
package/dist/server.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createTextDeltaVideoPlanner
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BKF3A357.js";
|
|
4
4
|
import {
|
|
5
5
|
createVideo
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XYZOJ5NZ.js";
|
|
7
7
|
import "./chunk-E7CL7UPB.js";
|
|
8
8
|
import {
|
|
9
9
|
BUILTIN_SERVER_TEMPLATE_KIT,
|
|
10
10
|
createServerTemplateRegistry,
|
|
11
11
|
overlayServerTemplateRegistry
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-I5YWVQ5R.js";
|
|
13
13
|
import {
|
|
14
14
|
createTemplateSceneValidator
|
|
15
15
|
} from "./chunk-LMG3GHLL.js";
|
|
16
16
|
import {
|
|
17
17
|
encodeVideoSseEvent,
|
|
18
18
|
videoSseHeaders
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-S5ZU5WUU.js";
|
|
20
|
+
import "./chunk-STIFILQG.js";
|
|
21
21
|
import {
|
|
22
22
|
parseVideoEvent,
|
|
23
23
|
safePublicDiagnostic
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-JW47XCRL.js";
|
|
25
25
|
import {
|
|
26
26
|
MAX_RETAINED_MEDIA_URL_LENGTH
|
|
27
27
|
} from "./chunk-GHJEAP4O.js";
|
|
@@ -29,14 +29,13 @@ import {
|
|
|
29
29
|
VIDEO_PROTOCOL_VERSION
|
|
30
30
|
} from "./chunk-EE6PK6XC.js";
|
|
31
31
|
import {
|
|
32
|
-
createTemplateSystemPrompt
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
import "./chunk-EVG2ZYIY.js";
|
|
32
|
+
createTemplateSystemPrompt,
|
|
33
|
+
getStandardMediaResolverContract
|
|
34
|
+
} from "./chunk-GR4I3MN2.js";
|
|
35
|
+
import "./chunk-WHPZED7X.js";
|
|
36
|
+
import "./chunk-3O7OMMMF.js";
|
|
38
37
|
import "./chunk-352ZZCET.js";
|
|
39
|
-
import "./chunk-
|
|
38
|
+
import "./chunk-XAMUOSX7.js";
|
|
40
39
|
import "./chunk-2E6T633S.js";
|
|
41
40
|
import {
|
|
42
41
|
validateBrandInput
|
|
@@ -130,6 +129,7 @@ function parseVideoRequest(value) {
|
|
|
130
129
|
const input = record(request.input, "request.input");
|
|
131
130
|
allowedKeys(input, [
|
|
132
131
|
"input",
|
|
132
|
+
"knowledgeMode",
|
|
133
133
|
"instructions",
|
|
134
134
|
"maxDurationSec",
|
|
135
135
|
"orientation",
|
|
@@ -141,6 +141,14 @@ function parseVideoRequest(value) {
|
|
|
141
141
|
"audio"
|
|
142
142
|
], "request.input");
|
|
143
143
|
string(input.input, "request.input.input");
|
|
144
|
+
if (input.knowledgeMode != null) {
|
|
145
|
+
enumValue(
|
|
146
|
+
input.knowledgeMode,
|
|
147
|
+
["input-only", "general"],
|
|
148
|
+
"input-only or general",
|
|
149
|
+
"request.input.knowledgeMode"
|
|
150
|
+
);
|
|
151
|
+
}
|
|
144
152
|
if (input.instructions != null) string(input.instructions, "request.input.instructions");
|
|
145
153
|
if (input.maxDurationSec != null) {
|
|
146
154
|
numberBetween(input.maxDurationSec, 5, 120, "request.input.maxDurationSec");
|
|
@@ -397,10 +405,11 @@ function createVideoStreamHandler(options) {
|
|
|
397
405
|
validateScene: options.validateScene,
|
|
398
406
|
getTemplatePacing: options.getTemplatePacing,
|
|
399
407
|
invalidPartBehavior: options.invalidPartBehavior ?? "drop",
|
|
408
|
+
requireCloser: options.requireCloser,
|
|
400
409
|
onError: options.onError,
|
|
401
410
|
onWarning: options.onWarning,
|
|
402
411
|
onComplete: options.onComplete,
|
|
403
|
-
systemPrompt
|
|
412
|
+
systemPrompt,
|
|
404
413
|
selectAudio: options.selectAudio,
|
|
405
414
|
snapshotRetention: options.snapshotRetention,
|
|
406
415
|
signal: request.signal,
|
|
@@ -459,6 +468,155 @@ function createVideoStreamHandler(options) {
|
|
|
459
468
|
};
|
|
460
469
|
}
|
|
461
470
|
|
|
471
|
+
// src/server/media-resolver.ts
|
|
472
|
+
var MAX_MEDIA_QUERY_CHARACTERS = 80;
|
|
473
|
+
var MAX_MEDIA_QUERY_WORDS = 8;
|
|
474
|
+
function queryIsBounded(query) {
|
|
475
|
+
const length = [...query].length;
|
|
476
|
+
const words = query.match(/\S+/gu)?.length ?? 0;
|
|
477
|
+
return length >= 2 && length <= MAX_MEDIA_QUERY_CHARACTERS && words <= MAX_MEDIA_QUERY_WORDS;
|
|
478
|
+
}
|
|
479
|
+
function preferredType(value) {
|
|
480
|
+
if (value === "photo") return "image";
|
|
481
|
+
if (value === "video") return "video";
|
|
482
|
+
return "any";
|
|
483
|
+
}
|
|
484
|
+
function cleanUrl(value, field) {
|
|
485
|
+
if (value == null || value === "") return void 0;
|
|
486
|
+
if (typeof value !== "string" || !value.trim() || value.length > MAX_RETAINED_MEDIA_URL_LENGTH) {
|
|
487
|
+
throw new Error(`Resolved media ${field} is invalid`);
|
|
488
|
+
}
|
|
489
|
+
return value.trim();
|
|
490
|
+
}
|
|
491
|
+
function fallbackVariables(variables) {
|
|
492
|
+
const fallback = { ...variables };
|
|
493
|
+
delete fallback.mediaKeyword;
|
|
494
|
+
delete fallback.mediaUrl;
|
|
495
|
+
delete fallback.mediaPoster;
|
|
496
|
+
fallback.mediaType = "gradient";
|
|
497
|
+
return fallback;
|
|
498
|
+
}
|
|
499
|
+
function isAbortError(error) {
|
|
500
|
+
return error instanceof Error && error.name === "AbortError";
|
|
501
|
+
}
|
|
502
|
+
async function resolveVariables(options) {
|
|
503
|
+
if (!Object.hasOwn(options.variables, "mediaKeyword")) return options.variables;
|
|
504
|
+
const variables = { ...options.variables };
|
|
505
|
+
const rawQuery = typeof variables.mediaKeyword === "string" ? variables.mediaKeyword.trim() : "";
|
|
506
|
+
delete variables.mediaKeyword;
|
|
507
|
+
if (!options.contract) return variables;
|
|
508
|
+
if (!options.openingReady || !options.resolveMedia || !queryIsBounded(rawQuery)) {
|
|
509
|
+
return fallbackVariables(variables);
|
|
510
|
+
}
|
|
511
|
+
let resolved;
|
|
512
|
+
try {
|
|
513
|
+
resolved = await options.resolveMedia(rawQuery, {
|
|
514
|
+
input: options.input,
|
|
515
|
+
templateId: options.templateId,
|
|
516
|
+
preferredType: preferredType(variables.mediaType),
|
|
517
|
+
signal: options.signal
|
|
518
|
+
});
|
|
519
|
+
} catch (error) {
|
|
520
|
+
if (options.signal.aborted) throw options.signal.reason ?? error;
|
|
521
|
+
if (isAbortError(error)) throw error;
|
|
522
|
+
return fallbackVariables(variables);
|
|
523
|
+
}
|
|
524
|
+
if (options.signal.aborted) {
|
|
525
|
+
throw options.signal.reason ?? new DOMException("The media request was aborted", "AbortError");
|
|
526
|
+
}
|
|
527
|
+
if (!resolved) return fallbackVariables(variables);
|
|
528
|
+
if (resolved.type !== "image" && resolved.type !== "video") {
|
|
529
|
+
throw new Error("Resolved media type is invalid");
|
|
530
|
+
}
|
|
531
|
+
const url = cleanUrl(resolved.url, "URL");
|
|
532
|
+
if (!url) throw new Error("Resolved media URL is required");
|
|
533
|
+
const posterUrl = options.contract.acceptsPoster ? cleanUrl(resolved.posterUrl, "poster URL") : void 0;
|
|
534
|
+
options.approveUrl(options.input, url);
|
|
535
|
+
if (posterUrl) options.approveUrl(options.input, posterUrl);
|
|
536
|
+
variables.mediaUrl = url;
|
|
537
|
+
variables.mediaType = resolved.type === "image" ? "photo" : "video";
|
|
538
|
+
if (posterUrl) variables.mediaPoster = posterUrl;
|
|
539
|
+
else delete variables.mediaPoster;
|
|
540
|
+
return variables;
|
|
541
|
+
}
|
|
542
|
+
function templateContract(templates, templateId) {
|
|
543
|
+
const metadata = templates.getTemplateMetadata(templateId);
|
|
544
|
+
return metadata == null ? void 0 : getStandardMediaResolverContract(metadata.schema);
|
|
545
|
+
}
|
|
546
|
+
function withoutKeyword(variables) {
|
|
547
|
+
if (!Object.hasOwn(variables, "mediaKeyword")) return variables;
|
|
548
|
+
const clean = { ...variables };
|
|
549
|
+
delete clean.mediaKeyword;
|
|
550
|
+
return clean;
|
|
551
|
+
}
|
|
552
|
+
function sanitizeUnknownTemplatePart(part) {
|
|
553
|
+
if (part.type === "scene.add") {
|
|
554
|
+
return { ...part, scene: { ...part.scene, variables: withoutKeyword(part.scene.variables) } };
|
|
555
|
+
}
|
|
556
|
+
if (part.type === "scene.patch" && part.patch.variables) {
|
|
557
|
+
return { ...part, patch: { ...part.patch, variables: withoutKeyword(part.patch.variables) } };
|
|
558
|
+
}
|
|
559
|
+
if (part.type === "asset.patch") {
|
|
560
|
+
return { ...part, variables: withoutKeyword(part.variables) };
|
|
561
|
+
}
|
|
562
|
+
return part;
|
|
563
|
+
}
|
|
564
|
+
async function resolvePartVariables(options) {
|
|
565
|
+
if (!options.templateId) return sanitizeUnknownTemplatePart(options.part);
|
|
566
|
+
const shared = {
|
|
567
|
+
contract: templateContract(options.templates, options.templateId),
|
|
568
|
+
input: options.input,
|
|
569
|
+
templateId: options.templateId,
|
|
570
|
+
signal: options.signal,
|
|
571
|
+
resolveMedia: options.resolveMedia,
|
|
572
|
+
approveUrl: options.approveUrl,
|
|
573
|
+
openingReady: options.openingReady
|
|
574
|
+
};
|
|
575
|
+
if (options.part.type === "scene.add") {
|
|
576
|
+
const variables = await resolveVariables({
|
|
577
|
+
...shared,
|
|
578
|
+
variables: options.part.scene.variables
|
|
579
|
+
});
|
|
580
|
+
return { ...options.part, scene: { ...options.part.scene, variables } };
|
|
581
|
+
}
|
|
582
|
+
if (options.part.type === "scene.patch" && options.part.patch.variables) {
|
|
583
|
+
const variables = await resolveVariables({
|
|
584
|
+
...shared,
|
|
585
|
+
variables: options.part.patch.variables
|
|
586
|
+
});
|
|
587
|
+
return { ...options.part, patch: { ...options.part.patch, variables } };
|
|
588
|
+
}
|
|
589
|
+
if (options.part.type === "asset.patch") {
|
|
590
|
+
const variables = await resolveVariables({
|
|
591
|
+
...shared,
|
|
592
|
+
variables: options.part.variables
|
|
593
|
+
});
|
|
594
|
+
return { ...options.part, variables };
|
|
595
|
+
}
|
|
596
|
+
return options.part;
|
|
597
|
+
}
|
|
598
|
+
function createMediaResolvingPlanner(options) {
|
|
599
|
+
return async function* resolveMediaPlan(context) {
|
|
600
|
+
const sceneTemplates = /* @__PURE__ */ new Map();
|
|
601
|
+
for await (const part of options.planner(context)) {
|
|
602
|
+
const templateId = part.type === "scene.add" ? part.scene.templateId : part.type === "scene.patch" || part.type === "asset.patch" ? sceneTemplates.get(part.sceneId) : void 0;
|
|
603
|
+
if (part.type === "scene.add") {
|
|
604
|
+
sceneTemplates.set(part.scene.id, part.scene.templateId);
|
|
605
|
+
}
|
|
606
|
+
yield await resolvePartVariables({
|
|
607
|
+
part,
|
|
608
|
+
templateId,
|
|
609
|
+
input: context.request.input,
|
|
610
|
+
signal: context.signal,
|
|
611
|
+
templates: options.templates,
|
|
612
|
+
resolveMedia: options.resolveMedia,
|
|
613
|
+
approveUrl: options.approveUrl,
|
|
614
|
+
openingReady: options.isOpeningReady(context.request.input)
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
462
620
|
// src/server/create-video-handler.ts
|
|
463
621
|
function createVideoHandler(options) {
|
|
464
622
|
if (options?.authorize !== "none" && typeof options?.authorize !== "function") {
|
|
@@ -473,22 +631,51 @@ function createVideoHandler(options) {
|
|
|
473
631
|
includeRawProviderData,
|
|
474
632
|
basePrompt,
|
|
475
633
|
allowMediaUrl,
|
|
634
|
+
resolveMedia,
|
|
635
|
+
requireCloser = true,
|
|
476
636
|
...handlerOptions
|
|
477
637
|
} = options;
|
|
478
638
|
const templates = configuredTemplates ? overlayServerTemplateRegistry(BUILTIN_SERVER_TEMPLATE_KIT, configuredTemplates) : BUILTIN_SERVER_TEMPLATE_KIT;
|
|
639
|
+
const approvedMediaUrls = /* @__PURE__ */ new WeakMap();
|
|
640
|
+
const openingReadyInputs = /* @__PURE__ */ new WeakSet();
|
|
641
|
+
const approveUrl = (input, url) => {
|
|
642
|
+
const approved = approvedMediaUrls.get(input) ?? /* @__PURE__ */ new Set();
|
|
643
|
+
approved.add(url);
|
|
644
|
+
approvedMediaUrls.set(input, approved);
|
|
645
|
+
};
|
|
646
|
+
const planner = createTextDeltaVideoPlanner({ streamText, includeRawProviderData });
|
|
647
|
+
const validateTemplateScene = createTemplateSceneValidator({
|
|
648
|
+
kit: templates,
|
|
649
|
+
allowMediaUrl: (url, context) => approvedMediaUrls.get(context.input)?.has(url) === true || allowMediaUrl?.(url, context) === true
|
|
650
|
+
});
|
|
479
651
|
return createVideoStreamHandler({
|
|
480
652
|
...handlerOptions,
|
|
481
|
-
|
|
482
|
-
|
|
653
|
+
requireCloser,
|
|
654
|
+
generate: createMediaResolvingPlanner({
|
|
655
|
+
planner,
|
|
656
|
+
templates,
|
|
657
|
+
resolveMedia,
|
|
658
|
+
approveUrl,
|
|
659
|
+
isOpeningReady: (input) => openingReadyInputs.has(input)
|
|
660
|
+
}),
|
|
661
|
+
systemPrompt: ({ request, capabilities: capabilities2 }) => {
|
|
483
662
|
const selectedIds = capabilities2?.templates == null ? void 0 : new Set(capabilities2.templates);
|
|
484
663
|
const selectedTemplates = selectedIds == null ? templates.listTemplateMetadata() : templates.listTemplateMetadata().filter(({ id }) => selectedIds.has(id));
|
|
485
664
|
return createTemplateSystemPrompt({
|
|
486
665
|
kit: { listTemplateMetadata: () => selectedTemplates },
|
|
487
|
-
basePrompt
|
|
666
|
+
basePrompt,
|
|
667
|
+
knowledgeMode: request.input.knowledgeMode,
|
|
668
|
+
mediaResolverAvailable: resolveMedia != null
|
|
488
669
|
});
|
|
489
670
|
},
|
|
490
671
|
supportedCapabilities: templates.capabilities,
|
|
491
|
-
validateScene:
|
|
672
|
+
validateScene: (scene, context) => {
|
|
673
|
+
validateTemplateScene(scene, context);
|
|
674
|
+
const isAsk = templates.getTemplateMetadata(scene.templateId)?.jobs?.includes("ask") === true;
|
|
675
|
+
if (!isAsk) {
|
|
676
|
+
openingReadyInputs.add(context.input);
|
|
677
|
+
}
|
|
678
|
+
},
|
|
492
679
|
getTemplatePacing: (templateId) => templates.getTemplateMetadata(templateId)
|
|
493
680
|
});
|
|
494
681
|
}
|
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-DA2cfJ96.js';
|
|
2
|
+
export { T as TemplateRegistry, c as createTemplateRegistry } from './kit-DA2cfJ96.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-CkO2EYr4.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,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as VideoWarning, b as VideoFinishReason } from './events-
|
|
1
|
+
import { g as VideoScene, f as VideoOrientation, h as VideoStyle, V as Video, a as VideoAudio, d as VideoInput } from './types-CkO2EYr4.js';
|
|
2
|
+
import { a as VideoWarning, b as VideoFinishReason } from './events-CTIsANzz.js';
|
|
3
3
|
|
|
4
4
|
type MockVideoStreamPart = {
|
|
5
5
|
type: "scene.add";
|
|
6
|
+
placement?: "closer";
|
|
6
7
|
scene: VideoScene;
|
|
7
8
|
} | {
|
|
8
9
|
type: "scene.patch";
|
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-TQOHDXNW.js");
|
|
237
|
+
const { createTextDeltaVideoPlanner } = await import("./text-stream-UPGUD2TD.js");
|
|
238
|
+
const { BUILTIN_SERVER_TEMPLATE_KIT } = await import("./builtin-server-BRTZN4Q7.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);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createTextDeltaVideoPlanner
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BKF3A357.js";
|
|
4
4
|
import "./chunk-E7CL7UPB.js";
|
|
5
|
-
import "./chunk-
|
|
5
|
+
import "./chunk-JW47XCRL.js";
|
|
6
6
|
import "./chunk-EE6PK6XC.js";
|
|
7
7
|
import "./chunk-XGRA2MUV.js";
|
|
8
8
|
export {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
declare const VIDEO_PROTOCOL_VERSION: "0.4";
|
|
2
2
|
declare const VIDEO_SCHEMA_VERSION: "0.1";
|
|
3
3
|
type VideoOrientation = "portrait" | "landscape";
|
|
4
|
+
type VideoKnowledgeMode = "input-only" | "general";
|
|
4
5
|
interface VideoBrandColors {
|
|
5
6
|
primary: string;
|
|
6
7
|
secondary: string;
|
|
@@ -114,15 +115,17 @@ interface VideoSuppliedMedia {
|
|
|
114
115
|
role?: "product" | "proof" | "background" | "logo";
|
|
115
116
|
}
|
|
116
117
|
interface VideoInput {
|
|
117
|
-
/** Raw
|
|
118
|
+
/** Raw source, request, or question for the video. */
|
|
118
119
|
input: string;
|
|
119
|
-
/**
|
|
120
|
+
/** Keep claims source-grounded by default, or allow stable model knowledge. */
|
|
121
|
+
knowledgeMode?: VideoKnowledgeMode;
|
|
122
|
+
/** Optional creative direction. It never changes the knowledge mode. */
|
|
120
123
|
instructions?: string;
|
|
121
124
|
maxDurationSec?: number;
|
|
122
125
|
orientation?: VideoOrientation;
|
|
123
126
|
/** Optional global visual direction. Omit for VanillaSky defaults. */
|
|
124
127
|
style?: VideoStyleOptions;
|
|
125
|
-
/** Optional
|
|
128
|
+
/** Optional custom opening copy. Omit for the deterministic "Creating your video..." fallback. */
|
|
126
129
|
opening?: string;
|
|
127
130
|
brand?: VideoBrandInput;
|
|
128
131
|
/** Viewer or account context that may appear verbatim. It is data, never instructions. */
|
|
@@ -159,6 +162,7 @@ interface VideoGenerationContext {
|
|
|
159
162
|
}
|
|
160
163
|
type VideoPlanPart = {
|
|
161
164
|
type: "scene.add";
|
|
165
|
+
placement?: "closer";
|
|
162
166
|
scene: VideoScene;
|
|
163
167
|
} | {
|
|
164
168
|
type: "scene.patch";
|
|
@@ -205,4 +209,4 @@ interface VideoSnapshotRetention {
|
|
|
205
209
|
suppliedMediaUrls?: boolean;
|
|
206
210
|
}
|
|
207
211
|
|
|
208
|
-
export { type Video as V, type VideoAudio as a, type VideoBackground as b, type VideoBrand as c, type VideoInput as d, type
|
|
212
|
+
export { type Video as V, type VideoAudio as a, type VideoBackground as b, type VideoBrand as c, type VideoInput as d, type VideoKnowledgeMode as e, type VideoOrientation as f, type VideoScene as g, type VideoStyle as h, type VideoStyleOptions as i, type VideoSuppliedMedia as j, type VideoCapabilities as k, VIDEO_PROTOCOL_VERSION as l, type VideoGenerationContext as m, type VideoPlanner as n, type VideoRequest as o, type VideoSceneValidator as p, type VideoTemplatePacing as q, type VideoSnapshotRetention as r, type VideoResumeCursor as s, type VideoSceneValidationContext as t };
|
package/docs/concepts.md
CHANGED
|
@@ -21,8 +21,11 @@ or export pipeline when an encoded file is required.
|
|
|
21
21
|
curated set of articles, metrics, events, notes, or an AI answer. It may be
|
|
22
22
|
short, but the planner is designed to distill larger sources into a concise
|
|
23
23
|
video rather than represent every fact;
|
|
24
|
+
- `knowledgeMode`: `input-only` by default, or `general` when stable model
|
|
25
|
+
knowledge may supplement the request;
|
|
24
26
|
- `instructions`: optional creative direction that cannot override facts;
|
|
25
|
-
- `opening`: optional copy for
|
|
27
|
+
- `opening`: optional custom copy for the deterministic opening; omission uses
|
|
28
|
+
`Creating your video...`;
|
|
26
29
|
- `personalization`: application-defined fields such as name, role, account,
|
|
27
30
|
period, goal, or onboarding partner;
|
|
28
31
|
- `brand`: an optional background preset plus name, logo, font, surfaces, and
|
|
@@ -54,6 +57,12 @@ another streaming text model.
|
|
|
54
57
|
The planner does not create public event IDs, checksums, revisions, or final
|
|
55
58
|
snapshots. The runtime owns those guarantees.
|
|
56
59
|
|
|
60
|
+
`createVideoHandler` asks the planner to emit one early
|
|
61
|
+
`scene.add` with `placement: "closer"` after the first playable body scene.
|
|
62
|
+
That placement is planner-only: the runtime holds the validated closer,
|
|
63
|
+
reserves its readable duration while later body scenes stream, and appends it
|
|
64
|
+
last without persisting `placement` into the completed `Video`.
|
|
65
|
+
|
|
57
66
|
| Boundary | Type | Owner |
|
|
58
67
|
| --- | --- | --- |
|
|
59
68
|
| Planner parts | Internal validated plan data | Server/provider adapter |
|
|
@@ -73,6 +82,7 @@ audio.set optional, before any scene
|
|
|
73
82
|
scene.add supplied opening
|
|
74
83
|
scene.add generated body
|
|
75
84
|
scene.add generated body
|
|
85
|
+
scene.add reserved closer
|
|
76
86
|
response.complete exact terminal snapshot
|
|
77
87
|
```
|
|
78
88
|
|
package/docs/custom-templates.md
CHANGED
|
@@ -83,6 +83,8 @@ The file created by `vanillasky create` is a complete working template. Keep
|
|
|
83
83
|
these concerns together:
|
|
84
84
|
|
|
85
85
|
- `useWhen` and `avoidWhen` tell the AI when the visual is appropriate;
|
|
86
|
+
- `jobs` declares the narrative roles the template can fill, and decides
|
|
87
|
+
whether it may close a video;
|
|
86
88
|
- `schema` defines allowed variables, validation, defaults, labels, and
|
|
87
89
|
grounding formats;
|
|
88
90
|
- named `examples` provide complete deterministic preview values;
|
|
@@ -90,6 +92,31 @@ these concerns together:
|
|
|
90
92
|
- raw `progress`, dimensions, and `safeZone` make semantic state deterministic
|
|
91
93
|
and layout safe in portrait and landscape.
|
|
92
94
|
|
|
95
|
+
### Which templates may close a video
|
|
96
|
+
|
|
97
|
+
Every complete plan ends on exactly one closer, and the runtime only accepts a
|
|
98
|
+
closer whose `jobs` include `"ask"` or `"payoff"`. Marking any other template as
|
|
99
|
+
the closer rejects that scene with `Scene template <id> cannot be used as a
|
|
100
|
+
closer`, and the reserved closing time is given to a template that qualifies.
|
|
101
|
+
|
|
102
|
+
Declare `jobs: ["ask"]` on a call to action and `jobs: ["payoff"]` on a
|
|
103
|
+
celebration or resolution beat. A template that only presents evidence keeps
|
|
104
|
+
`jobs: ["proof"]` and is never asked to close.
|
|
105
|
+
|
|
106
|
+
The same rule reads the built-in catalog, so an application that wants to
|
|
107
|
+
constrain how its videos end can compute the eligible IDs instead of guessing:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { builtinTemplates } from "@vanillaskyai/video/templates/catalog";
|
|
111
|
+
|
|
112
|
+
const closerTemplateIds = builtinTemplates
|
|
113
|
+
.filter((template) => template.jobs.some((job) => job === "ask" || job === "payoff"))
|
|
114
|
+
.map((template) => template.id);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Pacing uses the same signal: the planner reserves time for the closer whenever
|
|
118
|
+
the selected catalog contains a template with one of those two jobs.
|
|
119
|
+
|
|
93
120
|
Customer templates hard-cut by default. Opt into renderer-owned fades only
|
|
94
121
|
for scenes that use the standard media-background variables, and only after
|
|
95
122
|
both timing points are visually tested in portrait and landscape. Shared brand
|
|
@@ -194,7 +221,7 @@ import { templates } from "../vanillasky/server";
|
|
|
194
221
|
export const handleVideo = createVideoHandler({
|
|
195
222
|
templates,
|
|
196
223
|
authorize: (request) => {
|
|
197
|
-
if (process.env.
|
|
224
|
+
if (process.env.VANILLASKY_LOCAL_DEMO !== "1") return false;
|
|
198
225
|
const hostname = new URL(request.url).hostname;
|
|
199
226
|
return hostname === "localhost" || hostname === "127.0.0.1";
|
|
200
227
|
},
|
|
@@ -207,8 +234,9 @@ export const handleVideo = createVideoHandler({
|
|
|
207
234
|
});
|
|
208
235
|
```
|
|
209
236
|
|
|
210
|
-
The local-only authorization above is intentionally narrow.
|
|
211
|
-
|
|
237
|
+
The local-only authorization above is intentionally narrow. Supply its marker
|
|
238
|
+
only from the development command; replace it with your application's session
|
|
239
|
+
check before deployment, as shown in
|
|
212
240
|
[Getting started](getting-started.md).
|
|
213
241
|
|
|
214
242
|
Use the browser registry for generation and playback:
|
|
@@ -311,9 +339,9 @@ Templates whose core proof needs a real statistic can add
|
|
|
311
339
|
`"x-vanillasky": { "requiresStat": true }`. See the structured-data and
|
|
312
340
|
supplied-media references for complete examples.
|
|
313
341
|
|
|
314
|
-
The automatic `opening` uses the built-in `
|
|
315
|
-
|
|
316
|
-
keep that variable contract.
|
|
342
|
+
The automatic `opening` uses the built-in `media` variables (`texts` and
|
|
343
|
+
`mediaType: "gradient"`). If you replace `media` while using automatic
|
|
344
|
+
openings, keep that variable contract.
|
|
317
345
|
|
|
318
346
|
## What belongs where
|
|
319
347
|
|
package/docs/customization.md
CHANGED
|
@@ -76,7 +76,8 @@ appear in the story.
|
|
|
76
76
|
first validated scene arrives. The cover is player state, not video content: it
|
|
77
77
|
is never written to the event log, replay, or export.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
The opening is deterministic and should not wait for an LLM or remote media.
|
|
80
|
+
Omit it to use `Creating your video...`, or supply one concise custom sentence:
|
|
80
81
|
|
|
81
82
|
```ts
|
|
82
83
|
opening: "Joris, your Q2 recap is ready."
|
package/docs/errors.md
CHANGED
|
@@ -35,7 +35,9 @@ try {
|
|
|
35
35
|
`response.error` or `response.abort` rejects it. The hook still retains the
|
|
36
36
|
latest validated `video.video`, and its player stream still receives terminal
|
|
37
37
|
events, so an application may keep already accepted scenes visible while it
|
|
38
|
-
offers a retry.
|
|
38
|
+
offers a retry. If the planner already supplied a validated reserved closer,
|
|
39
|
+
the runtime appends it to that playable terminal snapshot before reporting a
|
|
40
|
+
late provider failure; it never invents or rewrites closer copy during recovery.
|
|
39
41
|
|
|
40
42
|
Both a server `response.abort` and an explicit `video.abort(reason)` keep
|
|
41
43
|
`video.status` at `aborted`; `video.error` contains the same safe typed abort
|
package/docs/getting-started.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Install VanillaSky:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @vanillaskyai/video@0.
|
|
8
|
+
npm install @vanillaskyai/video@0.3.0 ai @ai-sdk/openai
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Set your provider key in `.env.local` (never commit it):
|
|
@@ -29,7 +29,7 @@ const model = openai(process.env.OPENAI_MODEL ?? "gpt-4.1");
|
|
|
29
29
|
const handle = createVideoHandler({
|
|
30
30
|
// Local development only. Replace with your session check before deploying.
|
|
31
31
|
authorize: (request) => {
|
|
32
|
-
if (process.env.
|
|
32
|
+
if (process.env.VANILLASKY_LOCAL_DEMO !== "1") return false;
|
|
33
33
|
const hostname = new URL(request.url).hostname;
|
|
34
34
|
return hostname === "localhost" || hostname === "127.0.0.1";
|
|
35
35
|
},
|
|
@@ -45,11 +45,12 @@ export const POST = handle;
|
|
|
45
45
|
export const OPTIONS = handle;
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
The local bypass is intentionally fail-closed:
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
The local bypass is intentionally fail-closed: the packaged development command
|
|
49
|
+
sets its marker only for `next dev`, and it accepts only localhost. Every
|
|
50
|
+
production request is denied. Replace it with your real session validation
|
|
51
|
+
before deploying. For literal files and commands,
|
|
51
52
|
use the tested
|
|
52
|
-
[`examples/nextjs-quickstart` directory](https://github.com/VanillaSkyAi/video/tree/v0.
|
|
53
|
+
[`examples/nextjs-quickstart` directory](https://github.com/VanillaSkyAi/video/tree/v0.3.0/examples/nextjs-quickstart).
|
|
53
54
|
|
|
54
55
|
`model` can come from any AI SDK provider, registry, gateway, compatible API,
|
|
55
56
|
or custom implementation. The application can choose a cheaper or faster model
|
|
@@ -90,10 +91,12 @@ reactive UI.
|
|
|
90
91
|
|
|
91
92
|
`video.status` is `idle`, `streaming`, `complete`, `error`, or `aborted`.
|
|
92
93
|
`video.video` is the latest deterministic video, and `video.warnings` contains
|
|
93
|
-
bounded typed diagnostics safe to show or branch on.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
bounded typed diagnostics safe to show or branch on. A playable response that
|
|
95
|
+
stops at a planner length limit includes a `plan_incomplete` warning because
|
|
96
|
+
requested scenes or the ending may be missing. Provider finish reasons and
|
|
97
|
+
content-filter details remain available to the server through the `onComplete`
|
|
98
|
+
summary; surface that server-owned state separately when completeness matters
|
|
99
|
+
to your product.
|
|
97
100
|
|
|
98
101
|
Persist a completed `Video` as JSON and play it later without another model
|
|
99
102
|
request:
|
|
@@ -9,6 +9,7 @@ import type { VideoInput } from "@vanillaskyai/video";
|
|
|
9
9
|
|
|
10
10
|
const input: VideoInput = {
|
|
11
11
|
input: "Joris completed 142 customer conversations in Q2.",
|
|
12
|
+
knowledgeMode: "input-only",
|
|
12
13
|
instructions: "Celebrate the result. Never alter a metric.",
|
|
13
14
|
opening: "Joris, your Q2 recap is ready.",
|
|
14
15
|
personalization: { firstName: "Joris", period: "Q2" },
|
|
@@ -22,20 +23,30 @@ const input: VideoInput = {
|
|
|
22
23
|
|
|
23
24
|
## Raw source
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
In the default `input-only` mode, put every fact the response may claim in
|
|
27
|
+
`input`. Use plain text, compact JSON, or a server-produced digest. Include
|
|
28
|
+
units, periods, comparison bases, and provenance identifiers where ambiguity
|
|
29
|
+
is possible. Choose `knowledgeMode: "general"` only when the model should use
|
|
30
|
+
stable general knowledge to answer a question or develop content. Creative
|
|
31
|
+
direction in `instructions` never changes that setting.
|
|
26
32
|
|
|
27
33
|
Bound request bytes and reject secret-shaped fields on the server. Do not pass provider keys, authorization headers, internal prompt fragments, or storage credentials as source material.
|
|
28
34
|
|
|
29
35
|
## Opening
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
`opening` becomes a deterministic `media` scene emitted before provider work.
|
|
38
|
+
When callers omit it, the SDK uses `Creating your video...`; a supplied value
|
|
39
|
+
replaces that fallback. The scene contains only that sentence and explicitly
|
|
40
|
+
uses the brand gradient without stock media. The SDK requests a three-second
|
|
41
|
+
opening, then applies its normal readability and overall-duration budget. It
|
|
42
|
+
owns the scene ID, template, variables, and timing so callers only provide
|
|
43
|
+
optional custom copy. That copy should:
|
|
34
44
|
|
|
35
45
|
- be personal or situational enough to feel intentional;
|
|
36
46
|
- require no network media lookup;
|
|
37
47
|
- remain true if generation later fails;
|
|
38
|
-
-
|
|
48
|
+
- be one concise sentence that fits comfortably in both supported orientations;
|
|
39
49
|
- be part of the final story, not a spinner disguised as a scene.
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
The opening is runtime-owned, so it remains available even when `templateIds`
|
|
52
|
+
does not let the planner select `media` for generated body scenes.
|