@vanillaskyai/video 0.8.8 → 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 (173) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/PUBLIC-API.md +5 -8
  3. package/dist/{builtin-server-CUYPIDE7.js → builtin-server-W4PLJUZ5.js} +2 -2
  4. package/dist/{catalog-types-BIhSpOWK.d.ts → catalog-types-WTbLP6Jh.d.ts} +5 -1
  5. package/dist/chapter-title-YRLIQY3V.js +7 -0
  6. package/dist/check-runtime.js +5 -14
  7. package/dist/{chunk-AITKH6QT.js → chunk-2HFO22K2.js} +15 -40
  8. package/dist/chunk-4YM2M62S.js +13 -0
  9. package/dist/chunk-4ZJLPHBV.js +684 -0
  10. package/dist/chunk-77VY4O7A.js +390 -0
  11. package/dist/{chunk-MMUXVA47.js → chunk-7M56IUUX.js} +9 -8
  12. package/dist/chunk-7NZOMTAL.js +63 -0
  13. package/dist/{chunk-2DOLF4QN.js → chunk-AUPC6MDK.js} +12 -14
  14. package/dist/{chunk-352ZZCET.js → chunk-EGVQODKU.js} +2 -2
  15. package/dist/{chunk-2USVB3CY.js → chunk-ELVOT2FQ.js} +284 -177
  16. package/dist/chunk-EWWCQFTI.js +18 -0
  17. package/dist/{chunk-KT3M7KDL.js → chunk-HKUI5BTB.js} +3 -3
  18. package/dist/chunk-IIN5M5HW.js +697 -0
  19. package/dist/{chunk-LMG3GHLL.js → chunk-IQMYK5DX.js} +29 -3
  20. package/dist/{chunk-OWBT45EN.js → chunk-IR44XKBI.js} +1 -1
  21. package/dist/{chunk-MXZSDGZQ.js → chunk-NMFDC2W2.js} +4 -6
  22. package/dist/{chunk-5SLENAJW.js → chunk-PAJ44TFW.js} +1 -2
  23. package/dist/{chunk-L7QGGKJ2.js → chunk-PDQFQIQW.js} +1 -1
  24. package/dist/{chunk-WJ6FLKNC.js → chunk-R3XAOMKP.js} +14 -0
  25. package/dist/{chunk-2OKB27TG.js → chunk-RXTN2CW6.js} +2 -2
  26. package/dist/{chunk-XWWLKRNU.js → chunk-TVIU23OM.js} +38 -49
  27. package/dist/chunk-XX4GXKTX.js +35 -0
  28. package/dist/chunk-Z67Q5KUW.js +16 -0
  29. package/dist/{chunk-3VRMKFM7.js → chunk-ZDJNFA52.js} +11 -24
  30. package/dist/cinema-media-C7OUPNSH.js +12 -0
  31. package/dist/cli.js +18 -9
  32. package/dist/comparison-GNZOR7MF.js +37 -0
  33. package/dist/compose-video-TBFTONUC.js +12 -0
  34. package/dist/editorial-timeline-EMV5335S.js +43 -0
  35. package/dist/{events-BQ6z264n.d.ts → events-B4YCc4vc.d.ts} +1 -1
  36. package/dist/index.d.ts +4 -6
  37. package/dist/index.js +3 -7
  38. package/dist/key-figure-P37VK425.js +30 -0
  39. package/dist/{kit-tQnCPMOM.d.ts → kit-DrRpdn0p.d.ts} +2 -2
  40. package/dist/mobile-message-OF7T62B7.js +50 -0
  41. package/dist/quote-T6WYLN7A.js +33 -0
  42. package/dist/react.d.ts +21 -11
  43. package/dist/react.js +585 -826
  44. package/dist/scene-video-backdrop-PCTARAAJ.js +7 -0
  45. package/dist/server.d.ts +7 -8
  46. package/dist/server.js +179 -84
  47. package/dist/{system-prompt-RRXIWDDD.js → system-prompt-SYRT5DWM.js} +1 -1
  48. package/dist/template-catalog.d.ts +392 -16
  49. package/dist/template-catalog.js +1 -1
  50. package/dist/templates.d.ts +5 -5
  51. package/dist/test.d.ts +3 -3
  52. package/dist/test.js +18 -18
  53. package/dist/text-stream-LD364KBI.js +9 -0
  54. package/dist/{types-DWFO6qca.d.ts → types-CdBmNTGD.d.ts} +1 -1
  55. package/dist/{types-CEh9eUFu.d.ts → types-DdZw4GRQ.d.ts} +17 -48
  56. package/dist/validate-4OUD2CLX.js +10 -0
  57. package/docs/architecture.md +1 -1
  58. package/docs/concepts.md +1 -1
  59. package/docs/custom-templates.md +13 -28
  60. package/docs/customization.md +11 -59
  61. package/docs/getting-started.md +2 -2
  62. package/docs/immersive-interface.md +3 -4
  63. package/docs/maintainers/cinematic-migration.md +38 -0
  64. package/docs/media-and-audio.md +14 -14
  65. package/docs/motion-and-effects.md +6 -14
  66. package/docs/persistence.md +3 -3
  67. package/docs/production.md +11 -9
  68. package/docs/provider-integration.md +9 -16
  69. package/docs/reference/design-system.html +1 -1
  70. package/docs/reference/protocol.md +14 -4
  71. package/docs/testing.md +1 -1
  72. package/package.json +6 -3
  73. package/registry/items/backgrounds.json +1 -1
  74. package/registry/items/chapterTitle.json +80 -0
  75. package/registry/items/cinemaMedia.json +136 -0
  76. package/registry/items/comparison.json +168 -0
  77. package/registry/items/editorialTimeline.json +172 -0
  78. package/registry/items/keyFigure.json +162 -0
  79. package/registry/items/mobileMessage.json +153 -0
  80. package/registry/items/quote.json +161 -0
  81. package/registry/items/template-context.json +1 -1
  82. package/registry/items/theme.json +1 -1
  83. package/starters/video-chat/README.md +24 -26
  84. package/starters/video-chat/package.json +1 -1
  85. package/starters/video-chat/providers/video.ts +6 -5
  86. package/starters/video-chat/stock.ts +35 -151
  87. package/styles/fonts/OFL.txt +93 -0
  88. package/styles/fonts/manifest.json +66 -0
  89. package/styles/fonts/roboto-v51-cyrillic-ext.woff2 +0 -0
  90. package/styles/fonts/roboto-v51-cyrillic.woff2 +0 -0
  91. package/styles/fonts/roboto-v51-greek-ext.woff2 +0 -0
  92. package/styles/fonts/roboto-v51-greek.woff2 +0 -0
  93. package/styles/fonts/roboto-v51-latin-ext.woff2 +0 -0
  94. package/styles/fonts/roboto-v51-latin.woff2 +0 -0
  95. package/styles/fonts/roboto-v51-math.woff2 +0 -0
  96. package/styles/fonts/roboto-v51-symbols.woff2 +0 -0
  97. package/styles/fonts/roboto-v51-vietnamese.woff2 +0 -0
  98. package/styles/fonts/roboto.css +91 -0
  99. package/styles/video-chat.css +30 -22
  100. package/dist/bg-confetti-HITCGLGD.js +0 -89
  101. package/dist/bg-emoji-VU4UZYZN.js +0 -150
  102. package/dist/bg-media-L34PDQXJ.js +0 -106
  103. package/dist/brand-message-FUJ4STFO.js +0 -256
  104. package/dist/chart-bar-EGPHJATL.js +0 -310
  105. package/dist/chart-counter-5FFWQNZO.js +0 -306
  106. package/dist/chart-progress-ring-JMXQLMLC.js +0 -302
  107. package/dist/chunk-4UNKZEAT.js +0 -3575
  108. package/dist/chunk-5CDAM24P.js +0 -31
  109. package/dist/chunk-AOWSXU2K.js +0 -18
  110. package/dist/chunk-CJ7F2CE6.js +0 -104
  111. package/dist/chunk-FRN6WKHA.js +0 -185
  112. package/dist/chunk-G5JPHBX7.js +0 -336
  113. package/dist/chunk-L5BTTCZV.js +0 -97
  114. package/dist/chunk-P22HHFDK.js +0 -43
  115. package/dist/chunk-PP3MCPN4.js +0 -1998
  116. package/dist/chunk-QAAO7OAY.js +0 -19
  117. package/dist/chunk-S7HFLYNN.js +0 -35
  118. package/dist/chunk-W3VDIBGR.js +0 -217
  119. package/dist/chunk-XGRA2MUV.js +0 -242
  120. package/dist/chunk-XTOXFMLM.js +0 -105
  121. package/dist/chunk-YJJC4N4D.js +0 -761
  122. package/dist/compose-video-2NFH4DZY.js +0 -13
  123. package/dist/cta-logo-YR7LYUR5.js +0 -212
  124. package/dist/cta-media-DQJBKUO2.js +0 -221
  125. package/dist/incoming-call-54KQ7O5A.js +0 -323
  126. package/dist/infographic-before-after-ROS52GOW.js +0 -460
  127. package/dist/infographic-feature-list-E7MGUDAN.js +0 -272
  128. package/dist/infographic-problem-solution-3S6NGO5N.js +0 -343
  129. package/dist/infographic-stat-row-SYB7AB7H.js +0 -247
  130. package/dist/infographic-steps-KCE6JWE2.js +0 -406
  131. package/dist/prompt-input-MHX4O42G.js +0 -239
  132. package/dist/reaction-SLEGR3BE.js +0 -122
  133. package/dist/scene-video-backdrop-XFPX4O3I.js +0 -10
  134. package/dist/showcase-code-2HLAVKBM.js +0 -357
  135. package/dist/showcase-phone-Q4HEO4LH.js +0 -278
  136. package/dist/showcase-terminal-DL45LUFG.js +0 -312
  137. package/dist/showcase-web-2QZO73FZ.js +0 -396
  138. package/dist/social-conversation-75SD2637.js +0 -695
  139. package/dist/social-milestone-GEFUQSBF.js +0 -296
  140. package/dist/social-notification-FJ5VHVLK.js +0 -247
  141. package/dist/social-review-stack-KXQCKFRP.js +0 -242
  142. package/dist/social-testimonial-JZ7CHC7S.js +0 -287
  143. package/dist/social-tweet-UCDXM25F.js +0 -298
  144. package/dist/text-stream-54LXABJW.js +0 -10
  145. package/dist/validate-T7GBU2YF.js +0 -8
  146. package/registry/items/barChart.json +0 -245
  147. package/registry/items/beforeAfter.json +0 -180
  148. package/registry/items/bigNumber.json +0 -232
  149. package/registry/items/brandMessage.json +0 -164
  150. package/registry/items/cardList.json +0 -214
  151. package/registry/items/chatMessenger.json +0 -151
  152. package/registry/items/chatWhatsapp.json +0 -151
  153. package/registry/items/codeEditor.json +0 -191
  154. package/registry/items/confetti.json +0 -107
  155. package/registry/items/ctaLogo.json +0 -167
  156. package/registry/items/ctaMedia.json +0 -200
  157. package/registry/items/emojiBurst.json +0 -101
  158. package/registry/items/incomingCall.json +0 -177
  159. package/registry/items/media.json +0 -187
  160. package/registry/items/milestone.json +0 -212
  161. package/registry/items/notification.json +0 -189
  162. package/registry/items/phoneMockup.json +0 -261
  163. package/registry/items/problemSolution.json +0 -193
  164. package/registry/items/progressRing.json +0 -206
  165. package/registry/items/promptInput.json +0 -155
  166. package/registry/items/reaction.json +0 -176
  167. package/registry/items/reviewStack.json +0 -231
  168. package/registry/items/steps.json +0 -215
  169. package/registry/items/terminal.json +0 -219
  170. package/registry/items/testimonial.json +0 -171
  171. package/registry/items/tripleStats.json +0 -235
  172. package/registry/items/tweet.json +0 -210
  173. package/registry/items/webMockup.json +0 -280
