@vanillaskyai/video 0.9.0 → 0.10.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{builtin-server-HW6HGXCZ.js → builtin-server-W4PLJUZ5.js} +2 -2
  3. package/dist/check-runtime.js +2 -2
  4. package/dist/{chunk-UDIODEDK.js → chunk-224QNWRA.js} +5 -1
  5. package/dist/{chunk-OR52UMSF.js → chunk-2HFO22K2.js} +2 -1
  6. package/dist/{chunk-MXEYPK7M.js → chunk-4ZJLPHBV.js} +194 -62
  7. package/dist/{chunk-3ZTEFJIR.js → chunk-ELVOT2FQ.js} +1 -1
  8. package/dist/{chunk-LH4BPALA.js → chunk-IIN5M5HW.js} +194 -61
  9. package/dist/{chunk-MQILEEXC.js → chunk-IR44XKBI.js} +1 -1
  10. package/dist/{chunk-G7IZBBWF.js → chunk-NMFDC2W2.js} +3 -10
  11. package/dist/chunk-XX4GXKTX.js +35 -0
  12. package/dist/chunk-Z67Q5KUW.js +16 -0
  13. package/dist/{cinema-media-YQHSIFDA.js → cinema-media-C7OUPNSH.js} +3 -2
  14. package/dist/cli.js +3 -3
  15. package/dist/{comparison-RZMMHZR2.js → comparison-GNZOR7MF.js} +14 -4
  16. package/dist/{editorial-timeline-OZX7OS6G.js → editorial-timeline-EMV5335S.js} +10 -3
  17. package/dist/key-figure-P37VK425.js +30 -0
  18. package/dist/{mobile-message-UZUPIKHD.js → mobile-message-OF7T62B7.js} +3 -2
  19. package/dist/{preload-media-CFTZIMZL.js → preload-media-LJXWKTWG.js} +1 -1
  20. package/dist/quote-T6WYLN7A.js +33 -0
  21. package/dist/react.js +29 -27
  22. package/dist/server.js +97 -19
  23. package/dist/template-catalog.d.ts +154 -19
  24. package/dist/template-catalog.js +1 -1
  25. package/dist/test.js +1 -1
  26. package/docs/customization.md +1 -1
  27. package/docs/maintainers/cinematic-migration.md +4 -0
  28. package/docs/media-and-audio.md +3 -3
  29. package/docs/provider-integration.md +1 -1
  30. package/package.json +1 -1
  31. package/registry/items/comparison.json +71 -4
  32. package/registry/items/editorialTimeline.json +71 -4
  33. package/registry/items/keyFigure.json +69 -4
  34. package/registry/items/quote.json +71 -4
  35. package/starters/video-chat/package.json +1 -1
  36. package/dist/focus-cards-TJ72BT7U.js +0 -20
  37. package/dist/key-figure-GYENXKH3.js +0 -20
  38. package/dist/quote-EW5HQA6H.js +0 -23
  39. package/registry/items/focusCards.json +0 -90
package/dist/react.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  } from "./chunk-7NZOMTAL.js";
31
31
  import {
32
32
  getBuiltinTemplateMetadata
33
- } from "./chunk-LH4BPALA.js";
33
+ } from "./chunk-IIN5M5HW.js";
34
34
  import "./chunk-3O7OMMMF.js";
35
35
  import {
36
36
  assertTemplateTransitionMetadata
@@ -40,8 +40,8 @@ import {
40
40
  VideoFrame,
41
41
  getDimensions,
42
42
  sceneReadinessKey
43
- } from "./chunk-3ZTEFJIR.js";
44
- import "./chunk-UDIODEDK.js";
43
+ } from "./chunk-ELVOT2FQ.js";
44
+ import "./chunk-224QNWRA.js";
45
45
  import "./chunk-TVIU23OM.js";
