@vanillaskyai/video 0.10.23 → 0.11.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 (127) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/PUBLIC-API.md +45 -438
  3. package/README.md +53 -97
  4. package/dist/builtin-metadata-OT6V7TB4.js +8 -0
  5. package/dist/{chapter-title-2JVDU62E.js → chapter-title-2RCXX7SN.js} +1 -2
  6. package/dist/{chunk-PDQFQIQW.js → chunk-3EQ6PVWL.js} +1 -1
  7. package/dist/chunk-4G4JBMCM.js +37 -0
  8. package/dist/{chunk-7AA2JWHZ.js → chunk-5DOQTIMD.js} +6 -6
  9. package/dist/chunk-666HGTVZ.js +41 -0
  10. package/dist/{chunk-7M56IUUX.js → chunk-6Z3ID54H.js} +0 -18
  11. package/dist/{chunk-5BSJLT6H.js → chunk-E7FHBLAC.js} +16 -29
  12. package/dist/chunk-HIQM4J3U.js +30 -0
  13. package/dist/chunk-ISWQ6T5X.js +36 -0
  14. package/dist/{chunk-224QNWRA.js → chunk-K5J7ESRO.js} +1 -10
  15. package/dist/chunk-KMVRBUL5.js +50 -0
  16. package/dist/chunk-NM4CXXZY.js +25 -0
  17. package/dist/{chunk-44WND2VP.js → chunk-V2CP7PVY.js} +19 -63
  18. package/dist/{chunk-3O7OMMMF.js → chunk-WZESNEPT.js} +0 -1
  19. package/dist/{chunk-RXTN2CW6.js → chunk-Z3DLSLAJ.js} +1 -1
  20. package/dist/cinema-media-YZ2UANJG.js +365 -0
  21. package/dist/cli.js +154 -1748
  22. package/dist/{compose-video-BH5K6XWT.js → compose-video-H2YUQDUY.js} +4 -4
  23. package/dist/{events-B4YCc4vc.d.ts → events-BZAfl0Dm.d.ts} +1 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +4 -6
  26. package/dist/preload-media-23JXUFCF.js +77 -0
  27. package/dist/react.d.ts +39 -15
  28. package/dist/react.js +1350 -739
  29. package/dist/scene-validation-SGLLLFY5.js +9 -0
  30. package/dist/server.d.ts +67 -117
  31. package/dist/server.js +262 -614
  32. package/dist/test.d.ts +2 -2
  33. package/dist/test.js +21 -21
  34. package/dist/{text-stream-LD364KBI.js → text-stream-SOVLYR2L.js} +2 -2
  35. package/dist/{types-BqB8zC9u.d.ts → types-CG-kPI81.d.ts} +3 -1
  36. package/dist/{types-DdZw4GRQ.d.ts → types-ht-Zw3Wv.d.ts} +1 -1
  37. package/docs/agent-integration.md +19 -64
  38. package/docs/architecture.md +80 -106
  39. package/docs/customization.md +49 -66
  40. package/docs/development.md +18 -17
  41. package/docs/errors.md +4 -4
  42. package/docs/getting-started.md +74 -93
  43. package/docs/media-and-audio.md +9 -7
  44. package/docs/performance.md +11 -6
  45. package/docs/persistence.md +3 -7
  46. package/docs/production.md +4 -15
  47. package/docs/prompt-and-input.md +1 -2
  48. package/docs/provider-integration.md +134 -181
  49. package/docs/reference/protocol.md +5 -14
  50. package/docs/reference/provider-adapters.md +21 -13
  51. package/docs/security.md +1 -14
  52. package/docs/testing.md +56 -123
  53. package/package.json +4 -25
  54. package/starters/video-chat/.env.example +12 -4
  55. package/starters/video-chat/.env.native.example +13 -0
  56. package/starters/video-chat/README.md +72 -11
  57. package/starters/video-chat/package.json +1 -1
  58. package/starters/video-chat/providers/text-native.ts +94 -0
  59. package/starters/video-chat/providers/text.ts +19 -0
  60. package/starters/video-chat/providers/transcription.ts +32 -0
  61. package/starters/video-chat/providers/video-custom.ts +45 -0
  62. package/starters/video-chat/providers/video-delivery.ts +57 -0
  63. package/starters/video-chat/providers/video-google.ts +48 -0
  64. package/starters/video-chat/providers/video-job.ts +103 -0
  65. package/starters/video-chat/providers/video-runway.ts +45 -0
  66. package/starters/video-chat/providers/video.ts +39 -63
  67. package/starters/video-chat/server.ts +3 -29
  68. package/starters/video-chat/vite.config.ts +2 -8
  69. package/styles/video-chat.css +8 -105
  70. package/dist/builtin-server-W4PLJUZ5.js +0 -8
  71. package/dist/catalog-types-WTbLP6Jh.d.ts +0 -78
  72. package/dist/check-runtime.d.ts +0 -15
  73. package/dist/check-runtime.js +0 -96
  74. package/dist/chunk-2E6T633S.js +0 -27
  75. package/dist/chunk-4YM2M62S.js +0 -13
  76. package/dist/chunk-4ZJLPHBV.js +0 -684
  77. package/dist/chunk-5JBMYQP6.js +0 -156
  78. package/dist/chunk-73NTSFFI.js +0 -81
  79. package/dist/chunk-EGVQODKU.js +0 -83
  80. package/dist/chunk-HFVNAPHZ.js +0 -35
  81. package/dist/chunk-IIN5M5HW.js +0 -697
  82. package/dist/chunk-IQMYK5DX.js +0 -133
  83. package/dist/chunk-IR44XKBI.js +0 -46
  84. package/dist/chunk-JKVOBTRO.js +0 -145
  85. package/dist/chunk-LVM5Q2DL.js +0 -68
  86. package/dist/chunk-M4QTEJTK.js +0 -709
  87. package/dist/chunk-QSBDB4J2.js +0 -16
  88. package/dist/chunk-R3XAOMKP.js +0 -29
  89. package/dist/chunk-SPVTJH3F.js +0 -24
  90. package/dist/chunk-YAHT3LST.js +0 -663
  91. package/dist/chunk-ZD2VTUYR.js +0 -28
  92. package/dist/cinema-media-HYCDG65Z.js +0 -11
  93. package/dist/comparison-XXAP4S4J.js +0 -36
  94. package/dist/editorial-timeline-VUQC6KPA.js +0 -42
  95. package/dist/key-figure-4TJK7HLT.js +0 -29
  96. package/dist/kit-DrRpdn0p.d.ts +0 -79
  97. package/dist/mobile-message-FIHAO6XC.js +0 -49
  98. package/dist/preload-media-LJXWKTWG.js +0 -57
  99. package/dist/quote-GRPIYKQJ.js +0 -32
  100. package/dist/system-prompt-AG26KJAA.js +0 -12
  101. package/dist/template-catalog.d.ts +0 -400
  102. package/dist/template-catalog.js +0 -6
  103. package/dist/templates.d.ts +0 -25
  104. package/dist/templates.js +0 -102
  105. package/dist/validate-4OUD2CLX.js +0 -10
  106. package/docs/concepts.md +0 -107
  107. package/docs/custom-templates.md +0 -346
  108. package/docs/immersive-interface.md +0 -86
  109. package/docs/motion-and-effects.md +0 -109
  110. package/docs/reference/design-system.html +0 -125
  111. package/docs/responsive-orientation.md +0 -37
  112. package/docs/streaming-protocol.md +0 -15
  113. package/examples/custom-template/README.md +0 -19
  114. package/examples/custom-template/minimal-text.tsx +0 -82
  115. package/examples/custom-template/structured-data.tsx +0 -104
  116. package/registry/items/backgrounds.json +0 -70
  117. package/registry/items/chapterTitle.json +0 -80
  118. package/registry/items/cinemaMedia.json +0 -136
  119. package/registry/items/comparison.json +0 -168
  120. package/registry/items/editorialTimeline.json +0 -172
  121. package/registry/items/keyFigure.json +0 -162
  122. package/registry/items/mobileMessage.json +0 -153
  123. package/registry/items/motion.json +0 -45
  124. package/registry/items/quote.json +0 -161
  125. package/registry/items/template-context.json +0 -31
  126. package/registry/items/theme.json +0 -47
  127. package/registry/items/typography.json +0 -47