@@ -0,0 +1,7 @@
1
+ import {
2
+ SceneVideoBackdrop
3
+ } from "./chunk-77VY4O7A.js";
4
+ import "./chunk-TVIU23OM.js";
5
+ export {
6
+ SceneVideoBackdrop
7
+ };
package/dist/server.d.ts CHANGED
@@ -1,9 +1,9 @@
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
- import { S as SceneTemplateMetadata } from './catalog-types-BIhSpOWK.js';
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';
1
+ import { k as VideoGenerationContext, l as VideoPlanner, m as VideoRequest, a as VideoCapabilities, j as VideoInput, d as VideoAudio, n as VideoSceneValidator, g as VideoTemplatePacing, o as VideoSnapshotRetention, p as VideoResumeCursor, q as VideoSceneValidationContext, e as VideoScene, b as VideoOrientation } from './types-DdZw4GRQ.js';
2
+ import { a as VideoFinishReason, V as VideoWarning, b as VideoEvent } from './events-B4YCc4vc.js';
3
+ export { c as VideoWarningCategory } from './events-B4YCc4vc.js';
4
+ import { S as SceneTemplateMetadata } from './catalog-types-WTbLP6Jh.js';
5
+ import { f as VideoChatWelcomeOptions } from './types-CdBmNTGD.js';
6
+ export { d as VideoChatCapabilities, g as VideoChatConversationTurn, V as VideoChatMode, h as VideoChatWelcomePrompt } from './types-CdBmNTGD.js';
7
7
 