46
46
  import {
47
47
  getVideoDuration,
@@ -93,14 +93,13 @@ import { createElement } from "react";
93
93
 
94
94
  // src/visual-system/catalog/builtin-loaders.generated.ts
95
95
  var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
96
- "cinemaMedia": () => import("./cinema-media-YQHSIFDA.js").then((module) => ({ default: module.MediaSceneTemplate })),
96
+ "cinemaMedia": () => import("./cinema-media-C7OUPNSH.js").then((module) => ({ default: module.MediaSceneTemplate })),
97
97
  "chapterTitle": () => import("./chapter-title-YRLIQY3V.js").then((module) => ({ default: module.TitleSceneTemplate })),
98
- "focusCards": () => import("./focus-cards-TJ72BT7U.js").then((module) => ({ default: module.CardsSceneTemplate })),
99
- "editorialTimeline": () => import("./editorial-timeline-OZX7OS6G.js").then((module) => ({ default: module.TimelineSceneTemplate })),
100
- "mobileMessage": () => import("./mobile-message-UZUPIKHD.js").then((module) => ({ default: module.NotificationSceneTemplate })),
101
- "comparison": () => import("./comparison-RZMMHZR2.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
102
- "quote": () => import("./quote-EW5HQA6H.js").then((module) => ({ default: module.QuoteSceneTemplate })),
103
- "keyFigure": () => import("./key-figure-GYENXKH3.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
98
+ "editorialTimeline": () => import("./editorial-timeline-EMV5335S.js").then((module) => ({ default: module.TimelineSceneTemplate })),
99
+ "mobileMessage": () => import("./mobile-message-OF7T62B7.js").then((module) => ({ default: module.NotificationSceneTemplate })),
100
+ "comparison": () => import("./comparison-GNZOR7MF.js").then((module) => ({ default: module.ComparisonSceneTemplate })),
101
+ "quote": () => import("./quote-T6WYLN7A.js").then((module) => ({ default: module.QuoteSceneTemplate })),
102
+ "keyFigure": () => import("./key-figure-P37VK425.js").then((module) => ({ default: module.KeyFigureSceneTemplate }))
104
103
  };
105
104
 
106
105
  // src/visual-system/catalog/builtin-player.generated.ts
@@ -121,17 +120,6 @@ var GENERATED_BUILTIN_PLAYER_TEMPLATES = [
121
120
  },
122
121
  "usesGlobalTransition": false
123
122
  },
124
- {
125
- "id": "focusCards",
126
- "defaults": {
127
- "items": [
128
- "Listen closely",
129
- "Notice the pattern",
130
- "Make room for change"
131
- ]
132
- },
133
- "usesGlobalTransition": false
134
- },
135
123
  {
136
124
  "id": "editorialTimeline",
137
125
  "defaults": {
@@ -145,7 +133,10 @@ var GENERATED_BUILTIN_PLAYER_TEMPLATES = [
145
133
  {
146
134
  "label": "Act"
147
135
  }
148
- ]
136
+ ],
137
+ "mediaUrl": "",
138
+ "mediaType": "video",
139
+ "mediaPoster": ""
149
140
  },
150
141
  "usesGlobalTransition": false
151
142
  },
@@ -164,22 +155,33 @@ var GENERATED_BUILTIN_PLAYER_TEMPLATES = [
164
155
  "id": "comparison",
165
156
  "defaults": {
166
157
  "leftText": "More distractions",
167
- "rightText": "More room to think"
158
+ "rightText": "More room to think",
159
+ "mediaUrl": "",
160
+ "mediaType": "video",
161
+ "mediaPoster": ""
168
162
  },
169
163
  "usesGlobalTransition": false
170
164
  },
171
165
  {
172
166
  "id": "quote",
173
- "defaults": {},
167
+ "defaults": {
168
+ "mediaUrl": "",
169
+ "mediaType": "video",
170
+ "mediaPoster": ""
171
+ },
174
172
  "usesGlobalTransition": false
175
173
  },
176
174
  {
177
175
  "id": "keyFigure",
178
- "defaults": {},
176
+ "defaults": {
177
+ "mediaUrl": "",
178
+ "mediaType": "video",
179
+ "mediaPoster": ""
180
+ },
179
181
  "usesGlobalTransition": false
180
182
  }
181
183
  ];
182
- var GENERATED_BUILTIN_VIDEO_BACKDROP_IDS = ["cinemaMedia", "mobileMessage"];
184
+ var GENERATED_BUILTIN_VIDEO_BACKDROP_IDS = ["cinemaMedia", "editorialTimeline", "mobileMessage", "comparison", "quote", "keyFigure"];
183
185
 
184
186
  // src/visual-system/catalog/builtin-player.ts