package/README.md CHANGED
@@ -1,20 +1,20 @@
1
- # Give your AI a voice and a face
1
+ # Video answers for your AI chat
2
2
 
3
3
  [![CI](https://github.com/VanillaSkyAi/video/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/VanillaSkyAi/video/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/@vanillaskyai/video.svg)](https://www.npmjs.com/package/@vanillaskyai/video)
5
- [![runtime dependencies](https://img.shields.io/badge/runtime%20dependencies-0-brightgreen)](package.json)
6
5
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
7
6
 
8
- **VanillaSky is the open-source voice-and-video chat layer.** Add a polished,
9
- general-purpose AI conversation that speaks and starts playing visual answers
10
- while they are still being composed.
7
+ VanillaSky is an open-source React SDK for spoken video conversations. A short
8
+ chapter introduction starts the answer while footage prepares; narrated clips
9
+ then play in order as the response is generated.
11
10
 
12
- > **Status: Beta.** VanillaSky is pre-1.0 and its public API may change as we
13
- > test it in real applications. Pin an exact version before production use.
11
+ Your application owns models, keys, authentication, storage, and spending.
12
+ VanillaSky owns the chat interface, shot-planning prompts, streaming, validation,
13
+ voice timing, and playback. The core has no runtime dependencies.
14
14
 
15
- Your application owns the providers, keys, authentication, persistence,
16
- branding, and product copy. VanillaSky owns the chat flow, planning prompts,
17
- trusted templates, validation, streaming, voice timing, and player.
15
+ Pre-1.0 beta, distributed through npm with [best-effort support](SUPPORT.md).
16
+ Pin an exact version and use its matching docs; an unreleased checkout is not
17
+ the published package. Review [breaking changes](CHANGELOG.md) before upgrading.
18
18
 
19
19
  ## Start on localhost
20
20
 
@@ -22,7 +22,7 @@ trusted templates, validation, streaming, voice timing, and player.
22
22
  npx @vanillaskyai/video init
23
23
  ```
24
24
 
25
- Init installs the baseline dependencies and runs doctor automatically. Add
25
+ The default starter uses the optional Vercel AI SDK text adapter. Add
26
26
  `ANTHROPIC_API_KEY` to the generated, ignored `.env.local`, then run:
27
27
 
28
28
  ```bash
@@ -30,42 +30,16 @@ npx vanillasky doctor
30
30
  npm run dev
31
31
  ```
32
32
 
33
- Open the reported localhost URL. One text key gives you the chat, an immediate introduction, and browser voice.
34
- Add the video adapter for AI footage, or a Pexels key for the lower-cost stock mode.
35
- Without footage, authored chapter scenes carry the narration and subtitles. If installation is interrupted, rerun the init command.
33
+ Prefer native callbacks? Use `init --native` in a new directory and configure
34
+ `GEMINI_API_KEY`; that starter needs neither `ai` nor `@ai-sdk/anthropic`.
35
+ Both are application examples, not SDK requirements.
36
36
 
37
- Optional upgrades keep the same client:
38
-
39
- | Add | Setup command | Server-only key |
40
- | --- | --- | --- |
41
- | Generated speech | `npx vanillasky providers add speech` | `XAI_API_KEY` |
42
- | Generated video and voice transcription | `npx vanillasky providers add video` | `FAL_KEY` |
43
- | Pexels video search | No install needed | `PEXELS_API_KEY` |
44
-
45
- Run the selected command, add its key to `.env.local`, and restart the server.
46
- Only selected provider packages are installed. `npx vanillasky doctor` reports
47
- readiness by key name and never prints values. Provider SDKs remain application
48
- dependencies, outside the core package.
49
-
50
- For coding agents:
51
-
52
- ```bash
53
- npx skills add VanillaSkyAi/video@vanillasky
54
- ```
55
-
56
- Then prompt: `Use $vanillasky to set up and verify a general-purpose video chat in this project.`
57
-
58
- ## What init creates
59
-
60
- The generated application is a thin, editable shell:
61
-
62
- - `src/main.tsx` mounts the complete SDK-owned `<VideoChat />` interface;
63
- - `server.ts` connects app-owned text, speech, transcription, stock, and video
64
- providers through one `createVideoChatHandler`;
65
- - `vite.config.ts` serves the UI and the single `/api/video-chat` endpoint;
66
- - `.env.local` holds server-only keys and is ignored by Git.
67
-
68
- The UI stays this small:
37
+ One text key gives you chat, chapter introductions, subtitles, and browser voice.
38
+ Add stock or generated footage through app-owned adapters. fal, Google, and
39
+ Runway references are included; any vendor can implement the same callback.
40
+ Direct video generation also requires your storage/delivery callback.
41
+ See [Getting started](docs/getting-started.md) for the complete setup and
42
+ [Provider integration](docs/provider-integration.md) for the adapter boundary.
69
43
 
70
44
  ```tsx
71
45
  import { VideoChat } from "@vanillaskyai/video/react";
@@ -76,62 +50,44 @@ export function App() {
76
50
  }
77
51
  ```
78
52
 
79
- The default interface puts video behind a floating conversation field, with
80
- on-video subtitles and contextual controls. See the [immersive interface guide](docs/immersive-interface.md)
81
- for behavior and [customization](docs/customization.md) for application branding.
82
-
83
- Use `useVideoChat` when you want a custom interface while keeping the SDK-owned
84
- conversation and playback lifecycle. Edit the generated server when you want a
85
- different provider. The [provider guide](docs/provider-integration.md) explains
86
- both boundaries.
87
-
88
- ## An immediate intro, then moving footage
89
-
90
- The default chat uses a template introduction while its first shot prepares,
91
- then footage with narration and subtitles. Choose AI video or Pexels in Settings.
92
- AI mode never substitutes stock, and Pexels mode never calls the AI-video provider.
93
- Missing or late footage becomes a useful chapter scene. The planner adapts its
94
- visible actions and spoken beats to explanations, stories, comedy, imagination,
95
- and practical requests. It does not choose body templates.
53
+ Use `useVideoChat` to build your own interface, and `parseVideo` with
54
+ `VideoPlayer` to replay completed responses. Changing a provider does not
55
+ require changing the React client.
96
56
 
97
- Speech and footage prepare together. Silent clips loop through the remaining
98
- narration when necessary, and every answer preserves its intended ending.
57
+ Already have an assistant? The optional server `resolveAnswer` callback turns
58
+ its completed answer into the source for the video, while your application
59
+ keeps retrieval, tools and answer policy. See [existing-assistant integration](docs/provider-integration.md#use-an-existing-assistant).
99
60
 
100
- Packaged templates remain available for custom compositions and source ownership.
101
-
102
- Copy template source only when you want to own and edit it:
103
-
104
- ```bash
105
- npm install --save-dev tsx
106
- npx vanillasky templates add chapterTitle
107
- ```
61
+ ## How it works
108
62
 
109
- That compiler is needed only for source-owned templates. See
110
- [Custom templates](docs/custom-templates.md).
63
+ The model streams an answer brief and shot directions, not component code.
64
+ Footage generation overlaps browser-owned speech preparation. The server
65
+ announces prepared media early and streams validated scenes in order. AI-video mode never silently
66
+ substitutes stock; stock mode never spends on generated video. A failed or late
67
+ clip becomes a narrated chapter. Narration targets a 0.8-second visual tail;
68
+ one short rewrite may fit an oversized beat before generation. If it still
69
+ does not fit, the complete original narration plays over a chapter. Footage
70
+ plays once at native speed; unexpected overruns recover to a chapter without
71
+ cutting off the sentence.
111
72
 
112
- ## Go deeper
73
+ This is progressive **scene** delivery, not real-time frames from every vendor.
74
+ Some generation APIs take minutes; preloading cannot remove that latency.
75
+ Evaluate the model, voice and delivery path you actually deploy.
113
76
 
114
- Completed chat responses are deterministic JSON and can be stored and replayed.
115
- MP4/WebM export remains application-owned.
77
+ The supported visual vocabulary is deliberately small: footage and chapter
78
+ introductions/fallbacks. There is no template-authoring CLI, renderer plugin
79
+ system, or hosted service dependency.
116
80
 
117
81
  ## Documentation
118
82
 
119
- | Goal | Guide |
120
- | --- | --- |
121
- | Run the complete chat | [Getting started](docs/getting-started.md) |
122
- | Set it up with a coding agent | [Agent integration guide](docs/agent-integration.md) |
123
- | Change or add providers | [Provider integration](docs/provider-integration.md) |
124
- | Customize the interface | [Customization](docs/customization.md) |
125
- | Understand prompts and grounding | [Prompt and input](docs/prompt-and-input.md) |
126
- | Add media or voice | [Media and voice](docs/media-and-audio.md) |
127
- | Persist and replay results | [Performance measurements](docs/performance.md) · [Persistence and replay](docs/persistence.md) |
128
- | Test routes and streams | [Test integrations](docs/testing.md) |
129
- | Iterate on SDK chat | [Development](docs/development.md): `dev:chat`, `check:chat`, `verify:release` |
130
- | Deploy securely | [Production](docs/production.md) · [Security](docs/security.md) |
131
- | Inspect the API contract | [Public API](PUBLIC-API.md) · [Protocol](docs/reference/protocol.md) |
132
-
133
- Try a keyless template response in the
134
- [playground](https://vanillasky.ai/playground/), or visit
135
- [vanillasky.ai](https://vanillasky.ai/).
136
-
137
- Apache-2.0
83
+ - [Agent integration](docs/agent-integration.md) and the optional
84
+ [integration skill](https://github.com/VanillaSkyAi/video/blob/main/skills/vanillasky/SKILL.md)
85
+ - [Provider integration](docs/provider-integration.md), [adapter reference](docs/reference/provider-adapters.md), and [media and voice](docs/media-and-audio.md)
86
+ - [Customization](docs/customization.md) and [prompt guidance](docs/prompt-and-input.md)
87
+ - [Persistence and replay](docs/persistence.md), [protocol](docs/reference/protocol.md), and [testing](docs/testing.md)
88
+ - [Performance](docs/performance.md), [production](docs/production.md), [security](docs/security.md), and [errors](docs/errors.md)
89
+ - [Architecture](docs/architecture.md), [development](docs/development.md), and [contributing](https://github.com/VanillaSkyAi/video/blob/main/CONTRIBUTING.md)
90
+
91
+ Node 22+; React 18 or 19. The four code entry points are the root package,
92
+ `/server`, `/react`, and `/test`, plus the scoped `/video-chat.css` stylesheet.
93
+ See the [public API contract](PUBLIC-API.md).
@@ -0,0 +1,8 @@
1
+ import {
2
+ SCENE_DEFINITIONS,
3
+ getBuiltinSceneDefinition
4
+ } from "./chunk-NM4CXXZY.js";
5
+ export {
6
+ SCENE_DEFINITIONS,
7
+ getBuiltinSceneDefinition
8
+ };
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  TitleSceneTemplate
3
- } from "./chunk-7AA2JWHZ.js";
4
- import "./chunk-4YM2M62S.js";
3
+ } from "./chunk-5DOQTIMD.js";
5
4
  export {
6
5
  TitleSceneTemplate
7
6
  };
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-E7CL7UPB.js";
7
7
  import {
8
8
  parseVideoPlanPart
9
- } from "./chunk-7M56IUUX.js";
9
+ } from "./chunk-6Z3ID54H.js";
10
10
 
11
11
  // src/server/model/truncated-json.ts
12
12
  function isConfidentlyTruncatedJson(source) {
@@ -0,0 +1,37 @@
1
+ // src/video-chat/deadline.ts
2
+ function withDeadline(operation, timeoutMs, parent) {
3
+ return new Promise((resolve, reject) => {
4
+ const controller = new AbortController();
5
+ let settled = false;
6
+ const timer = setTimeout(() => finish({ error: new DOMException("Optional work exceeded its deadline", "TimeoutError") }), timeoutMs);
7
+ const finish = (result) => {
8
+ if (settled) return;
9
+ settled = true;
10
+ clearTimeout(timer);
11
+ parent?.removeEventListener("abort", cancel);
12
+ if ("error" in result) {
13
+ reject(result.error);
14
+ controller.abort(result.error);
15
+ } else {
16
+ resolve(result.value);
17
+ controller.abort();
18
+ }
19
+ };
20
+ const cancel = () => finish({ error: parent?.reason ?? new DOMException("Cancelled", "AbortError") });
21
+ if (parent?.aborted) {
22
+ cancel();
23
+ return;
24
+ }
25
+ parent?.addEventListener("abort", cancel, { once: true });
26
+ try {
27
+ const result = operation(controller.signal);
28
+ Promise.resolve(result).then((value) => finish({ value }), (error) => finish({ error }));
29
+ } catch (error) {
30
+ finish({ error });
31
+ }
32
+ });
33
+ }
34
+
35
+ export {
36
+ withDeadline
37
+ };
@@ -1,15 +1,15 @@
1
- import {
2
- editorialFont,
3
- fade
4
- } from "./chunk-4YM2M62S.js";
5
-
6
1
  // src/visual-system/scene-templates/chapter-title.tsx
7
2
  import { jsx } from "react/jsx-runtime";
3
+ var editorialFont = '-apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif';
4
+ function ease(value) {
5
+ const t = Math.max(0, Math.min(1, value));
6
+ return t * t * (3 - 2 * t);
7
+ }
8
8
  function TitleScene({ variables, width, height, progress, motionProgress }) {
9
9
  const title = String(variables.title ?? "A different perspective");
10
10
  const unit = Math.min(width, height);
11
11
  const presentation = motionProgress ?? progress;
12
- const opacity = fade(presentation / 0.22) * (1 - fade((presentation - 0.76) / 0.24));
12
+ const opacity = ease(presentation / 0.22) * (1 - ease((presentation - 0.76) / 0.24));
13
13
  return /* @__PURE__ */ jsx("div", { "data-template": "title", "data-title-treatment": "quiet-fade", style: { position: "absolute", inset: 0, background: "#000", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx("div", { "data-title-composition": "centered", style: { width: "76%", textAlign: "center", opacity, fontFamily: editorialFont, fontWeight: 500, fontSize: unit * 0.068, lineHeight: 1.18, letterSpacing: "-.025em", textWrap: "balance", overflowWrap: "anywhere" }, children: title }) });
14
14
  }
15
15
  var TitleSceneTemplate = TitleScene;
@@ -0,0 +1,41 @@
1
+ // src/visual-system/scene-templates/external-video-backdrop.tsx
2
+ import React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var DEFAULT = { mode: false, audioMuted: true, audioVolume: 1 };
5
+ var sharedContext = globalThis;
6
+ var BackdropContext = sharedContext.__vanillaskyVideoBackdropContext ??= React.createContext(DEFAULT);
7
+ function ExternalVideoBackdropProvider({
8
+ mode,
9
+ audioMuted = true,
10
+ audioVolume = 1,
11
+ preparingNarration = false,
12
+ onMediaError,
13
+ children
14
+ }) {
15
+ const value = React.useMemo(
16
+ () => ({ mode, audioMuted, audioVolume, preparingNarration, onMediaError }),
17
+ [mode, audioMuted, audioVolume, preparingNarration, onMediaError]
18
+ );
19
+ return /* @__PURE__ */ jsx(BackdropContext.Provider, { value, children });
20
+ }
21
+ function useExternalVideoBackdrop() {
22
+ return React.useContext(BackdropContext).mode;
23
+ }
24
+ function useMediaAudio() {
25
+ const { audioMuted, audioVolume } = React.useContext(BackdropContext);
26
+ return { muted: audioMuted, volume: audioVolume };
27
+ }
28
+ function useNarrationPreroll() {
29
+ return React.useContext(BackdropContext).preparingNarration === true;
30
+ }
31
+ function useMediaFailure() {
32
+ return React.useContext(BackdropContext).onMediaError;
33
+ }
34
+
35
+ export {
36
+ ExternalVideoBackdropProvider,
37
+ useExternalVideoBackdrop,
38
+ useMediaAudio,
39
+ useNarrationPreroll,
40
+ useMediaFailure
41
+ };
@@ -33,22 +33,6 @@ function safePublicDiagnostic(value, fallback) {
33
33
  if (redacted.length <= MAX_PUBLIC_DIAGNOSTIC_LENGTH) return redacted;
34
34
  return `${redacted.slice(0, MAX_PUBLIC_DIAGNOSTIC_LENGTH - 1).trimEnd()}\u2026`;
35
35
  }
36
- function createClippedVariableWarning(templateId, fields) {
37
- return {
38
- code: "scene_variable_clipped",
39
- category: VIDEO_WARNING_CATEGORIES.scene_variable_clipped,
40
- message: `Trimmed ${fields.join(", ")} on ${templateId} to the length the template declares.`,
41
- recoverable: true
42
- };
43
- }
44
- function createMediaBudgetWarning(limit) {
45
- return {
46
- code: "media_budget_reached",
47
- category: VIDEO_WARNING_CATEGORIES.media_budget_reached,
48
- message: `Resolved media for ${limit} scene${limit === 1 ? "" : "s"}; later scenes keep their copy on the brand gradient.`,
49
- recoverable: true
50
- };
51
- }
52
36
 
53
37
  // src/protocol/json-validation.ts
54
38
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
@@ -361,8 +345,6 @@ function parseVideoPlanPart(value) {
361
345
 
362
346
  export {
363
347
  safePublicDiagnostic,
364
- createClippedVariableWarning,
365
- createMediaBudgetWarning,
366
348
  record,
367
349
  allowedKeys,
368
350
  string,
@@ -1,16 +1,21 @@
1
- import {
2
- attachGenerationLifecycleSink
3
- } from "./chunk-E7CL7UPB.js";
4
1
  import {
5
2
  applyVideoEvent,
6
3
  checksumVideo,
7
4
  createVideoEventFactory,
8
5
  createVideoState
9
- } from "./chunk-RXTN2CW6.js";
6
+ } from "./chunk-Z3DLSLAJ.js";
7
+ import {
8
+ getCloserReserve,
9
+ getReadableSceneDuration,
10
+ paceScene
11
+ } from "./chunk-WZESNEPT.js";
12
+ import {
13
+ attachGenerationLifecycleSink
14
+ } from "./chunk-E7CL7UPB.js";
10
15
  import {
11
16
  parseVideoPlanPart,
12
17
  safePublicDiagnostic
13
- } from "./chunk-7M56IUUX.js";
18
+ } from "./chunk-6Z3ID54H.js";
14
19
  import {
15
20
  MAX_RETAINED_INSTRUCTIONS_LENGTH,
16
21
  MAX_RETAINED_MEDIA_URLS,
@@ -22,11 +27,6 @@ import {
22
27
  VIDEO_SCHEMA_VERSION,
23
28
  createVideoRequest
24
29
  } from "./chunk-7NZOMTAL.js";
25
- import {
26
- getCloserReserve,
27
- getReadableSceneDuration,
28
- paceScene
29
- } from "./chunk-3O7OMMMF.js";
30
30
 
31
31
  // src/replayable-stream.ts
32
32
  function createReplayableStream(source, onError) {
@@ -108,23 +108,11 @@ function buildVideoUserPrompt(input, openingDurationSec = 0) {
108
108
  "Compose a video response from the structured customer input below.",
109
109
  `Knowledge mode: ${input.knowledgeMode ?? "input-only"}.`,
110
110
  `Maximum duration: ${input.maxDurationSec ?? 30} seconds, including the supplied opening.`,
111
- input.opening === false ? "The host owns the opening wait. Add the first grounded scene as soon as it is complete; it may use host-resolved media." : typeof input.opening === "string" && 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.",
112
- ...input.opening === false ? [] : [
113
- "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.",
114
- "Add that scene before resolving any stock or supplied asset. Resolve media as part of each complete later body scene."
115
- ],
116
- "Use only claims supported by the factual basis permitted by the trusted system prompt.",
117
- "Develop the requested answer within the duration: preserve its mechanism, essential actions, story resolution or punchline. A short prompt can need several distinct visual beats; input length is not the answer length.",
118
- "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.",
119
- "When selecting claims from the raw input, preserve their exact wording and numbers. Preserve qualifiers, units, denominators, ranges, and comparison direction; for example, do not shorten 4.8 out of 5 to 4.8.",
120
- "Choose the scene count from the requested intent, visual progression and duration budget. A joke can be brief; a practical sequence or imaginative story may need more development. Do not stop while an essential step or payoff is missing.",
121
- "If the creative instructions explicitly require one separate scene per named item, release, section, or list entry, do not merge, group, or omit those required items. Keep related required scenes adjacent in a coherent progression while preserving each item as its own scene.",
122
- "Before emitting, verify that the explicitly requested structure can fit readably within the maximum duration. If it cannot, preserve readability and the requested separation for the scenes that fit, then finish with plan.complete using finishReason length rather than silently changing the structure.",
123
- "Before plan.complete, check that the answer fulfills the actual request. Do not mistake a hook and a single supporting statement for a complete explanation, or explain a joke after its punchline.",
124
- "Emit exactly one final scene.add with placement closer. Land the requested ending without repeating the opening or implying another scene follows.",
125
- "Reuse the same suitable template when it serves successive beats. Vary the subject action, shot scale, perspective or purposeful detail, not the template merely for variety.",
126
- "Never add filler to satisfy a count or diversity target.",
127
- input.suppliedMedia?.length ? input.opening === false ? "Select zero or more relevant opaque supplied-media references for visible scenes. Never invent or transform a reference. Only emit mediaKeyword when the trusted system catalog explicitly exposes it. Never invent mediaUrl or mediaPoster." : "Select zero or more relevant opaque supplied-media references for visible later scenes. Never invent or transform a reference. Only emit mediaKeyword when the trusted system catalog explicitly exposes it, and only on later scenes. Never invent mediaUrl or mediaPoster." : input.opening === false ? "No supplied media URL is available. Use asset-free templates unless the trusted system catalog explicitly permits host-resolved media intent, which may begin on the first scene. Only emit mediaKeyword when the trusted system catalog explicitly exposes it. Never invent mediaUrl or mediaPoster." : "No supplied media URL is available. Use asset-free templates unless the trusted system catalog explicitly permits host-resolved media intent. Only emit mediaKeyword when the trusted system catalog explicitly exposes it, and only on later scenes. Never invent mediaUrl or mediaPoster.",
111
+ input.opening === false ? "The host owns the opening wait. Add the first grounded scene as soon as it is complete." : `The host has already added the opening scene, which consumes ${openingDurationSec} seconds. Do not repeat it.`,
112
+ "Use cinemaMedia for resolved footage and chapterTitle for a chapter opening or recovery.",
113
+ "Use only claims supported by the factual basis permitted by the trusted system prompt. Preserve quantities, qualifications and the answer's conclusion.",
114
+ "Use only the supplied opaque media references or media resolved by the host. Never invent media URLs.",
115
+ "End with one closer scene and plan.complete; do not add filler.",
128
116
  "",
129
117
  "RAW INPUT",
130
118
  input.input.trim(),
@@ -691,7 +679,7 @@ function createVideo(rawInput, options) {
691
679
  position
692
680
  }));
693
681
  }
694
- const systemPrompt = options.systemPrompt ?? (await import("./system-prompt-AG26KJAA.js")).createVideoSystemPrompt(input.knowledgeMode);
682
+ const systemPrompt = options.systemPrompt ?? "";
695
683
  const context = { request, systemPrompt, userPrompt, initialConfig, signal: controller.signal };
696
684
  attachGenerationLifecycleSink(context, lifecycle);
697
685
  for await (const untrustedPart of options.generate(context)) {
@@ -864,6 +852,5 @@ function createVideo(rawInput, options) {
864
852
  }
865
853
 
866
854
  export {
867
- invokeIsolated,
868
855
  createVideo
869
856
  };
@@ -0,0 +1,30 @@
1
+ import {
2
+ sanitizeVideoChatMedia
3
+ } from "./chunk-ISWQ6T5X.js";
4
+
5
+ // src/server/scene-validation.ts
6
+ function validateBuiltinScene(scene) {
7
+ const variables = scene.variables;
8
+ const boundedText = (value, maximum) => typeof value === "string" && Boolean(value.trim()) && [...value].length <= maximum;
9
+ if (scene.templateId === "chapterTitle") {
10
+ if (!boundedText(variables.title, 65)) throw new Error("Chapter requires a title of 1\u201365 characters");
11
+ if (Object.keys(variables).some((key) => key !== "title")) throw new Error("Unsupported chapter variable");
12
+ return;
13
+ }
14
+ if (scene.templateId !== "cinemaMedia") throw new Error("Unsupported scene kind");
15
+ if (!sanitizeVideoChatMedia({
16
+ url: variables.mediaUrl,
17
+ type: variables.mediaType === "photo" ? "image" : variables.mediaType,
18
+ ...variables.mediaPoster ? { posterUrl: variables.mediaPoster } : {}
19
+ })) throw new Error("Media scene requires a safe media URL and type");
20
+ if (variables.fallbackText !== void 0 && !boundedText(variables.fallbackText, 65)) {
21
+ throw new Error("Media recovery title must contain 1\u201365 characters");
22
+ }
23
+ if (variables.mediaDurationSec !== void 0 && (typeof variables.mediaDurationSec !== "number" || !Number.isFinite(variables.mediaDurationSec) || variables.mediaDurationSec <= 0 || variables.mediaDurationSec > 3600)) throw new Error("Invalid media duration");
24
+ const allowed = /* @__PURE__ */ new Set(["mediaUrl", "mediaType", "mediaPoster", "fallbackText", "mediaDurationSec"]);
25
+ if (Object.keys(variables).some((key) => !allowed.has(key))) throw new Error("Unsupported media variable");
26
+ }
27
+
28
+ export {
29
+ validateBuiltinScene
30
+ };
@@ -0,0 +1,36 @@
1
+ import {
2
+ MAX_RETAINED_MEDIA_URL_LENGTH
3
+ } from "./chunk-AUPC6MDK.js";
4
+
5
+ // src/video-chat/media.ts
6
+ function sanitizeVideoChatMedia(value) {
7
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
8
+ const media = value;
9
+ if (media.type !== "image" && media.type !== "video") return null;
10
+ if (typeof media.url !== "string") return null;
11
+ const url = media.url.trim();
12
+ if (!safeMediaUrl(url)) return null;
13
+ if (media.posterUrl != null && typeof media.posterUrl !== "string") return null;
14
+ const posterUrl = typeof media.posterUrl === "string" ? media.posterUrl.trim() : "";
15
+ if (posterUrl && !safeMediaUrl(posterUrl)) return null;
16
+ if (media.durationSec !== void 0 && (typeof media.durationSec !== "number" || !Number.isFinite(media.durationSec) || media.durationSec <= 0 || media.durationSec > 3600)) return null;
17
+ return {
18
+ url,
19
+ type: media.type,
20
+ ...posterUrl ? { posterUrl } : {},
21
+ ...typeof media.durationSec === "number" ? { durationSec: media.durationSec } : {}
22
+ };
23
+ }
24
+ function safeMediaUrl(value) {
25
+ if (!value || value.length > MAX_RETAINED_MEDIA_URL_LENGTH) return false;
26
+ try {
27
+ const protocol = new URL(value).protocol;
28
+ return protocol === "https:" || protocol === "http:";
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ export {
35
+ sanitizeVideoChatMedia
36
+ };
@@ -9,22 +9,13 @@ function isVideoUrl(url) {
9
9
  return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
10
10
  }
11
11
  }
12
- function limitsConcurrentVideoDecoders() {
13
- if (typeof navigator === "undefined") return false;
14
- return /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
15
- }
16
12
  function resolveMediaType(mediaType, mediaUrl) {
17
13
  if (mediaType === "gradient") return "gradient";
18
14
  if (mediaType === "video") return "video";
19
15
  if (mediaType === "photo") return "photo";
20
16
  return mediaUrl && isVideoUrl(mediaUrl) ? "video" : "photo";
21
17
  }
22
- function hasSceneMedia(variables) {
23
- return String(variables.mediaUrl || "").trim() !== "" && String(variables.mediaType || "auto") !== "gradient";
24
- }
25
18
 
26
19
  export {
27
- limitsConcurrentVideoDecoders,
28
- resolveMediaType,
29
- hasSceneMedia
20
+ resolveMediaType
30
21
  };
@@ -0,0 +1,50 @@
1
+ import {
2
+ getReadableSceneDuration
3
+ } from "./chunk-WZESNEPT.js";
4
+
5
+ // src/protocol/timeline.ts
6
+ function resolveVideoTimeline(config) {
7
+ const ranges = [];
8
+ let cursor = 0;
9
+ for (const scene of config.scenes) {
10
+ const timing = scene.timing ?? {};
11
+ const beatStart = timing.beatStart == null ? void 0 : config.audio?.beatMarkers[timing.beatStart]?.time;
12
+ const beatEnd = timing.beatEnd == null ? void 0 : config.audio?.beatMarkers[timing.beatEnd]?.time;
13
+ const start = timing.startTime ?? beatStart ?? cursor;
14
+ const duration = Math.max(1e-3, timing.fixedDuration ?? 5);
15
+ const end = Math.max(start + 1e-3, timing.endTime ?? beatEnd ?? start + duration);
16
+ ranges.push({ scene, start, end });
17
+ cursor = end;
18
+ }
19
+ return ranges;
20
+ }
21
+ function getVideoDuration(config) {
22
+ return resolveVideoTimeline(config).at(-1)?.end ?? 0;
23
+ }
24
+
25
+ // src/protocol/scene-duration.ts
26
+ var NARRATION_WORDS_PER_SECOND = 2.6;
27
+ var NARRATION_TAIL_SECONDS = 0.6;
28
+ function getSceneDurationBounds(scene, metadata) {
29
+ return {
30
+ minimum: Math.max(1e-3, metadata?.minDuration ?? 1),
31
+ readable: getReadableSceneDuration(scene, metadata)
32
+ };
33
+ }
34
+ function getSpokenDuration(narration) {
35
+ const words = narration.trim().split(/\s+/u).filter(Boolean).length;
36
+ if (words === 0) return 0;
37
+ return words / NARRATION_WORDS_PER_SECOND + NARRATION_TAIL_SECONDS;
38
+ }
39
+ function getSceneDuration(scene, metadata) {
40
+ const { readable } = getSceneDurationBounds(scene, metadata);
41
+ return Math.max(readable, scene.narration ? getSpokenDuration(scene.narration) : 0);
42
+ }
43
+
44
+ export {
45
+ resolveVideoTimeline,
46
+ getVideoDuration,
47
+ getSceneDurationBounds,
48
+ getSpokenDuration,
49
+ getSceneDuration
50
+ };
@@ -0,0 +1,25 @@
1
+ // src/visual-system/catalog/builtin-metadata.ts
2
+ var SCENE_DEFINITIONS = Object.freeze([
3
+ Object.freeze({
4
+ id: "cinemaMedia",
5
+ defaults: Object.freeze({ mediaUrl: "", mediaType: "video", mediaPoster: "" }),
6
+ minDuration: 3,
7
+ preferredDuration: 6,
8
+ timing: Object.freeze({ contentFields: Object.freeze([]), contentUnit: "words", revealSeconds: 0, holdSeconds: 3, exitSeconds: 0 })
9
+ }),
10
+ Object.freeze({
11
+ id: "chapterTitle",
12
+ defaults: Object.freeze({ title: "A different perspective" }),
13
+ minDuration: 3,
14
+ preferredDuration: 4,
15
+ timing: Object.freeze({ contentFields: Object.freeze(["title"]), contentUnit: "words", revealSeconds: 0.8, holdSeconds: 1.5, exitSeconds: 0.8 })
16
+ })
17
+ ]);
18
+ function getBuiltinSceneDefinition(id) {
19
+ return SCENE_DEFINITIONS.find((scene) => scene.id === id);
20
+ }
21
+
22
+ export {
23
+ SCENE_DEFINITIONS,
24
+ getBuiltinSceneDefinition
25
+ };