@vanillaskyai/video 0.1.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 (162) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +201 -0
  3. package/PUBLIC-API.md +276 -0
  4. package/README.md +152 -0
  5. package/SECURITY.md +22 -0
  6. package/SUPPORT.md +28 -0
  7. package/bin/vanillasky.js +3 -0
  8. package/dist/bg-confetti-WNQXS7ZS.js +89 -0
  9. package/dist/bg-emoji-ETI47GLS.js +150 -0
  10. package/dist/bg-media-SVUZAMGX.js +99 -0
  11. package/dist/brand-message-F3RD4P3P.js +253 -0
  12. package/dist/builtin-server-F7YXRVCH.js +8 -0
  13. package/dist/catalog-types-BIhSpOWK.d.ts +74 -0
  14. package/dist/chart-bar-CNSC7BNK.js +304 -0
  15. package/dist/chart-counter-MCRHKN77.js +299 -0
  16. package/dist/chart-progress-ring-RMI4A7ZK.js +296 -0
  17. package/dist/check-runtime.d.ts +15 -0
  18. package/dist/check-runtime.js +102 -0
  19. package/dist/chunk-2E6T633S.js +27 -0
  20. package/dist/chunk-352ZZCET.js +83 -0
  21. package/dist/chunk-73NTSFFI.js +81 -0
  22. package/dist/chunk-A2X45FET.js +46 -0
  23. package/dist/chunk-ABPKM4GK.js +187 -0
  24. package/dist/chunk-ABZACD4G.js +216 -0
  25. package/dist/chunk-BCRUUJ6A.js +756 -0
  26. package/dist/chunk-CHLGW22F.js +3551 -0
  27. package/dist/chunk-CJ7F2CE6.js +104 -0
  28. package/dist/chunk-CR7FE7BW.js +109 -0
  29. package/dist/chunk-E7CL7UPB.js +109 -0
  30. package/dist/chunk-EE6PK6XC.js +18 -0
  31. package/dist/chunk-ERY4S6QV.js +385 -0
  32. package/dist/chunk-EVG2ZYIY.js +125 -0
  33. package/dist/chunk-FNAEQ7QO.js +155 -0
  34. package/dist/chunk-FQ47WXAJ.js +38 -0
  35. package/dist/chunk-FSG2PYWG.js +336 -0
  36. package/dist/chunk-GHJEAP4O.js +504 -0
  37. package/dist/chunk-GRMUV7QY.js +262 -0
  38. package/dist/chunk-IFOW65Z6.js +205 -0
  39. package/dist/chunk-IXCUY3B3.js +786 -0
  40. package/dist/chunk-L5BTTCZV.js +97 -0
  41. package/dist/chunk-LMG3GHLL.js +107 -0
  42. package/dist/chunk-OOPIETLW.js +101 -0
  43. package/dist/chunk-P22HHFDK.js +43 -0
  44. package/dist/chunk-QAAO7OAY.js +19 -0
  45. package/dist/chunk-S7HFLYNN.js +35 -0
  46. package/dist/chunk-SPVTJH3F.js +24 -0
  47. package/dist/chunk-W2XJKG6C.js +3177 -0
  48. package/dist/chunk-XB7LH4J2.js +301 -0
  49. package/dist/chunk-XGRA2MUV.js +242 -0
  50. package/dist/chunk-XTOXFMLM.js +105 -0
  51. package/dist/chunk-YC5MFXA5.js +132 -0
  52. package/dist/cli.d.ts +1 -0
  53. package/dist/cli.js +1681 -0
  54. package/dist/compose-video-CN3VSWST.js +13 -0
  55. package/dist/cta-logo-SVA5GP2A.js +209 -0
  56. package/dist/cta-media-E7VLLK42.js +215 -0
  57. package/dist/events-_wfjbmEp.d.ts +81 -0
  58. package/dist/incoming-call-62MFZL3Z.js +320 -0
  59. package/dist/index.d.ts +15 -0
  60. package/dist/index.js +14 -0
  61. package/dist/infographic-before-after-AXCDAWND.js +460 -0
  62. package/dist/infographic-feature-list-GW5GUE6O.js +266 -0
  63. package/dist/infographic-problem-solution-7NURJANA.js +340 -0
  64. package/dist/infographic-stat-row-J7IZR6ZN.js +241 -0
  65. package/dist/infographic-steps-VSZM6F5M.js +400 -0
  66. package/dist/kit-BMNcHqa9.d.ts +79 -0
  67. package/dist/prompt-input-LOUCRXCY.js +236 -0
  68. package/dist/react.d.ts +81 -0
  69. package/dist/react.js +835 -0
  70. package/dist/reaction-QG4CYZLQ.js +119 -0
  71. package/dist/server.d.ts +141 -0
  72. package/dist/server.js +498 -0
  73. package/dist/showcase-code-RUTFZR3H.js +351 -0
  74. package/dist/showcase-phone-6CP6XEU5.js +272 -0
  75. package/dist/showcase-terminal-MGAEQWPR.js +306 -0
  76. package/dist/showcase-web-Y4W2HVRJ.js +390 -0
  77. package/dist/social-conversation-75SD2637.js +695 -0
  78. package/dist/social-milestone-XI2QE5BO.js +292 -0
  79. package/dist/social-notification-2UOHQCUY.js +244 -0
  80. package/dist/social-review-stack-FDYZGZTJ.js +239 -0
  81. package/dist/social-testimonial-OQ7N52GJ.js +284 -0
  82. package/dist/social-tweet-5OLCNPPC.js +295 -0
  83. package/dist/state-DZcKuS32.d.ts +3 -0
  84. package/dist/system-prompt-A6UXNTPK.js +8 -0
  85. package/dist/template-catalog.d.ts +24 -0
  86. package/dist/template-catalog.js +6 -0
  87. package/dist/templates.d.ts +25 -0
  88. package/dist/templates.js +19 -0
  89. package/dist/test.d.ts +149 -0
  90. package/dist/test.js +272 -0
  91. package/dist/text-stream-6XHC7J47.js +10 -0
  92. package/dist/types-B-PZR6G_.d.ts +208 -0
  93. package/dist/validate-T7GBU2YF.js +8 -0
  94. package/docs/agent-integration.md +39 -0
  95. package/docs/architecture.md +84 -0
  96. package/docs/branding-and-personalization.md +87 -0
  97. package/docs/concepts.md +106 -0
  98. package/docs/custom-templates.md +329 -0
  99. package/docs/customization.md +141 -0
  100. package/docs/errors.md +70 -0
  101. package/docs/getting-started.md +111 -0
  102. package/docs/input-and-first-scene.md +41 -0
  103. package/docs/integrate-nextjs.md +79 -0
  104. package/docs/media-and-audio.md +69 -0
  105. package/docs/motion-and-effects.md +107 -0
  106. package/docs/persistence.md +95 -0
  107. package/docs/production.md +122 -0
  108. package/docs/prompt-and-input.md +185 -0
  109. package/docs/provider-integration.md +115 -0
  110. package/docs/reference/protocol.md +96 -0
  111. package/docs/reference/provider-adapters.md +158 -0
  112. package/docs/responsive-orientation.md +37 -0
  113. package/docs/security.md +38 -0
  114. package/docs/streaming-protocol.md +15 -0
  115. package/docs/testing.md +137 -0
  116. package/docs/use-cases.md +59 -0
  117. package/examples/custom-template/README.md +19 -0
  118. package/examples/custom-template/minimal-text.tsx +82 -0
  119. package/examples/custom-template/structured-data.tsx +104 -0
  120. package/examples/custom-template/supplied-media.tsx +92 -0
  121. package/examples/nextjs-quickstart/.env.example +2 -0
  122. package/examples/nextjs-quickstart/README.md +22 -0
  123. package/examples/nextjs-quickstart/next-env.d.ts +4 -0
  124. package/examples/nextjs-quickstart/package.json +24 -0
  125. package/examples/nextjs-quickstart/src/app/api/video/route.ts +22 -0
  126. package/examples/nextjs-quickstart/src/app/layout.tsx +5 -0
  127. package/examples/nextjs-quickstart/src/app/page.tsx +31 -0
  128. package/examples/nextjs-quickstart/tsconfig.json +26 -0
  129. package/package.json +167 -0
  130. package/registry/items/backgrounds.json +52 -0
  131. package/registry/items/barChart.json +237 -0
  132. package/registry/items/beforeAfter.json +180 -0
  133. package/registry/items/bigNumber.json +225 -0
  134. package/registry/items/brandMessage.json +158 -0
  135. package/registry/items/cardList.json +206 -0
  136. package/registry/items/chatMessenger.json +151 -0
  137. package/registry/items/chatWhatsapp.json +151 -0
  138. package/registry/items/codeEditor.json +184 -0
  139. package/registry/items/confetti.json +107 -0
  140. package/registry/items/ctaLogo.json +160 -0
  141. package/registry/items/ctaMedia.json +193 -0
  142. package/registry/items/emojiBurst.json +101 -0
  143. package/registry/items/incomingCall.json +170 -0
  144. package/registry/items/media.json +179 -0
  145. package/registry/items/milestone.json +205 -0
  146. package/registry/items/motion.json +45 -0
  147. package/registry/items/notification.json +182 -0
  148. package/registry/items/phoneMockup.json +254 -0
  149. package/registry/items/problemSolution.json +186 -0
  150. package/registry/items/progressRing.json +199 -0
  151. package/registry/items/promptInput.json +148 -0
  152. package/registry/items/reaction.json +170 -0
  153. package/registry/items/reviewStack.json +224 -0
  154. package/registry/items/steps.json +207 -0
  155. package/registry/items/template-context.json +31 -0
  156. package/registry/items/terminal.json +212 -0
  157. package/registry/items/testimonial.json +164 -0
  158. package/registry/items/theme.json +47 -0
  159. package/registry/items/tripleStats.json +228 -0
  160. package/registry/items/tweet.json +203 -0
  161. package/registry/items/typography.json +47 -0
  162. package/registry/items/webMockup.json +273 -0