185
187
  function createPreloadableBuiltinTemplate(loader, mediaBackdrop) {
@@ -235,7 +237,7 @@ function preloadBuiltinTemplate(id) {
235
237
 
236
238
  // src/player/warm-scene-media.ts
237
239
  function warmSceneMedia(variables) {
238
- void import("./preload-media-CFTZIMZL.js").then((module) => module.preloadSceneMedia(variables)).catch(() => {
240
+ void import("./preload-media-LJXWKTWG.js").then((module) => module.preloadSceneMedia(variables)).catch(() => {
239
241
  });
240
242
  }
241
243
 
package/dist/server.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  BUILTIN_SERVER_TEMPLATE_KIT,
13
13
  createServerTemplateRegistry,
14
14
  overlayServerTemplateRegistry
15
- } from "./chunk-MQILEEXC.js";
15
+ } from "./chunk-IR44XKBI.js";
16
16
  import {
17
17
  createTemplateSceneValidator,
18
18
  validateTemplateSceneStructure
@@ -47,14 +47,77 @@ import {
47
47
  import {
48
48
  createTemplateSystemPrompt,
49
49
  getStandardMediaResolverContract
50
- } from "./chunk-OR52UMSF.js";
50
+ } from "./chunk-2HFO22K2.js";
51
51
  import "./chunk-PAJ44TFW.js";
52
52
  import "./chunk-EGVQODKU.js";
53
- import "./chunk-LH4BPALA.js";
53
+ import "./chunk-IIN5M5HW.js";
54
54
  import "./chunk-3O7OMMMF.js";
55
55
  import "./chunk-2E6T633S.js";
56
56
  import "./chunk-73NTSFFI.js";
57
57
 
58
+ // src/server/opening-continuity.ts
59
+ function continueAfterOpening(narration, earlier) {
60
+ const words = (value) => Array.from(value.matchAll(/[\p{L}\p{N}]+(?:['’][\p{L}\p{N}]+)*/gu));
61
+ let remaining = narration;
62
+ for (const previous of [...earlier].reverse()) {
63
+ const known = words(previous), current = words(remaining);
64
+ if (known.length < 5 || known.length > current.length) continue;
65
+ if (!known.every((word, index) => word[0].toLowerCase() === current[index][0].toLowerCase())) continue;
66
+ if (known.length === current.length) return "";
67
+ const last = current[known.length - 1];
68
+ const suffix = remaining.slice(last.index + last[0].length);
69
+ const boundary = suffix.match(/^[”"'’\])]*[.!?…]+[\s”"'’\])]*/u);
70
+ if (boundary) remaining = suffix.slice(boundary[0].length).trim();
71
+ }
72
+ return remaining;
73
+ }
74
+ function createOpeningContinuation(initialOpening) {
75
+ const earlier = initialOpening ? [initialOpening] : [];
76
+ let bodyStarted = false;
77
+ const copy = (authored, narration) => {
78
+ if (continueAfterOpening(authored, earlier) !== "") return authored;
79
+ const next = continueAfterOpening(narration, earlier).trim();
80
+ return next && [...next].length <= 65 && /[.!?…][”"’')\]]*$/u.test(next) ? next : authored;
81
+ };
82
+ return {
83
+ copy,
84
+ remember(line) {
85
+ if (line.trim()) earlier.push(line);
86
+ },
87
+ narration(line) {
88
+ return continueAfterOpening(line, earlier);
89
+ },
90
+ line(rawLine) {
91
+ if (bodyStarted || earlier.length === 0) return rawLine;
92
+ let part;
93
+ try {
94
+ part = JSON.parse(rawLine);
95
+ } catch {
96
+ return rawLine;
97
+ }
98
+ if (!part || part.type !== "scene.add" || part.placement === "closer") return rawLine;
99
+ const scene = part.scene;
100
+ if (!scene) return rawLine;
101
+ if (typeof scene.narration !== "string") {
102
+ bodyStarted = true;
103
+ return rawLine;
104
+ }
105
+ const narration = continueAfterOpening(scene.narration, earlier);
106
+ if (!narration && scene.templateId === "cinemaMedia") return null;
107
+ const variables = scene.variables;
108
+ if (!narration && scene.templateId === "chapterTitle" && typeof variables?.title === "string" && !continueAfterOpening(variables.title, earlier)) return null;
109
+ bodyStarted = true;
110
+ if (!narration) return rawLine;
111
+ const field = scene.templateId === "cinemaMedia" ? "fallbackText" : scene.templateId === "chapterTitle" ? "title" : void 0;
112
+ const authored = field && variables?.[field];
113
+ const updated = typeof authored === "string" ? copy(authored, narration) : void 0;
114
+ const copyChanged = typeof authored === "string" && updated !== authored;
115
+ if (narration === scene.narration && !copyChanged) return rawLine;
116
+ return JSON.stringify({ ...part, scene: { ...scene, narration, ...copyChanged ? { variables: { ...variables, [field]: updated } } : {} } });
117
+ }
118
+ };
119
+ }
120
+
58
121
  // src/server/request-validation.ts
59
122
  var MAX_GENERATED_LOOK_LENGTH = 1e3;
60
123
  function boundedString(value, path) {
@@ -865,11 +928,12 @@ function createVideoHandler(options) {
865
928
  function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlreadyProvided = false, maxGeneratedVideos = 5) {
866
929
  return [
867
930
  "Respond as a coherent short film: establish, develop, then land a useful or emotional payoff. Match the user's requested form.",
868
- generatedVideoAvailable ? 'First emit one host-consumed opening JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject","firstShot":{"text":"short grounded fallback","narration":"one natural spoken sentence","mediaKeyword":"2-8 concrete words","shotDirection":"optional framing and action"}}. Include firstShot only when a distinctive illustrative shot serves the story; omit it for an abstract explanation best opened with graphics. When present, the host inserts it once and begins generation immediately; do not emit it again.' : openingAlreadyProvided ? "Start directly with scene.add; the opening has already been supplied." : 'First emit {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject"}. This is host-consumed, not a scene.',
931
+ generatedVideoAvailable ? 'First emit one host-consumed opening JSON object: {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject","firstShot":{"text":"short grounded fallback","narration":"one natural sentence advancing the hook","mediaKeyword":"2-8 concrete words","shotDirection":"optional framing and action"}}. Include firstShot only when a distinctive illustrative shot serves the story; omit it for an abstract explanation best opened with graphics. When present, the host inserts it once and begins generation immediately; do not emit it again.' : openingAlreadyProvided ? "Start directly with scene.add; the opening has already been supplied." : 'First emit {"type":"video-chat.opening","spokenHook":"6-9 words","mediaKeyword":"literal subject"}. This is host-consumed, not a scene.',
869
932
  openingAlreadyProvided ? "Preserve the supplied opening exactly; continue it without repeating its words or claim." : void 0,
933
+ "The spokenHook is already heard before the first scene. Start firstShot.narration with the next fact, cause, action or consequence; never restate the hook. The first ordinary scene must then advance beyond firstShot rather than repeat its narration or opening claim. If firstShot is omitted, that first scene must advance the hook directly. firstShot.text, fallbackText and chapter titles must also express that new beat, not reuse the hook or prior shot copy; write a short complete grounded thought within the schema budget, never a clipped statistic or qualifying clause.",
870
934
  "Continue with only as many scenes as the story and duration need. Never pad to a fixed count. Every emitted scene carries narration beside variables and timing; no additional narration request should be needed.",
871
- "For concrete topics, coherent footage carries the story and graphics clarify particular points. Maintain consistent setting, lighting and subject while varying shot scale. Avoid unrelated cinematic montages.",
872
- "Use the installed catalog: parallel points, ordered events, comparison, exact quote, one key figure, chapter or a message only when its narrative job fits. Do not force every template into a video. Avoid repeating the same graphic or more than two consecutive graphic scenes unless necessary.",
935
+ "Make most of the film relevant footage: use full-bleed media for action, subjects and atmosphere, with graphics only to clarify a specific relationship or piece of evidence. Maintain consistent setting, lighting and subject while varying shot scale. Avoid unrelated cinematic montages.",
936
+ "Use the installed catalog: ordered events, comparison, exact quote, one key figure, chapter or a message only when its narrative job fits. At most one graphic-led explanatory beat per 30 seconds, plus a brief chapter opening when needed. Do not place graphic beats consecutively unless no honest relevant media is available. Give media-capable graphics a relevant background when it supports the meaning; keep black when no matching media exists. Do not force every template into a video or use lists of bullets.",
873
937
  `At most ${generatedVideoAvailable ? maxGeneratedVideos : 0} generated-video attempts are available. Media scenes choose mediaSource=generate for distinctive illustrative shots or mediaSource=stock for generic verified imagery. A stock miss is not permission to broaden essential details. Do not spend generation on every scene.`,
874
938
  "Each mediaKeyword is a literal filmable subject/action, 2\u20138 words, maximum 80 characters. Use optional shotDirection for action, camera framing and continuity; it does not change the literal stock subject. Supply a short grounded fallbackText when the schema declares it. Do not put visible headline text over full-bleed footage.",
875
939
  "No invented quotations, attribution, statistics, personal evidence or URLs. Creative stories may be invented when requested; do not present generated illustration as historical evidence.",
@@ -1111,6 +1175,19 @@ function interceptOpeningPlan(source, options) {
1111
1175
  const textStream = (async function* () {
1112
1176
  let buffer = "";
1113
1177
  let decided = !options.expectOpening;
1178
+ const continuation = createOpeningContinuation(options.openingLine);
1179
+ const acceptOpening = (opening) => {
1180
+ if (!options.openingProvided) {
1181
+ options.publish(opening.line ? opening : void 0);
1182
+ continuation.remember(opening.line);
1183
+ }
1184
+ if (!options.generatedVideoAvailable || !opening.firstShot) return void 0;
1185
+ const narration = continuation.narration(opening.firstShot.narration);
1186
+ if (!narration) return void 0;
1187
+ const text = continuation.copy(opening.firstShot.text, narration);
1188
+ continuation.remember(narration);
1189
+ return JSON.stringify(reservedFirstScene(options.requestId, { ...opening.firstShot, text, narration }));
1190
+ };
1114
1191
  try {
1115
1192
  for await (const delta of upstream) {
1116
1193
  if (typeof delta !== "string") throw new Error("The LLM adapter returned a non-text delta");
@@ -1124,17 +1201,16 @@ function interceptOpeningPlan(source, options) {
1124
1201
  const opening = readOpeningPlanLine(line);
1125
1202
  decided = true;
1126
1203
  if (opening) {
1127
- if (!options.openingProvided) options.publish(opening.line ? opening : void 0);
1128
- if (options.generatedVideoAvailable && opening.firstShot) {
1129
- yield `${JSON.stringify(reservedFirstScene(options.requestId, opening.firstShot))}
1204
+ const firstScene = acceptOpening(opening);
1205
+ if (firstScene) yield `${firstScene}
1130
1206
  `;
1131
- }
1132
1207
  newline = buffer.indexOf("\n");
1133
1208
  continue;
1134
1209
  }
1135
1210
  options.publish(void 0);
1136
1211
  }
1137
- yield `${rawLine}
1212
+ const continued = continuation.line(rawLine);
1213
+ if (continued != null) yield `${continued}
1138
1214
  `;
1139
1215
  newline = buffer.indexOf("\n");
1140
1216
  }
@@ -1145,17 +1221,17 @@ function interceptOpeningPlan(source, options) {
1145
1221
  const opening = readOpeningPlanLine(line);
1146
1222
  decided = true;
1147
1223
  if (opening) {
1148
- if (!options.openingProvided) options.publish(opening.line ? opening : void 0);
1149
- if (options.generatedVideoAvailable && opening.firstShot) {
1150
- yield `${JSON.stringify(reservedFirstScene(options.requestId, opening.firstShot))}
1224
+ const firstScene = acceptOpening(opening);
1225
+ if (firstScene) yield `${firstScene}
1151
1226
  `;
1152
- }
1153
1227
  } else {
1154
1228
  options.publish(void 0);
1155
- yield buffer;
1229
+ const continued = continuation.line(buffer);
1230
+ if (continued != null) yield continued;
1156
1231
  }
1157
1232
  } else {
1158
- yield buffer;
1233
+ const continued = continuation.line(buffer);
1234
+ if (continued != null) yield continued;
1159
1235
  }
1160
1236
  }
1161
1237
  } finally {
@@ -1332,7 +1408,7 @@ function createVideoChatHandler(options) {
1332
1408
  const heroQuery = welcomeOptions?.heroQuery;
1333
1409
  let welcomeResponse;
1334
1410
  let requestSequence = 0;
1335
- const responseHandler = (requestId, openingProvided, openingChannel) => {
1411
+ const responseHandler = (requestId, openingProvided, openingChannel, openingLine) => {
1336
1412
  const generatedVideoAvailable = generateVideo != null && maxGeneratedVideos > 0;
1337
1413
  let lifecycle;
1338
1414
  let generatedAttempts = 0;
@@ -1400,6 +1476,7 @@ function createVideoChatHandler(options) {
1400
1476
  return interceptOpeningPlan(videoOptions.streamText(context), {
1401
1477
  expectOpening: generatedVideoAvailable || !openingProvided,
1402
1478
  openingProvided,
1479
+ openingLine,
1403
1480
  requestId,
1404
1481
  generatedVideoAvailable,
1405
1482
  publish: openingChannel.publish
@@ -1569,7 +1646,8 @@ function createVideoChatHandler(options) {
1569
1646
  const response = await responseHandler(
1570
1647
  requestId,
1571
1648
  input.opening != null,
1572
- openingChannel
1649
+ openingChannel,
1650
+ input.opening
1573
1651
  )(videoRequest);
1574
1652
  return streamVideoChatOpening(response, openingChannel.ready);
1575
1653
  }
@@ -66,25 +66,6 @@ declare const BUILTIN_TEMPLATE_SCHEMAS: {
66
66
  readonly required: readonly ["title"];
67
67
  readonly additionalProperties: false;
68
68
  };
69
- readonly focusCards: {
70
- readonly type: "object";
71
- readonly properties: {
72
- readonly items: {
73
- readonly type: "array";
74
- readonly minItems: 2;
75
- readonly maxItems: 4;
76
- readonly items: {
77
- readonly type: "string";
78
- readonly minLength: 1;
79
- readonly maxLength: 55;
80
- };
81
- readonly default: readonly ["Listen closely", "Notice the pattern", "Make room for change"];
82
- readonly description: "2–4 parallel phrases, about 2–6 words each; no heading or numbering.";
83
- };
84
- };
85
- readonly required: readonly ["items"];
86
- readonly additionalProperties: false;
87
- };
88
69
  readonly editorialTimeline: {
89
70
  readonly type: "object";
90
71
  readonly properties: {
@@ -113,9 +94,48 @@ declare const BUILTIN_TEMPLATE_SCHEMAS: {
113
94
  }];
114
95
  readonly description: "3–5 ordered events or actions, each with one short label; no dates.";
115
96
  };
97
+ readonly mediaKeyword: {
98
+ readonly type: "string";
99
+ readonly format: "stock-media-keyword";
100
+ readonly minLength: 1;
101
+ readonly maxLength: 80;
102
+ readonly description: "2–8 word literal subject/action search intent, maximum 80 characters. Host resolves URLs.";
103
+ readonly examples: readonly ["Ocean waves breaking on a rocky shore"];
104
+ };
105
+ readonly mediaUrl: {
106
+ readonly type: "string";
107
+ readonly format: "uri";
108
+ readonly description: "Host-only approved photo or video URL.";
109
+ readonly default: "";
110
+ };
111
+ readonly mediaType: {
112
+ readonly type: "string";
113
+ readonly enum: readonly ["photo", "video"];
114
+ readonly default: "video";
115
+ readonly description: "Host-resolved asset kind.";
116
+ };
117
+ readonly mediaPoster: {
118
+ readonly type: "string";
119
+ readonly format: "uri";
120
+ readonly description: "Host-only approved poster URL for video decoding.";
121
+ readonly default: "";
122
+ };
123
+ readonly shotDirection: {
124
+ readonly type: "string";
125
+ readonly maxLength: 220;
126
+ readonly description: "Optional action, framing and continuity for generation. Preserve the subject; do not request rendered text.";
127
+ };
128
+ readonly mediaSource: {
129
+ readonly type: "string";
130
+ readonly enum: readonly ["generate", "stock"];
131
+ readonly description: "Generate distinctive illustrative shots; use approved stock for familiar observable subjects.";
132
+ };
116
133
  };
117
134
  readonly required: readonly ["events"];
118
135
  readonly additionalProperties: false;
136
+ readonly "x-vanillasky": {
137
+ readonly allowsStockMedia: true;
138
+ };
119
139
  };
120
140
  readonly mobileMessage: {
121
141
  readonly type: "object";
@@ -202,9 +222,48 @@ declare const BUILTIN_TEMPLATE_SCHEMAS: {
202
222
  readonly default: "More room to think";
203
223
  readonly description: "Second real alternative, parallel to the first.";
204
224
  };
225
+ readonly mediaKeyword: {
226
+ readonly type: "string";
227
+ readonly format: "stock-media-keyword";
228
+ readonly minLength: 1;
229
+ readonly maxLength: 80;
230
+ readonly description: "2–8 word literal subject/action search intent, maximum 80 characters. Host resolves URLs.";
231
+ readonly examples: readonly ["Ocean waves breaking on a rocky shore"];
232
+ };
233
+ readonly mediaUrl: {
234
+ readonly type: "string";
235
+ readonly format: "uri";
236
+ readonly description: "Host-only approved photo or video URL.";
237
+ readonly default: "";
238
+ };
239
+ readonly mediaType: {
240
+ readonly type: "string";
241
+ readonly enum: readonly ["photo", "video"];
242
+ readonly default: "video";
243
+ readonly description: "Host-resolved asset kind.";
244
+ };
245
+ readonly mediaPoster: {
246
+ readonly type: "string";
247
+ readonly format: "uri";
248
+ readonly description: "Host-only approved poster URL for video decoding.";
249
+ readonly default: "";
250
+ };
251
+ readonly shotDirection: {
252
+ readonly type: "string";
253
+ readonly maxLength: 220;
254
+ readonly description: "Optional action, framing and continuity for generation. Preserve the subject; do not request rendered text.";
255
+ };
256
+ readonly mediaSource: {
257
+ readonly type: "string";
258
+ readonly enum: readonly ["generate", "stock"];
259
+ readonly description: "Generate distinctive illustrative shots; use approved stock for familiar observable subjects.";
260
+ };
205
261
  };
206
262
  readonly required: readonly ["leftText", "rightText"];
207
263
  readonly additionalProperties: false;
264
+ readonly "x-vanillasky": {
265
+ readonly allowsStockMedia: true;
266
+ };
208
267
  };
209
268
  readonly quote: {
210
269
  readonly type: "object";
@@ -224,9 +283,48 @@ declare const BUILTIN_TEMPLATE_SCHEMAS: {
224
283
  readonly examples: readonly ["Illustrative example"];
225
284
  readonly description: "Grounded attribution belonging to these exact quoted words.";
226
285
  };
286
+ readonly mediaKeyword: {
287
+ readonly type: "string";
288
+ readonly format: "stock-media-keyword";
289
+ readonly minLength: 1;
290
+ readonly maxLength: 80;
291
+ readonly description: "2–8 word literal subject/action search intent, maximum 80 characters. Host resolves URLs.";
292
+ readonly examples: readonly ["Ocean waves breaking on a rocky shore"];
293
+ };
294
+ readonly mediaUrl: {
295
+ readonly type: "string";
296
+ readonly format: "uri";
297
+ readonly description: "Host-only approved photo or video URL.";
298
+ readonly default: "";
299
+ };
300
+ readonly mediaType: {
301
+ readonly type: "string";
302
+ readonly enum: readonly ["photo", "video"];
303
+ readonly default: "video";
304
+ readonly description: "Host-resolved asset kind.";
305
+ };
306
+ readonly mediaPoster: {
307
+ readonly type: "string";
308
+ readonly format: "uri";
309
+ readonly description: "Host-only approved poster URL for video decoding.";
310
+ readonly default: "";
311
+ };
312
+ readonly shotDirection: {
313
+ readonly type: "string";
314
+ readonly maxLength: 220;
315
+ readonly description: "Optional action, framing and continuity for generation. Preserve the subject; do not request rendered text.";
316
+ };
317
+ readonly mediaSource: {
318
+ readonly type: "string";
319
+ readonly enum: readonly ["generate", "stock"];
320
+ readonly description: "Generate distinctive illustrative shots; use approved stock for familiar observable subjects.";
321
+ };
227
322
  };
228
323
  readonly required: readonly ["quote", "attribution"];
229
324
  readonly additionalProperties: false;
325
+ readonly "x-vanillasky": {
326
+ readonly allowsStockMedia: true;
327
+ };
230
328
  };
231
329
  readonly keyFigure: {
232
330
  readonly type: "object";
@@ -246,11 +344,48 @@ declare const BUILTIN_TEMPLATE_SCHEMAS: {
246
344
  readonly examples: readonly ["Illustrative example"];
247
345
  readonly description: "Exactly one short label identifying the quantity and its context.";
248
346
  };
347
+ readonly mediaKeyword: {
348
+ readonly type: "string";
349
+ readonly format: "stock-media-keyword";
350
+ readonly minLength: 1;
351
+ readonly maxLength: 80;
352
+ readonly description: "2–8 word literal subject/action search intent, maximum 80 characters. Host resolves URLs.";
353
+ readonly examples: readonly ["Ocean waves breaking on a rocky shore"];
354
+ };
355
+ readonly mediaUrl: {
356
+ readonly type: "string";
357
+ readonly format: "uri";
358
+ readonly description: "Host-only approved photo or video URL.";
359
+ readonly default: "";
360
+ };
361
+ readonly mediaType: {
362
+ readonly type: "string";
363
+ readonly enum: readonly ["photo", "video"];
364
+ readonly default: "video";
365
+ readonly description: "Host-resolved asset kind.";
366
+ };
367
+ readonly mediaPoster: {
368
+ readonly type: "string";
369
+ readonly format: "uri";
370
+ readonly description: "Host-only approved poster URL for video decoding.";
371
+ readonly default: "";
372
+ };
373
+ readonly shotDirection: {
374
+ readonly type: "string";
375
+ readonly maxLength: 220;
376
+ readonly description: "Optional action, framing and continuity for generation. Preserve the subject; do not request rendered text.";
377
+ };
378
+ readonly mediaSource: {
379
+ readonly type: "string";
380
+ readonly enum: readonly ["generate", "stock"];
381
+ readonly description: "Generate distinctive illustrative shots; use approved stock for familiar observable subjects.";
382
+ };
249
383
  };
250
384
  readonly required: readonly ["value", "label"];
251
385
  readonly additionalProperties: false;
252
386
  readonly "x-vanillasky": {
253
387
  readonly requiresStat: true;
388
+ readonly allowsStockMedia: true;
254
389
  };
255
390
  };
256
391
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BUILTIN_TEMPLATE_MANIFEST
3
- } from "./chunk-MXEYPK7M.js";
3
+ } from "./chunk-4ZJLPHBV.js";
4
4
  export {
5
5
  BUILTIN_TEMPLATE_MANIFEST as builtinTemplates
6
6
  };
package/dist/test.js CHANGED
@@ -235,7 +235,7 @@ async function* simulateVideoStream(parts, options = {}) {
235
235
  }
236
236
  const { createVideo } = await import("./compose-video-TBFTONUC.js");
237
237
  const { createTextDeltaVideoPlanner } = await import("./text-stream-LD364KBI.js");
238
- const { BUILTIN_SERVER_TEMPLATE_KIT } = await import("./builtin-server-HW6HGXCZ.js");
238
+ const { BUILTIN_SERVER_TEMPLATE_KIT } = await import("./builtin-server-W4PLJUZ5.js");
239
239
  const { createTemplateSceneValidator } = await import("./validate-4OUD2CLX.js");
240
240
  const controller = new AbortController();
241
241
  const forwardAbort = () => controller.abort(options.signal?.reason);
@@ -52,7 +52,7 @@ explain with their own composition and motion. There is no brand-kit option.
52
52
  The host can provide a shared `generatedLook` description for media preparation.
53
53
  Custom source-owned templates can define their own visual language in code.
54
54
  Do not rely on old global brand, text-effect, or gradient controls to restyle
55
- the eight cinematic templates.
55
+ the seven cinematic templates.
56
56
 
57
57
  ## Opening
58
58
 
@@ -32,3 +32,7 @@ Evidence: persistence tests preserve the untouched old release fixture and asser
32
32
  The cinematic templates use `-apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif` at regular and medium weights. Apple devices keep native system typography. The existing `@vanillaskyai/video/video-chat.css` entry registers packaged Roboto v51 WOFF2 subsets as the fallback for environments without those fonts. Standalone player and source-owned template integrations should also import that stylesheet. No font is fetched from Google at runtime; browsers fetch local packaged subsets only when Roboto is selected for the rendered glyphs. The font assets include the SIL Open Font License and a source/hash manifest.
33
33
 
34
34
  Live native fonts and a Linux renderer’s Roboto have slightly different metrics. For repeatable exports, keep the browser, installed fonts, viewport and package version fixed, and await `document.fonts.ready` after mounting the final scene before capturing frames. This is a host export responsibility; the SDK does not add a separate export API or force downloaded fonts onto Apple devices.
35
+
36
+ ## Media-led follow-up
37
+
38
+ The owner requested removing `focusCards`. Regenerate persisted videos using that ID; migrate parallel explanations to narration over footage rather than another bullet layout. The remaining seven templates retain their IDs. Comparison, quote, key figure and timeline accept optional standard media variables and remain readable on black when assets are absent. No provider callback signatures or host limits change.
@@ -8,14 +8,14 @@ all credentials out of React and the browser bundle.
8
8
 
9
9
  ## One cinematic mode
10
10
 
11
- The director combines relevant footage and eight editorial templates. Graphics
11
+ The director combines relevant footage and seven editorial templates. Graphics
12
12
  use black and white; footage carries atmosphere and concrete action. Each media
13
13
  scene declares `mediaSource: "stock"` or `"generate"`. Provider availability and
14
14
  host budgets constrain those choices. There is no all-stock or all-AI switch.
15
15
 
16
- The canonical templates are `cinemaMedia`, `chapterTitle`, `focusCards`,
16
+ The canonical templates are `cinemaMedia`, `chapterTitle`,
17
17
  `editorialTimeline`, `mobileMessage`, `comparison`, `quote`, and `keyFigure`.
18
- Only `cinemaMedia` and `mobileMessage` accept media backgrounds. Full-bleed
18
+ All except `chapterTitle` accept optional media backgrounds. Editorial overlays use soft text shadows and a dark contrast scrim over relevant media, and fall back to black. Use at most one explanatory overlay per typical thirty-second answer. Full-bleed
19
19
  footage has no headline; narration and subtitles carry the explanation.
20
20
 
21
21
  ## Reviewed stock
@@ -180,7 +180,7 @@ is intentionally substantial. It is stable for the same SDK version, template
180
180
  kit, media policy, and base prompt. Record input-token usage, keep the selected
181
181
  kit no broader than the product needs, and enable provider-side prompt caching
182
182
  where the chosen provider/model supports it. VanillaSky does not assume one
183
- provider's cache controls in its provider-neutral adapter. The cinematic catalog contains eight templates; use provider-reported token
183
+ provider's cache controls in its provider-neutral adapter. The cinematic catalog contains seven templates; use provider-reported token
184
184
  usage as the authoritative measurement rather than a character estimate.
185
185
 
186
186
  Provider finish reasons `error` and `tool-calls` are terminal failures.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillaskyai/video",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Open-source voice-and-video chat SDK for AI applications.",
5
5
  "keywords": [
6
6
  "video-chat",