8
8
  interface VideoProviderUsage {
9
9
  inputTokens?: number;
@@ -170,8 +170,7 @@ interface VideoHandlerOptions extends Omit<VideoStreamHandlerOptions, "generate"
170
170
  *
171
171
  * Unbounded by default, which is right when media is searched for and wrong
172
172
  * when it is generated: the planner decides the scene count, and every scene
173
- * is then a paid clip. Past the ceiling a scene keeps its copy on the brand
174
- * gradient, and a `media_budget_reached` warning says it happened.
173
+ * is then a paid clip. Past the ceiling a scene uses its grounded text fallback, and a `media_budget_reached` warning says it happened.
175
174
  */
176
175
  maxResolvedMedia?: number;
177
176
  /**
package/dist/server.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createTextDeltaVideoPlanner
3
- } from "./chunk-L7QGGKJ2.js";
3
+ } from "./chunk-PDQFQIQW.js";
4
4
  import {
5
5
  createVideo,
6
6
  invokeIsolated
7
- } from "./chunk-3VRMKFM7.js";
7
+ } from "./chunk-ZDJNFA52.js";
8
8
  import {
9
9
  getGenerationLifecycleSink
10
10
  } from "./chunk-E7CL7UPB.js";
@@ -12,10 +12,11 @@ import {
12
12
  BUILTIN_SERVER_TEMPLATE_KIT,
13
13
  createServerTemplateRegistry,
14
14
  overlayServerTemplateRegistry
15
- } from "./chunk-OWBT45EN.js";
15
+ } from "./chunk-IR44XKBI.js";
16
16
  import {
17
- createTemplateSceneValidator
18
- } from "./chunk-LMG3GHLL.js";
17
+ createTemplateSceneValidator,
18
+ validateTemplateSceneStructure
19
+ } from "./chunk-IQMYK5DX.js";
19
20
  import {
20
21
  MEDIA_RECOVERY_NOTICE,
21
22
  decodeVideoSse,
@@ -23,8 +24,8 @@ import {
23
24
  sanitizeVideoChatMedia,
24
25
  videoSseHeaders,
25
26
  withDeadline
26
- } from "./chunk-KT3M7KDL.js";
27
- import "./chunk-2OKB27TG.js";
27
+ } from "./chunk-HKUI5BTB.js";
28
+ import "./chunk-RXTN2CW6.js";
28
29
  import {
29
30
  allowedKeys,
30
31
  createClippedVariableWarning,
@@ -32,30 +33,91 @@ import {
32
33
  jsonValue,
33
34
  nonNegativeInteger,
34
35
  parseVideoEvent,
36
+ parseVideoPlanPart,
35
37
  record,
36
38
  safePublicDiagnostic,
37
39
  string
38
- } from "./chunk-MMUXVA47.js";
40
+ } from "./chunk-7M56IUUX.js";
39
41
  import {
40
42
  MAX_RETAINED_MEDIA_URL_LENGTH
41
- } from "./chunk-2DOLF4QN.js";
43
+ } from "./chunk-AUPC6MDK.js";
42
44
  import {
43
45
  VIDEO_PROTOCOL_VERSION
44
- } from "./chunk-AOWSXU2K.js";
46
+ } from "./chunk-7NZOMTAL.js";
45
47
  import {
46
48
  createTemplateSystemPrompt,
47
49
  getStandardMediaResolverContract
48
- } from "./chunk-AITKH6QT.js";
49
- import "./chunk-5SLENAJW.js";
50
- import "./chunk-352ZZCET.js";
51
- import "./chunk-4UNKZEAT.js";
50
+ } from "./chunk-2HFO22K2.js";
51
+ import "./chunk-PAJ44TFW.js";
52
+ import "./chunk-EGVQODKU.js";
53
+ import "./chunk-IIN5M5HW.js";
52
54
  import "./chunk-3O7OMMMF.js";
53
55
  import "./chunk-2E6T633S.js";
54
- import {
55
- validateBrandInput
56
- } from "./chunk-XGRA2MUV.js";
57
56
  import "./chunk-73NTSFFI.js";
58
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
+
59
121
  // src/server/request-validation.ts
60
122
  var MAX_GENERATED_LOOK_LENGTH = 1e3;
61
123
  function boundedString(value, path) {
@@ -109,7 +171,6 @@ function parseVideoRequest(value) {
109
171
  "orientation",
110
172
  "style",
111
173
  "opening",
112
- "brand",
113
174
  "personalization",
114
175
  "suppliedMedia",
115
176
  "audio"
@@ -224,7 +285,6 @@ function parseVideoRequest(value) {
224
285
  );
225
286
  }
226
287
  }
227
- if (input.brand != null) validateBrandInput(input.brand, "request.input.brand");
228
288
  jsonValue(input, "request.input");
229
289
  if (request.capabilities != null) capabilities(request.capabilities, "request.capabilities");
230
290
  if (request.resume != null) {
@@ -473,7 +533,8 @@ function fallbackVariables(variables) {
473
533
  delete fallback.mediaKeyword;
474
534
  delete fallback.mediaUrl;
475
535
  delete fallback.mediaPoster;
476
- fallback.mediaType = "gradient";
536
+ delete fallback.mediaType;
537
+ delete fallback.mediaSource;
477
538
  return fallback;
478
539
  }
479
540
  async function resolveVariables(options) {
@@ -559,7 +620,18 @@ async function resolvePartVariables(options) {
559
620
  scene: options.part.scene,
560
621
  variables: options.part.scene.variables
561
622
  });
562
- return { ...options.part, scene: { ...options.part.scene, variables } };
623
+ if (options.templateId === "cinemaMedia" && !variables.mediaUrl) {
624
+ const title = variables.fallbackText;
625
+ if (typeof title !== "string" || !title.trim() || [...title].length > 65) {
626
+ throw new Error("A media scene without a usable asset requires grounded fallbackText (1\u201365 characters)");
627
+ }
628
+ return { ...options.part, scene: { ...options.part.scene, templateId: "chapterTitle", variables: { title: title.trim() } } };
629
+ }
630
+ const rendered = { ...variables };
631
+ delete rendered.mediaSource;
632
+ delete rendered.fallbackText;
633
+ delete rendered.shotDirection;
634
+ return { ...options.part, scene: { ...options.part.scene, variables: rendered } };
563
635
  }
564
636
  return options.part;
565
637
  }
@@ -579,6 +651,25 @@ function createMediaResolvingPlanner(options) {
579
651
  }
580
652
  return false;
581
653
  };
654
+ const preflight = (part) => {
655
+ try {
656
+ parseVideoPlanPart(part);
657
+ if (part.type === "scene.add") {
658
+ const { templateId, variables } = part.scene;
659
+ if (context.request.capabilities?.templates && !context.request.capabilities.templates.includes(templateId)) {
660
+ throw new Error(`Scene template ${templateId} was not negotiated`);
661
+ }
662
+ const template = options.templates.getTemplateMetadata(templateId);
663
+ if (!template) throw new Error(`Template ${templateId} is not installed`);
664
+ validateTemplateSceneStructure(template.schema, variables, true);
665
+ }
666
+ return true;
667
+ } catch (cause) {
668
+ const error = cause instanceof Error ? cause : new Error(String(cause));
669
+ if (!getGenerationLifecycleSink(context)?.rejectPart?.(error)) throw error;
670
+ return false;
671
+ }
672
+ };
582
673
  const resolveOne = (part, openingReady2) => resolvePartVariables({
583
674
  part,
584
675
  requestId: context.request.requestId,
@@ -598,7 +689,9 @@ function createMediaResolvingPlanner(options) {
598
689
  })
599
690
  });
600
691
  if (limit === 1) {
601
- for await (const part of options.planner(context)) yield await resolveOne(part);
692
+ for await (const part of options.planner(context)) {
693
+ if (preflight(part)) yield await resolveOne(part);
694
+ }
602
695
  return;
603
696
  }
604
697
  let openingReady = options.isOpeningReady(context.request.input);
@@ -631,6 +724,7 @@ function createMediaResolvingPlanner(options) {
631
724
  const next = await iterator.next();
632
725
  if (next.done || consumerClosed) break;
633
726
  const part = next.value;
727
+ if (!preflight(part)) continue;
634
728
  pending.push(resolveOne(part, openingReady).then(
635
729
  (resolved) => ({ part: resolved }),
636
730
  (cause) => ({ cause })
@@ -680,6 +774,7 @@ function clipText(value, maxLength) {
680
774
  }
681
775
  function boundValue(value, schema) {
682
776
  if (!schema) return value;
777
+ if (schema.format === "grounded-stat" || schema.format === "grounded-quote") return value;
683
778
  if (typeof value === "string") {
684
779
  return typeof schema.maxLength === "number" ? clipText(value, schema.maxLength) : value;
685
780
  }
@@ -707,10 +802,10 @@ function createBoundedVariablePlanner(options) {
707
802
  }
708
803
  try {
709
804
  const lifecycle = getGenerationLifecycleSink(context);
710
- if (!options.templates.getTemplateMetadata(part.scene.templateId) && lifecycle?.recoverGeneratedParts && (context.request.capabilities?.templates == null || context.request.capabilities.templates.includes("media"))) {
805
+ if (!options.templates.getTemplateMetadata(part.scene.templateId) && lifecycle?.recoverGeneratedParts && (context.request.capabilities?.templates == null || context.request.capabilities.templates.includes("chapterTitle"))) {
711
806
  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));
712
807
  if (copy) {
713
- part = { ...part, scene: { ...part.scene, templateId: "media", variables: { texts: copy, mediaType: "gradient" } } };
808
+ part = { ...part, scene: { ...part.scene, templateId: "chapterTitle", variables: { title: copy } } };
714
809
  lifecycle.reportWarning?.({ code: "provider_warning", category: "provider", message: "Some scenes use a simpler layout.", recoverable: true });
715
810
  }
716
811
  }
@@ -830,30 +925,20 @@ function createVideoHandler(options) {
830
925
  }
831
926
 
832
927
  // src/server/video-chat-prompts.ts
833
- var RESPONSE_SCENES = 5;
834
- function createVideoChatResponseInstructions(fullAiVideo, openingAlreadyProvided = false, maxGeneratedVideos = 5) {
928
+ function createVideoChatResponseInstructions(generatedVideoAvailable, openingAlreadyProvided = false, maxGeneratedVideos = 5) {
835
929
  return [
836
- "The input is a prompt from a user. Respond as a short, coherent video.",
837
- "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.",
838
- 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.',
839
- "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.",
840
- fullAiVideo && openingAlreadyProvided ? "Copy the supplied OPENING ALREADY SPOKEN exactly into spokenHook; do not replace or paraphrase it." : void 0,
841
- 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:`,
842
- 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,
843
- fullAiVideo ? void 0 : "1. Open directly on the strongest image, action, claim, or idea.",
844
- "2. Develop it with new information or movement.",
845
- "3. Deepen, complicate, or advance the response.",
846
- "4. Deliver the turn, consequence, recommendation, or emotional peak.",
847
- "5. Close on the payoff or the one thing worth carrying forward, never a recap.",
848
- "No scene may repeat another scene's job. If a scene could be deleted without weakening the response, replace it.",
849
- "The prompt is already on screen, so never spend the opening scene restating it.",
850
- "If the input includes an OPENING ALREADY SPOKEN transcript, continue directly from that exact hook. Never repeat, paraphrase, contradict, or render it as the first scene's copy.",
851
- "For factual requests, distinguish supplied facts from stable general knowledge and never invent statistics, quotations, sources, or personal experience.",
852
- "For creative requests, create the requested material directly rather than explaining how one might create it.",
853
- fullAiVideo ? "Use the media template for every scene you emit. Every scene must set mediaType to video and carry a mediaKeyword: a concrete, filmable subject, a real object doing a real thing, never a diagram or an abstraction. Never use mediaType gradient and never omit mediaKeyword - a scene without one has nothing to film. If a beat seems too abstract to film, film the closest real thing that shows it." : "Choose the template that fits each beat honestly - a figure belongs in a number scene, an ordered process in steps, a comparison in a chart - rather than repeating one shape. Give any scene that would be stronger over real footage a mediaKeyword: a concrete, filmable subject, a real object doing a real thing, never a diagram or an abstraction. Use the media template for the one or two beats most worth watching happen.",
854
- fullAiVideo ? 'Mark the final scene placement:"closer". Use the media template for it, as for every other beat.' : 'Mark the final scene placement:"closer" and use either the milestone or the media template for it. Those are the only two here that may close, so the response must end on one.',
855
- "Every scene must carry a timing object, even an empty one. A scene without it fails the whole response.",
856
- "Never mention the video, the scenes, or yourself."
930
+ "Respond as a coherent short film: establish, develop, then land a useful or emotional payoff. Match the user's requested form.",
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.',
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.",
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.",
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.",
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.`,
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.",
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.",
940
+ 'Emit exactly one final placement:"closer" scene using a catalog template with a suitable payoff or ask job. It should land the meaning, not recap the whole answer.',
941
+ "Every scene needs timing, even an empty object. Narration forms one continuous spoken explanation, uses natural sentence lengths, and does not read every on-screen word back. Finish with plan.complete."
857
942
  ].filter((line) => line != null).join("\n");
858
943
  }
859
944
  var VIDEO_CHAT_NARRATION_PROMPT = [
@@ -944,10 +1029,10 @@ function boundedString2(value, label, maximum = MAX_PROMPT_CHARACTERS) {
944
1029
  }
945
1030
  function parseResponseRequest(value) {
946
1031
  const body = record2(value, "request");
947
- allowedKeys2(body, ["prompt", "opening", "mode", "orientation", "conversation", "brand", "style"], "request");
948
- const mode = body.mode ?? "templates";
949
- if (mode !== "templates" && mode !== "full") {
950
- throw new Error("request.mode must be templates or full");
1032
+ allowedKeys2(body, ["prompt", "opening", "mode", "orientation", "conversation", "style"], "request");
1033
+ const mode = body.mode ?? "cinematic";
1034
+ if (mode !== "cinematic") {
1035
+ throw new Error("request.mode must be cinematic");
951
1036
  }
952
1037
  const orientation = body.orientation ?? "landscape";
953
1038
  if (orientation !== "portrait" && orientation !== "landscape") {
@@ -970,7 +1055,6 @@ function parseResponseRequest(value) {
970
1055
  ...turn.response == null ? {} : { response: boundedString2(turn.response, `request.conversation[${index}].response`, MAX_CONVERSATION_RESPONSE_CHARACTERS) }
971
1056
  };
972
1057
  }),
973
- ...body.brand == null ? {} : { brand: body.brand },
974
1058
  ...body.style == null ? {} : { style: body.style }
975
1059
  };
976
1060
  }
@@ -1015,10 +1099,11 @@ function readGeneratedFirstShot(value) {
1015
1099
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1016
1100
  const shot = value;
1017
1101
  const bounded = (field, maximum) => typeof field === "string" ? [...field.trim()].slice(0, maximum).join("").trim() : "";
1018
- const text = bounded(shot.text, 120);
1102
+ const text = bounded(shot.text, 65);
1019
1103
  const narration = bounded(shot.narration, 300);
1020
1104
  const mediaKeyword = bounded(shot.mediaKeyword, 80).match(/\S+/gu)?.slice(0, 8).join(" ") ?? "";
1021
- return text && narration && mediaKeyword ? { text, narration, mediaKeyword } : void 0;
1105
+ const shotDirection = bounded(shot.shotDirection, 220);
1106
+ return text && narration && mediaKeyword ? { text, narration, mediaKeyword, ...shotDirection ? { shotDirection } : {} } : void 0;
1022
1107
  }
1023
1108
  function boundedWords(value, maximum, characters) {
1024
1109
  if (typeof value !== "string") return "";
@@ -1044,9 +1129,11 @@ function reservedFirstScene(requestId, firstShot) {
1044
1129
  type: "scene.add",
1045
1130
  scene: {
1046
1131
  id: `${requestId}-first-shot`,
1047
- templateId: "media",
1132
+ templateId: "cinemaMedia",
1048
1133
  variables: {
1049
- texts: firstShot.text,
1134
+ fallbackText: firstShot.text,
1135
+ mediaSource: "generate",
1136
+ ...firstShot.shotDirection ? { shotDirection: firstShot.shotDirection } : {},
1050
1137
  mediaType: "video",
1051
1138
  mediaKeyword: firstShot.mediaKeyword
1052
1139
  },
@@ -1088,6 +1175,19 @@ function interceptOpeningPlan(source, options) {
1088
1175
  const textStream = (async function* () {
1089
1176
  let buffer = "";
1090
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
+ };
1091
1191
  try {
1092
1192
  for await (const delta of upstream) {
1093
1193
  if (typeof delta !== "string") throw new Error("The LLM adapter returned a non-text delta");
@@ -1101,17 +1201,16 @@ function interceptOpeningPlan(source, options) {
1101
1201
  const opening = readOpeningPlanLine(line);
1102
1202
  decided = true;
1103
1203
  if (opening) {
1104
- if (!options.openingProvided) options.publish(opening.line ? opening : void 0);
1105
- if (options.fullAiVideo && opening.firstShot) {
1106
- yield `${JSON.stringify(reservedFirstScene(options.requestId, opening.firstShot))}
1204
+ const firstScene = acceptOpening(opening);
1205
+ if (firstScene) yield `${firstScene}
1107
1206
  `;
1108
- }
1109
1207
  newline = buffer.indexOf("\n");
1110
1208
  continue;
1111
1209
  }
1112
1210
  options.publish(void 0);
1113
1211
  }
1114
- yield `${rawLine}
1212
+ const continued = continuation.line(rawLine);
1213
+ if (continued != null) yield `${continued}
1115
1214
  `;
1116
1215
  newline = buffer.indexOf("\n");
1117
1216
  }
@@ -1122,17 +1221,17 @@ function interceptOpeningPlan(source, options) {
1122
1221
  const opening = readOpeningPlanLine(line);
1123
1222
  decided = true;
1124
1223
  if (opening) {
1125
- if (!options.openingProvided) options.publish(opening.line ? opening : void 0);
1126
- if (options.fullAiVideo && opening.firstShot) {
1127
- yield `${JSON.stringify(reservedFirstScene(options.requestId, opening.firstShot))}
1224
+ const firstScene = acceptOpening(opening);
1225
+ if (firstScene) yield `${firstScene}
1128
1226
  `;
1129
- }
1130
1227
  } else {
1131
1228
  options.publish(void 0);
1132
- yield buffer;
1229
+ const continued = continuation.line(buffer);
1230
+ if (continued != null) yield continued;
1133
1231
  }
1134
1232
  } else {
1135
- yield buffer;
1233
+ const continued = continuation.line(buffer);
1234
+ if (continued != null) yield continued;
1136
1235
  }
1137
1236
  }
1138
1237
  } finally {
@@ -1303,21 +1402,20 @@ function createVideoChatHandler(options) {
1303
1402
  generatedVideo: generateVideo != null,
1304
1403
  stockMedia: searchMedia != null,
1305
1404
  transcription: transcribe != null,
1306
- modes: generateVideo ? ["templates", "full"] : ["templates"]
1405
+ modes: ["cinematic"]
1307
1406
  };
1308
1407
  const welcomePrompts = (welcomeOptions?.prompts ?? DEFAULT_WELCOME_PROMPTS).slice(0, 4);
1309
1408
  const heroQuery = welcomeOptions?.heroQuery;
1310
1409
  let welcomeResponse;
1311
1410
  let requestSequence = 0;
1312
- const responseHandler = (requestedMode, requestId, openingProvided, openingChannel) => {
1313
- const mode = requestedMode !== "templates" && !generateVideo ? "templates" : requestedMode;
1314
- const fullAiVideo = mode === "full";
1411
+ const responseHandler = (requestId, openingProvided, openingChannel, openingLine) => {
1412
+ const generatedVideoAvailable = generateVideo != null && maxGeneratedVideos > 0;
1315
1413
  let lifecycle;
1316
1414
  let generatedAttempts = 0;
1317
1415
  const completedVideos = /* @__PURE__ */ new Map();
1318
1416
  const reusedUrls = /* @__PURE__ */ new Set();
1319
1417
  const resolveSelected = generateVideo || searchMedia ? async (query, context) => {
1320
- const reuseKey = JSON.stringify([query, context.generatedLook ?? ""]);
1418
+ const reuseKey = JSON.stringify([query, context.generatedLook ?? "", context.scene.variables.shotDirection ?? "", context.input.orientation ?? "landscape"]);
1321
1419
  const remember = (media) => {
1322
1420
  if (media?.type === "video") completedVideos.set(reuseKey, media);
1323
1421
  return media;
@@ -1349,7 +1447,7 @@ function createVideoChatHandler(options) {
1349
1447
  return null;
1350
1448
  }
1351
1449
  };
1352
- if (fullAiVideo) {
1450
+ if (generatedVideoAvailable && context.scene.variables.mediaSource === "generate") {
1353
1451
  const generated = generatedAttempts < maxGeneratedVideos ? (generatedAttempts++, await attempt(generateVideo, generateVideoTimeoutMs)) : null;
1354
1452
  if (generated) return remember(generated);
1355
1453
  lifecycle?.reportWarning?.({
@@ -1361,7 +1459,7 @@ function createVideoChatHandler(options) {
1361
1459
  }
1362
1460
  const stock = await attempt(searchMedia, 3e3);
1363
1461
  if (stock) return remember(stock);
1364
- if (fullAiVideo) {
1462
+ if (generatedVideoAvailable) {
1365
1463
  const previous = completedVideos.get(reuseKey);
1366
1464
  if (previous && !reusedUrls.has(previous.url)) {
1367
1465
  reusedUrls.add(previous.url);
@@ -1376,10 +1474,11 @@ function createVideoChatHandler(options) {
1376
1474
  lifecycle = getGenerationLifecycleSink(context);
1377
1475
  try {
1378
1476
  return interceptOpeningPlan(videoOptions.streamText(context), {
1379
- expectOpening: fullAiVideo || !openingProvided,
1477
+ expectOpening: generatedVideoAvailable || !openingProvided,
1380
1478
  openingProvided,
1479
+ openingLine,
1381
1480
  requestId,
1382
- fullAiVideo,
1481
+ generatedVideoAvailable,
1383
1482
  publish: openingChannel.publish
1384
1483
  });
1385
1484
  } catch (cause) {
@@ -1392,7 +1491,7 @@ function createVideoChatHandler(options) {
1392
1491
  allowCredentials,
1393
1492
  maxBodyBytes,
1394
1493
  mediaConcurrency,
1395
- basePrompt: [createVideoChatResponseInstructions(fullAiVideo, openingProvided, maxGeneratedVideos), instructions?.trim()].filter(Boolean).join("\n\nAPPLICATION GUIDANCE\n"),
1494
+ basePrompt: [createVideoChatResponseInstructions(generatedVideoAvailable, openingProvided, maxGeneratedVideos), instructions?.trim()].filter(Boolean).join("\n\nAPPLICATION GUIDANCE\n"),
1396
1495
  narrate: true,
1397
1496
  resolveMedia: resolveSelected
1398
1497
  });
@@ -1518,8 +1617,6 @@ function createVideoChatHandler(options) {
1518
1617
  } catch (cause) {
1519
1618
  return jsonError2(400, "invalid_request", cause instanceof Error ? cause.message : "Request is invalid", headers);
1520
1619
  }
1521
- const mode = input.mode !== "templates" && !generateVideo ? "templates" : input.mode;
1522
- const fullAiVideo = mode === "full";
1523
1620
  const requestId = `video-chat-${Date.now()}-${requestSequence += 1}`;
1524
1621
  const openingChannel = createOpeningChannel(input.opening ? { line: input.opening, keyword: "" } : void 0);
1525
1622
  const forwardedHeaders = new Headers(request.headers);
@@ -1529,16 +1626,14 @@ function createVideoChatHandler(options) {
1529
1626
  headers: forwardedHeaders,
1530
1627
  signal: request.signal,
1531
1628
  body: JSON.stringify({
1532
- protocolVersion: "0.5",
1629
+ protocolVersion: VIDEO_PROTOCOL_VERSION,
1533
1630
  requestId,
1534
- ...fullAiVideo ? { capabilities: { templates: ["media"] } } : {},
1535
1631
  input: {
1536
1632
  input: conversationInput(input.prompt, input.conversation, input.opening),
1537
1633
  knowledgeMode: "general",
1538
1634
  opening: false,
1539
1635
  orientation: input.orientation,
1540
1636
  maxDurationSec: 40,
1541
- ...input.brand ? { brand: input.brand } : {},
1542
1637
  style: {
1543
1638
  density: "airy",
1544
1639
  motion: "calm",
@@ -1549,10 +1644,10 @@ function createVideoChatHandler(options) {
1549
1644
  })
1550
1645
  });
1551
1646
  const response = await responseHandler(
1552
- mode,
1553
1647
  requestId,
1554
1648
  input.opening != null,
1555
- openingChannel
1649
+ openingChannel,
1650
+ input.opening
1556
1651
  )(videoRequest);
1557
1652
  return streamVideoChatOpening(response, openingChannel.ready);
1558
1653
  }
@@ -3,7 +3,7 @@ import {
3
3
  VIDEO_PLAN_INSTRUCTION,
4
4
  createVideoSystemPrompt,
5
5
  videoPlanInstruction
6
- } from "./chunk-5SLENAJW.js";
6
+ } from "./chunk-PAJ44TFW.js";
7
7
  export {
8
8
  DEFAULT_VIDEO_SYSTEM_PROMPT,
9
9
  VIDEO_PLAN_INSTRUCTION,