@@ -0,0 +1,786 @@
1
+ import {
2
+ attachGenerationLifecycleSink
3
+ } from "./chunk-E7CL7UPB.js";
4
+ import {
5
+ applyVideoEvent,
6
+ checksumVideo,
7
+ createVideoState
8
+ } from "./chunk-ABPKM4GK.js";
9
+ import {
10
+ parseVideoPlanPart,
11
+ safePublicDiagnostic
12
+ } from "./chunk-ERY4S6QV.js";
13
+ import {
14
+ MAX_RETAINED_INSTRUCTIONS_LENGTH,
15
+ MAX_RETAINED_MEDIA_URLS,
16
+ MAX_RETAINED_MEDIA_URL_LENGTH,
17
+ MAX_RETAINED_SOURCE_LENGTH,
18
+ parseVideo
19
+ } from "./chunk-GHJEAP4O.js";
20
+ import {
21
+ VIDEO_PROTOCOL_VERSION,
22
+ VIDEO_SCHEMA_VERSION,
23
+ createVideoRequest
24
+ } from "./chunk-EE6PK6XC.js";
25
+ import {
26
+ getCloserReserve,
27
+ getReadableSceneDuration,
28
+ paceScene
29
+ } from "./chunk-EVG2ZYIY.js";
30
+ import {
31
+ resolveVideoBrand
32
+ } from "./chunk-XGRA2MUV.js";
33
+
34
+ // src/protocol/events.ts
35
+ function createVideoEventFactory(options) {
36
+ let sequence = options.initialSequence ?? 0;
37
+ return {
38
+ create(type, data) {
39
+ const currentSequence = sequence;
40
+ sequence += 1;
41
+ return {
42
+ protocolVersion: VIDEO_PROTOCOL_VERSION,
43
+ runId: options.runId,
44
+ sequence: currentSequence,
45
+ eventId: `${options.runId}:${currentSequence}`,
46
+ type,
47
+ data
48
+ };
49
+ }
50
+ };
51
+ }
52
+
53
+ // src/server/prompts/user-prompt.ts
54
+ var SUPPLIED_MEDIA_REFERENCE_BASE = "https://vanillasky.invalid/supplied/";
55
+ function suppliedMediaReferences(input) {
56
+ return (input.suppliedMedia ?? []).map((media, index) => ({
57
+ reference: `${SUPPLIED_MEDIA_REFERENCE_BASE}media-${index + 1}`,
58
+ ...media.posterUrl ? { posterReference: `${SUPPLIED_MEDIA_REFERENCE_BASE}poster-${index + 1}` } : {},
59
+ type: media.type,
60
+ ...media.description ? { description: media.description } : {},
61
+ ...media.mimeType ? { mimeType: media.mimeType } : {},
62
+ ...media.focalPoint ? { focalPoint: media.focalPoint } : {},
63
+ ...media.treatment ? { treatment: media.treatment } : {},
64
+ ...media.role ? { role: media.role } : {}
65
+ }));
66
+ }
67
+ function mediaReferenceMap(input) {
68
+ const result = /* @__PURE__ */ new Map();
69
+ for (const [index, media] of (input.suppliedMedia ?? []).entries()) {
70
+ result.set(`${SUPPLIED_MEDIA_REFERENCE_BASE}media-${index + 1}`, media.url);
71
+ if (media.posterUrl) result.set(`${SUPPLIED_MEDIA_REFERENCE_BASE}poster-${index + 1}`, media.posterUrl);
72
+ }
73
+ return result;
74
+ }
75
+ function resolveReferences(value, references) {
76
+ if (typeof value === "string") return references.get(value) ?? value;
77
+ if (Array.isArray(value)) return value.map((item) => resolveReferences(item, references));
78
+ if (!value || typeof value !== "object") return value;
79
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, resolveReferences(child, references)]));
80
+ }
81
+ function resolveSuppliedMediaPlanPart(part, input) {
82
+ const references = mediaReferenceMap(input);
83
+ if (references.size === 0) return part;
84
+ if (part.type === "scene.add") {
85
+ return { ...part, scene: { ...part.scene, variables: resolveReferences(part.scene.variables, references) } };
86
+ }
87
+ if (part.type === "scene.patch" && part.patch.variables) {
88
+ return { ...part, patch: { ...part.patch, variables: resolveReferences(part.patch.variables, references) } };
89
+ }
90
+ if (part.type === "asset.patch") {
91
+ return { ...part, variables: resolveReferences(part.variables, references) };
92
+ }
93
+ return part;
94
+ }
95
+ function buildVideoUserPrompt(input, openingDurationSec = 0) {
96
+ return [
97
+ "Compose a video response from the structured customer input below.",
98
+ `Maximum duration: ${input.maxDurationSec ?? 30} seconds, including the supplied opening.`,
99
+ input.opening?.trim() ? `The host has already added the opening scene, which consumes ${openingDurationSec} seconds. Continue after it and do not repeat or rewrite it.` : "Add the first grounded scene as soon as it is complete.",
100
+ "The first generated body scene must be fully playable without external media. Use a content-fit text, data, comparison, list, or device-free template with no media URL or keyword.",
101
+ "Never use media, ctaMedia, or reaction as the first generated body template, even with mediaType=gradient. Choose a non-media template first.",
102
+ "Add that scene before resolving any stock or supplied asset. Media belongs on later body scenes and must arrive without blocking scene additions.",
103
+ "The raw input is the complete factual boundary. Do not add outside claims.",
104
+ "Select the most decision-relevant grounded takeaways that fit the duration; represent each selected takeaway once before completing the response.",
105
+ "For a long source, summarize instead of attempting to represent every fact, unless the creative instructions explicitly request complete fact coverage that fits the duration.",
106
+ "Preserve exact wording and numbers for the takeaways you select. Preserve qualifiers, units, denominators, ranges, and comparison direction; for example, do not shorten 4.8 out of 5 to 4.8.",
107
+ "Choose the scene count from the distinct grounded material and the duration budget. Use fewer scenes for sparse input; continue beyond five when rich input warrants it and timing allows.",
108
+ "Use a different suitable template for each body scene when the catalog supports it.",
109
+ "Never add filler to satisfy a count or diversity target.",
110
+ input.suppliedMedia?.length ? "Select zero or more relevant opaque supplied-media references for visible later scenes. Never invent or transform a reference, and never emit mediaKeyword without a resolved mediaUrl." : "Use asset-free templates. No media URL has been supplied or resolved. Never emit mediaKeyword without a resolved mediaUrl.",
111
+ "",
112
+ "RAW INPUT",
113
+ input.input.trim(),
114
+ "",
115
+ "CREATIVE INSTRUCTIONS",
116
+ input.instructions?.trim() || "None supplied.",
117
+ "",
118
+ "PERSONALIZATION",
119
+ JSON.stringify(input.personalization ?? {}),
120
+ "",
121
+ "SUPPLIED MEDIA",
122
+ JSON.stringify(suppliedMediaReferences(input)),
123
+ "",
124
+ "BRAND",
125
+ JSON.stringify({
126
+ ...input.brand?.name?.trim() ? { name: input.brand.name.trim() } : {},
127
+ hasLogo: Boolean(input.brand?.logoUrl?.trim())
128
+ })
129
+ ].join("\n");
130
+ }
131
+
132
+ // src/server/compose-video.ts
133
+ function invokeIsolated(callback, value) {
134
+ if (!callback) return;
135
+ try {
136
+ void Promise.resolve(callback(value)).catch(() => void 0);
137
+ } catch {
138
+ }
139
+ }
140
+ function cloneWarning(warning) {
141
+ return {
142
+ code: warning.code,
143
+ category: warning.category,
144
+ message: warning.message,
145
+ ...warning.sceneId != null ? { sceneId: warning.sceneId } : {},
146
+ recoverable: warning.recoverable
147
+ };
148
+ }
149
+ function now() {
150
+ return globalThis.performance?.now?.() ?? Date.now();
151
+ }
152
+ function safeAbortReason(value) {
153
+ if (value instanceof DOMException && value.name === "TimeoutError") return "Request timed out";
154
+ if (value instanceof Error && value.name === "TimeoutError") return "Request timed out";
155
+ return safePublicDiagnostic(value instanceof Error ? value.message : value, "Request aborted");
156
+ }
157
+ function buildInitialConfig(input, audio, snapshotRetention, closerReserveSec, getTemplatePacing) {
158
+ const openingText = input.opening?.trim();
159
+ const rawScenes = openingText ? [{
160
+ id: "supplied-opening",
161
+ templateId: "notification",
162
+ variables: {
163
+ appName: input.brand?.name ?? "",
164
+ message: openingText
165
+ },
166
+ timing: { fixedDuration: 5 }
167
+ }] : [];
168
+ const scenes = [];
169
+ const warnings = [];
170
+ for (const scene of rawScenes) {
171
+ const maxDurationSec = input.maxDurationSec ?? 30;
172
+ const readableMinimum = getReadableSceneDuration(scene, getTemplatePacing?.(scene.templateId));
173
+ if (maxDurationSec < readableMinimum) {
174
+ throw new Error("Video response maximum duration cannot fit the supplied opening readably");
175
+ }
176
+ const paced = paceScene(scene, {
177
+ previousScenes: scenes,
178
+ audio,
179
+ maxDurationSec,
180
+ closerReserveSec: Math.min(closerReserveSec, maxDurationSec - readableMinimum),
181
+ getTemplatePacing
182
+ });
183
+ if (!paced.scene) throw new Error("Video response maximum duration cannot fit the supplied opening readably");
184
+ scenes.push(paced.scene);
185
+ warnings.push(...paced.warnings);
186
+ }
187
+ const meta = {
188
+ name: input.brand?.name?.trim() || "Video response"
189
+ };
190
+ if (snapshotRetention?.source) {
191
+ meta.source = input.input.trim().slice(0, MAX_RETAINED_SOURCE_LENGTH);
192
+ }
193
+ if (snapshotRetention?.instructions && input.instructions?.trim()) {
194
+ meta.prompt = input.instructions.trim().slice(0, MAX_RETAINED_INSTRUCTIONS_LENGTH);
195
+ }
196
+ if (snapshotRetention?.suppliedMediaUrls && input.suppliedMedia?.length) {
197
+ meta.uploadedMediaUrls = input.suppliedMedia.slice(0, MAX_RETAINED_MEDIA_URLS).map((item) => item.url);
198
+ }
199
+ return { config: {
200
+ schemaVersion: VIDEO_SCHEMA_VERSION,
201
+ orientation: input.orientation ?? "portrait",
202
+ scenes,
203
+ ...audio ? { audio } : {},
204
+ style: {
205
+ brand: resolveVideoBrand(input.brand),
206
+ density: input.style?.density ?? "normal",
207
+ motion: input.style?.motion ?? "normal",
208
+ defaultBackgroundEffect: input.style?.backgroundEffect ?? "slow-zoom-in",
209
+ defaultTextArchetype: input.style?.textArchetype ?? "subtle"
210
+ },
211
+ meta
212
+ }, warnings };
213
+ }
214
+ function validateInput(input) {
215
+ if (!input.input.trim()) throw new Error("Video response input is required");
216
+ if (input.maxDurationSec != null && (!Number.isFinite(input.maxDurationSec) || input.maxDurationSec < 5 || input.maxDurationSec > 120)) {
217
+ throw new Error("Video response maximum duration must be between 5 and 120 seconds");
218
+ }
219
+ if (input.opening != null && !input.opening.trim()) {
220
+ throw new Error("Video response opening must be a non-empty string");
221
+ }
222
+ if (input.audio && !input.audio.src.trim()) {
223
+ throw new Error("Video response audio src must be a non-empty string");
224
+ }
225
+ if (input.audio && input.audio.src.length > MAX_RETAINED_MEDIA_URL_LENGTH) {
226
+ throw new Error(`Video response audio src must be at most ${MAX_RETAINED_MEDIA_URL_LENGTH} characters`);
227
+ }
228
+ for (const [index, media] of (input.suppliedMedia ?? []).entries()) {
229
+ if (media.url.length > MAX_RETAINED_MEDIA_URL_LENGTH) {
230
+ throw new Error(`Video response supplied media ${index} URL must be at most ${MAX_RETAINED_MEDIA_URL_LENGTH} characters`);
231
+ }
232
+ if (media.posterUrl && media.posterUrl.length > MAX_RETAINED_MEDIA_URL_LENGTH) {
233
+ throw new Error(`Video response supplied media ${index} poster URL must be at most ${MAX_RETAINED_MEDIA_URL_LENGTH} characters`);
234
+ }
235
+ }
236
+ resolveVideoBrand(input.brand);
237
+ }
238
+ function createId(prefix) {
239
+ const randomId = globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
240
+ return `${prefix}-${randomId}`;
241
+ }
242
+ function createPatchReadabilityWarning(sceneId) {
243
+ return {
244
+ code: "scene_patch_rejected_readability",
245
+ category: "readability",
246
+ message: "Scene update was omitted because it would make the timeline unreadable.",
247
+ sceneId,
248
+ recoverable: true
249
+ };
250
+ }
251
+ function createOmittedForCloserWarning(sceneId) {
252
+ return {
253
+ code: "scene_omitted_for_closer",
254
+ category: "readability",
255
+ message: "Scene was omitted because the final action must remain the last scene.",
256
+ sceneId,
257
+ recoverable: true
258
+ };
259
+ }
260
+ function sceneSlotDuration(scene) {
261
+ return Math.max(0, (scene.timing.endTime ?? 0) - (scene.timing.startTime ?? 0));
262
+ }
263
+ function createSceneQualityWarnings(scene) {
264
+ if (scene.templateId !== "barChart" || !Array.isArray(scene.variables.bars)) return [];
265
+ const values = scene.variables.bars.flatMap((bar) => {
266
+ if (!bar || typeof bar !== "object") return [];
267
+ const value = bar.value;
268
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? [value] : [];
269
+ });
270
+ if (values.length < 2 || Math.max(...values) / Math.min(...values) <= 20) return [];
271
+ return [{
272
+ code: "chart_scale_imbalance",
273
+ category: "readability",
274
+ message: "Chart values span more than 20\xD7; verify that they use comparable units.",
275
+ sceneId: scene.id,
276
+ recoverable: true
277
+ }];
278
+ }
279
+ function createVideo(input, options) {
280
+ validateInput(input);
281
+ if (input.opening?.trim() && options.capabilities?.templates != null && !options.capabilities.templates.includes("notification")) {
282
+ throw new Error("Scene template notification was not negotiated");
283
+ }
284
+ const requestId = options.requestId ?? createId("request");
285
+ const runId = options.runId ?? createId("run");
286
+ const request = createVideoRequest(input, {
287
+ requestId,
288
+ capabilities: options.capabilities
289
+ });
290
+ const initialAudio = input.audio === false ? void 0 : input.audio ? {
291
+ trackId: "soundtrack",
292
+ audioUrl: input.audio.src,
293
+ duration: input.maxDurationSec ?? 30,
294
+ beatDetection: { sensitivity: 0.5 },
295
+ beatMarkers: [],
296
+ volume: 1,
297
+ fadeOutMs: 3e3
298
+ } : options.selectAudio?.(input);
299
+ const closerReserveSec = getCloserReserve(
300
+ options.capabilities?.templates,
301
+ options.getTemplatePacing
302
+ );
303
+ const initial = buildInitialConfig(
304
+ input,
305
+ initialAudio,
306
+ options.snapshotRetention,
307
+ closerReserveSec,
308
+ options.getTemplatePacing
309
+ );
310
+ const initialConfig = initial.config;
311
+ for (let position = 0; position < initialConfig.scenes.length; position += 1) {
312
+ options.validateScene?.(initialConfig.scenes[position], {
313
+ input,
314
+ previousScenes: initialConfig.scenes.slice(0, position)
315
+ });
316
+ }
317
+ const userPrompt = buildVideoUserPrompt(
318
+ input,
319
+ initialConfig.scenes.at(-1)?.timing.endTime ?? 0
320
+ );
321
+ const events = createVideoEventFactory({ runId });
322
+ const controller = new AbortController();
323
+ let abortReason = "Request aborted";
324
+ const forwardAbort = () => {
325
+ abortReason = safeAbortReason(options.signal?.reason);
326
+ controller.abort(options.signal?.reason);
327
+ };
328
+ if (options.signal?.aborted) forwardAbort();
329
+ else options.signal?.addEventListener("abort", forwardAbort, { once: true });
330
+ let resolveResult;
331
+ let rejectResult;
332
+ let settled = false;
333
+ const result = new Promise((resolve, reject) => {
334
+ resolveResult = resolve;
335
+ rejectResult = reject;
336
+ });
337
+ const startedAt = now();
338
+ let acceptedSceneCount = 0;
339
+ let rejectedSceneCount = 0;
340
+ let timeToFirstSceneMs;
341
+ const providerResults = [];
342
+ const lifecycle = {
343
+ registerProviderResult(providerResult) {
344
+ providerResults.push(providerResult);
345
+ }
346
+ };
347
+ const reportedErrors = /* @__PURE__ */ new WeakSet();
348
+ const reportError = (error) => {
349
+ if (reportedErrors.has(error)) return;
350
+ reportedErrors.add(error);
351
+ invokeIsolated(options.onError, error);
352
+ };
353
+ const settleProviderLifecycle = async () => {
354
+ const results = await Promise.all(providerResults);
355
+ const warnings = results.flatMap((result2) => result2.warnings);
356
+ const uniqueWarnings = [...new Map(warnings.map((warning) => [
357
+ `${warning.code}\0${warning.category}\0${warning.message}\0${warning.sceneId ?? ""}`,
358
+ warning
359
+ ])).values()];
360
+ return results.reduce((summary, result2) => ({
361
+ ...summary,
362
+ ...result2,
363
+ warnings: uniqueWarnings
364
+ }), { warnings: uniqueWarnings });
365
+ };
366
+ const eventSource = (async function* () {
367
+ let state = createVideoState();
368
+ const emit = (event) => {
369
+ state = applyVideoEvent(state, event);
370
+ if (event.type === "response.warning") {
371
+ invokeIsolated(options.onWarning, cloneWarning(event.data.warning));
372
+ }
373
+ options.onEvent?.(event);
374
+ return event;
375
+ };
376
+ const finish = (finalState) => {
377
+ if (!settled) {
378
+ settled = true;
379
+ resolveResult(finalState);
380
+ }
381
+ };
382
+ try {
383
+ yield emit(events.create("response.start", {
384
+ requestId,
385
+ format: { orientation: initialConfig.orientation ?? "portrait" },
386
+ style: initialConfig.style,
387
+ meta: initialConfig.meta,
388
+ capabilities: options.capabilities
389
+ }));
390
+ if (initialConfig.audio) yield emit(events.create("audio.set", { audio: initialConfig.audio }));
391
+ for (const warning of initial.warnings) {
392
+ yield emit(events.create("response.warning", { warning }));
393
+ }
394
+ for (let position = 0; position < initialConfig.scenes.length; position += 1) {
395
+ yield emit(events.create("scene.add", {
396
+ scene: initialConfig.scenes[position],
397
+ position,
398
+ revision: 0
399
+ }));
400
+ }
401
+ let planCompleted = false;
402
+ let generatedSceneCount = 0;
403
+ let runtimeDurationLimited = false;
404
+ let askAccepted = false;
405
+ const deferredForCloser = [];
406
+ let plannerReportedLength = false;
407
+ let finishReason = "stop";
408
+ const systemPrompt = options.systemPrompt ?? (await import("./system-prompt-A6UXNTPK.js")).DEFAULT_VIDEO_SYSTEM_PROMPT;
409
+ const context = { request, systemPrompt, userPrompt, initialConfig, signal: controller.signal };
410
+ attachGenerationLifecycleSink(context, lifecycle);
411
+ for await (const untrustedPart of options.generate(context)) {
412
+ let attemptedScene = false;
413
+ try {
414
+ const part = resolveSuppliedMediaPlanPart(parseVideoPlanPart(untrustedPart), input);
415
+ attemptedScene = part.type === "scene.add";
416
+ if (controller.signal.aborted) throw controller.signal.reason ?? new Error(abortReason);
417
+ if (planCompleted && part.type !== "plan.complete") {
418
+ throw new Error("The planner emitted content after plan.complete");
419
+ }
420
+ if (part.type === "scene.add") {
421
+ if (options.capabilities?.templates != null && !options.capabilities.templates.includes(part.scene.templateId)) {
422
+ throw new Error(`Scene template ${part.scene.templateId} was not negotiated`);
423
+ }
424
+ if (askAccepted) {
425
+ runtimeDurationLimited = true;
426
+ rejectedSceneCount += 1;
427
+ yield emit(events.create("response.warning", {
428
+ warning: createOmittedForCloserWarning(part.scene.id)
429
+ }));
430
+ continue;
431
+ }
432
+ options.validateScene?.(part.scene, {
433
+ input,
434
+ previousScenes: state.config?.scenes ?? []
435
+ });
436
+ const templatePacing = options.getTemplatePacing?.(part.scene.templateId);
437
+ const isAskCandidate = templatePacing?.jobs?.includes("ask") === true;
438
+ if (deferredForCloser.length && !isAskCandidate) {
439
+ deferredForCloser.push(part.scene);
440
+ continue;
441
+ }
442
+ const paced = paceScene(part.scene, {
443
+ previousScenes: state.config?.scenes ?? [],
444
+ audio: state.config?.audio,
445
+ maxDurationSec: input.maxDurationSec ?? 30,
446
+ closerReserveSec,
447
+ getTemplatePacing: options.getTemplatePacing
448
+ });
449
+ if (!paced.scene) {
450
+ if (paced.warnings.some(({ code }) => code === "scene_omitted_for_closer")) {
451
+ deferredForCloser.push(part.scene);
452
+ continue;
453
+ }
454
+ for (const warning of paced.warnings) {
455
+ yield emit(events.create("response.warning", { warning }));
456
+ }
457
+ runtimeDurationLimited = true;
458
+ rejectedSceneCount += 1;
459
+ continue;
460
+ }
461
+ const scene = paced.scene;
462
+ const isAsk = isAskCandidate;
463
+ if (isAsk && deferredForCloser.length) {
464
+ runtimeDurationLimited = true;
465
+ for (const deferred of deferredForCloser.splice(0)) {
466
+ rejectedSceneCount += 1;
467
+ yield emit(events.create("response.warning", {
468
+ warning: createOmittedForCloserWarning(deferred.id)
469
+ }));
470
+ }
471
+ }
472
+ for (const warning of paced.warnings) {
473
+ yield emit(events.create("response.warning", { warning }));
474
+ }
475
+ for (const warning of createSceneQualityWarnings(scene)) {
476
+ yield emit(events.create("response.warning", { warning }));
477
+ }
478
+ yield emit(events.create("scene.add", {
479
+ scene,
480
+ position: state.config?.scenes.length ?? 0,
481
+ revision: 0
482
+ }));
483
+ if (isAsk) {
484
+ askAccepted = true;
485
+ }
486
+ generatedSceneCount += 1;
487
+ acceptedSceneCount += 1;
488
+ timeToFirstSceneMs ??= Math.max(0, now() - startedAt);
489
+ } else if (part.type === "scene.patch") {
490
+ const scenes = state.config?.scenes ?? [];
491
+ const sceneIndex = scenes.findIndex((scene) => scene.id === part.sceneId);
492
+ if (sceneIndex < 0) throw new Error(`Scene ${part.sceneId} does not exist`);
493
+ if (part.patch.timing && sceneIndex < scenes.length - 1) {
494
+ yield emit(events.create("response.warning", {
495
+ warning: createPatchReadabilityWarning(part.sceneId)
496
+ }));
497
+ continue;
498
+ }
499
+ const currentScene = scenes[sceneIndex];
500
+ if (part.patch.timing?.startTime != null && Math.abs(part.patch.timing.startTime - (currentScene.timing.startTime ?? 0)) > 1e-6) {
501
+ yield emit(events.create("response.warning", {
502
+ warning: createPatchReadabilityWarning(part.sceneId)
503
+ }));
504
+ continue;
505
+ }
506
+ let timing;
507
+ if (part.patch.timing) {
508
+ const requestedTiming = { ...currentScene.timing, ...part.patch.timing };
509
+ if (part.patch.timing.startTime != null && part.patch.timing.endTime == null) {
510
+ delete requestedTiming.endTime;
511
+ }
512
+ if (part.patch.timing.fixedDuration != null && part.patch.timing.endTime == null) {
513
+ delete requestedTiming.endTime;
514
+ }
515
+ const pacedPatch = paceScene({
516
+ ...currentScene,
517
+ ...part.patch,
518
+ variables: part.patch.variables ? { ...currentScene.variables, ...part.patch.variables } : currentScene.variables,
519
+ timing: requestedTiming
520
+ }, {
521
+ previousScenes: scenes.slice(0, sceneIndex),
522
+ audio: state.config?.audio,
523
+ maxDurationSec: input.maxDurationSec ?? 30,
524
+ closerReserveSec,
525
+ getTemplatePacing: options.getTemplatePacing
526
+ });
527
+ if (!pacedPatch.scene) {
528
+ yield emit(events.create("response.warning", {
529
+ warning: createPatchReadabilityWarning(part.sceneId)
530
+ }));
531
+ continue;
532
+ }
533
+ for (const warning of pacedPatch.warnings) {
534
+ yield emit(events.create("response.warning", { warning }));
535
+ }
536
+ timing = pacedPatch.scene.timing;
537
+ }
538
+ const patch = {
539
+ ...part.patch,
540
+ ...timing ? { timing } : {}
541
+ };
542
+ const nextScene = {
543
+ ...currentScene,
544
+ ...patch,
545
+ variables: patch.variables ? { ...currentScene.variables, ...patch.variables } : currentScene.variables,
546
+ timing: patch.timing ? { ...currentScene.timing, ...patch.timing } : currentScene.timing
547
+ };
548
+ options.validateScene?.(nextScene, { input, previousScenes: scenes });
549
+ if (getReadableSceneDuration(
550
+ nextScene,
551
+ options.getTemplatePacing?.(nextScene.templateId)
552
+ ) > sceneSlotDuration(nextScene) + 1e-6) {
553
+ yield emit(events.create("response.warning", {
554
+ warning: createPatchReadabilityWarning(part.sceneId)
555
+ }));
556
+ continue;
557
+ }
558
+ yield emit(events.create("scene.patch", {
559
+ sceneId: part.sceneId,
560
+ revision: (state.sceneRevisions[part.sceneId] ?? -1) + 1,
561
+ patch
562
+ }));
563
+ } else if (part.type === "asset.patch") {
564
+ const scenes = state.config?.scenes ?? [];
565
+ const scene = scenes.find((item) => item.id === part.sceneId);
566
+ if (!scene) throw new Error(`Scene ${part.sceneId} does not exist`);
567
+ const nextScene = {
568
+ ...scene,
569
+ variables: { ...scene.variables, ...part.variables }
570
+ };
571
+ options.validateScene?.(nextScene, { input, previousScenes: scenes });
572
+ if (getReadableSceneDuration(
573
+ nextScene,
574
+ options.getTemplatePacing?.(nextScene.templateId)
575
+ ) > sceneSlotDuration(nextScene) + 1e-6) {
576
+ yield emit(events.create("response.warning", {
577
+ warning: createPatchReadabilityWarning(part.sceneId)
578
+ }));
579
+ continue;
580
+ }
581
+ yield emit(events.create("asset.patch", {
582
+ sceneId: part.sceneId,
583
+ revision: (state.sceneRevisions[part.sceneId] ?? -1) + 1,
584
+ variables: part.variables
585
+ }));
586
+ } else if (part.type === "plan.error") {
587
+ const internalError = new Error(part.error.message);
588
+ reportError(internalError);
589
+ const terminal = !(part.error.recoverable ?? false);
590
+ const errorEvent = emit(events.create("response.error", {
591
+ error: {
592
+ code: "generation_failed",
593
+ message: "Video response generation failed",
594
+ recoverable: part.error.recoverable ?? false
595
+ },
596
+ terminal,
597
+ ...terminal && state.config ? { snapshot: state.config } : {}
598
+ }));
599
+ if (terminal) {
600
+ finish(state);
601
+ yield errorEvent;
602
+ return;
603
+ }
604
+ yield errorEvent;
605
+ } else if (part.type === "plan.complete") {
606
+ if (planCompleted) throw new Error("The planner emitted plan.complete more than once");
607
+ if (!askAccepted && deferredForCloser.length) {
608
+ for (const deferred of deferredForCloser.splice(0)) {
609
+ const recovered = paceScene(deferred, {
610
+ previousScenes: state.config?.scenes ?? [],
611
+ audio: state.config?.audio,
612
+ maxDurationSec: input.maxDurationSec ?? 30,
613
+ closerReserveSec: 0,
614
+ getTemplatePacing: options.getTemplatePacing
615
+ });
616
+ for (const warning of recovered.warnings) {
617
+ yield emit(events.create("response.warning", { warning }));
618
+ }
619
+ if (!recovered.scene) {
620
+ runtimeDurationLimited = true;
621
+ rejectedSceneCount += 1;
622
+ continue;
623
+ }
624
+ for (const warning of createSceneQualityWarnings(recovered.scene)) {
625
+ yield emit(events.create("response.warning", { warning }));
626
+ }
627
+ yield emit(events.create("scene.add", {
628
+ scene: recovered.scene,
629
+ position: state.config?.scenes.length ?? 0,
630
+ revision: 0
631
+ }));
632
+ generatedSceneCount += 1;
633
+ acceptedSceneCount += 1;
634
+ timeToFirstSceneMs ??= Math.max(0, now() - startedAt);
635
+ }
636
+ }
637
+ planCompleted = true;
638
+ finishReason = runtimeDurationLimited ? "length" : part.finishReason ?? "stop";
639
+ plannerReportedLength = part.finishReason === "length";
640
+ continue;
641
+ }
642
+ } catch (cause) {
643
+ const error = cause instanceof Error ? cause : new Error(String(cause));
644
+ if (attemptedScene || untrustedPart != null && typeof untrustedPart === "object" && untrustedPart.type === "scene.add") rejectedSceneCount += 1;
645
+ reportError(error);
646
+ if ((options.invalidPartBehavior ?? "fail") === "fail") throw error;
647
+ yield emit(events.create("response.error", {
648
+ error: {
649
+ code: "invalid_generated_part",
650
+ message: "Generated content was skipped",
651
+ recoverable: true
652
+ },
653
+ terminal: false
654
+ }));
655
+ }
656
+ }
657
+ if (controller.signal.aborted) {
658
+ throw controller.signal.reason ?? new Error(abortReason);
659
+ }
660
+ const provider = await settleProviderLifecycle();
661
+ for (const warning of provider.warnings) {
662
+ yield emit(events.create("response.warning", { warning }));
663
+ }
664
+ if (!planCompleted) throw new Error("The planner stream ended before plan.complete");
665
+ if (plannerReportedLength && generatedSceneCount === 0) {
666
+ throw new Error("The planner was truncated before adding a generated scene");
667
+ }
668
+ if (!state.config?.scenes.length) throw new Error("The planner completed without adding a scene");
669
+ const snapshot = parseVideo(state.config);
670
+ const completeEvent = emit(events.create("response.complete", {
671
+ finishReason,
672
+ snapshot,
673
+ checksum: checksumVideo(snapshot)
674
+ }));
675
+ finish(state);
676
+ const summary = {
677
+ finishReason,
678
+ ...provider.usage ? { usage: provider.usage } : {},
679
+ ...provider.providerMetadata !== void 0 ? { providerMetadata: provider.providerMetadata } : {},
680
+ ...provider.requestedModelId ? { requestedModelId: provider.requestedModelId } : {},
681
+ ...provider.resolvedModelId ? { resolvedModelId: provider.resolvedModelId } : {},
682
+ ...timeToFirstSceneMs != null ? { timeToFirstSceneMs } : {},
683
+ totalDurationMs: Math.max(0, now() - startedAt),
684
+ acceptedSceneCount,
685
+ rejectedSceneCount,
686
+ videoDurationSec: snapshot.scenes.at(-1)?.timing.endTime ?? 0,
687
+ warnings: state.warnings.map(cloneWarning)
688
+ };
689
+ invokeIsolated(options.onComplete, summary);
690
+ yield completeEvent;
691
+ } catch (cause) {
692
+ if (controller.signal.aborted) {
693
+ const abortEvent = emit(events.create("response.abort", {
694
+ reason: abortReason,
695
+ ...state.config ? { snapshot: state.config } : {}
696
+ }));
697
+ finish(state);
698
+ yield abortEvent;
699
+ return;
700
+ }
701
+ try {
702
+ const provider = await settleProviderLifecycle();
703
+ for (const warning of provider.warnings) {
704
+ if (!state.warnings.some((existing) => existing.code === warning.code && existing.message === warning.message)) {
705
+ yield emit(events.create("response.warning", { warning }));
706
+ }
707
+ }
708
+ const error = cause instanceof Error ? cause : new Error(String(cause));
709
+ reportError(error);
710
+ const emptyResult = error.message === "The planner completed without adding a scene";
711
+ const errorEvent = emit(events.create("response.error", {
712
+ error: {
713
+ code: "generation_failed",
714
+ message: emptyResult ? "No valid scenes were generated; inspect recoverable errors and warnings, then retry with a more capable model." : "Video response generation failed",
715
+ recoverable: false
716
+ },
717
+ terminal: true,
718
+ ...state.config ? { snapshot: state.config } : {}
719
+ }));
720
+ finish(state);
721
+ yield errorEvent;
722
+ } catch (terminalCause) {
723
+ if (!settled) {
724
+ settled = true;
725
+ rejectResult(terminalCause);
726
+ }
727
+ throw terminalCause;
728
+ }
729
+ } finally {
730
+ options.signal?.removeEventListener("abort", forwardAbort);
731
+ }
732
+ })();
733
+ const buffered = [];
734
+ const waiters = /* @__PURE__ */ new Set();
735
+ let streamDone = false;
736
+ let streamError;
737
+ const notify = () => {
738
+ for (const waiter of waiters) waiter();
739
+ waiters.clear();
740
+ };
741
+ void (async () => {
742
+ try {
743
+ for await (const event of eventSource) {
744
+ buffered.push(event);
745
+ notify();
746
+ }
747
+ } catch (cause) {
748
+ streamError = cause;
749
+ if (!settled) {
750
+ settled = true;
751
+ rejectResult(cause);
752
+ }
753
+ } finally {
754
+ streamDone = true;
755
+ notify();
756
+ }
757
+ })();
758
+ const stream = {
759
+ async *[Symbol.asyncIterator]() {
760
+ let index = 0;
761
+ while (true) {
762
+ while (index < buffered.length) yield buffered[index++];
763
+ if (streamDone) {
764
+ if (streamError) throw streamError;
765
+ return;
766
+ }
767
+ await new Promise((resolve) => waiters.add(resolve));
768
+ }
769
+ }
770
+ };
771
+ return {
772
+ request,
773
+ initialConfig,
774
+ initialStyle: initialConfig.style,
775
+ stream,
776
+ result,
777
+ abort(reason = "user cancelled") {
778
+ abortReason = safePublicDiagnostic(reason, "User cancelled");
779
+ controller.abort(new Error(reason));
780
+ }
781
+ };
782
+ }
783
+
784
+ export {
785
+ createVideo
786
+ };