@skyphusion-labs/vivijure-core 0.9.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 (267) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +87 -0
  3. package/dist/audio-routing.d.ts +2 -0
  4. package/dist/audio-routing.d.ts.map +1 -0
  5. package/dist/audio-routing.js +4 -0
  6. package/dist/audio-routing.js.map +1 -0
  7. package/dist/audio-stage.d.ts +4 -0
  8. package/dist/audio-stage.d.ts.map +1 -0
  9. package/dist/audio-stage.js +25 -0
  10. package/dist/audio-stage.js.map +1 -0
  11. package/dist/beat-analyze.d.ts +22 -0
  12. package/dist/beat-analyze.d.ts.map +1 -0
  13. package/dist/beat-analyze.js +111 -0
  14. package/dist/beat-analyze.js.map +1 -0
  15. package/dist/beat-sync-types.d.ts +33 -0
  16. package/dist/beat-sync-types.d.ts.map +1 -0
  17. package/dist/beat-sync-types.js +4 -0
  18. package/dist/beat-sync-types.js.map +1 -0
  19. package/dist/bundle-assembler.d.ts +54 -0
  20. package/dist/bundle-assembler.d.ts.map +1 -0
  21. package/dist/bundle-assembler.js +376 -0
  22. package/dist/bundle-assembler.js.map +1 -0
  23. package/dist/bundle-durations.d.ts +5 -0
  24. package/dist/bundle-durations.d.ts.map +1 -0
  25. package/dist/bundle-durations.js +68 -0
  26. package/dist/bundle-durations.js.map +1 -0
  27. package/dist/bundle-storyboard.d.ts +11 -0
  28. package/dist/bundle-storyboard.d.ts.map +1 -0
  29. package/dist/bundle-storyboard.js +82 -0
  30. package/dist/bundle-storyboard.js.map +1 -0
  31. package/dist/captions.d.ts +25 -0
  32. package/dist/captions.d.ts.map +1 -0
  33. package/dist/captions.js +56 -0
  34. package/dist/captions.js.map +1 -0
  35. package/dist/cast-db.d.ts +61 -0
  36. package/dist/cast-db.d.ts.map +1 -0
  37. package/dist/cast-db.js +324 -0
  38. package/dist/cast-db.js.map +1 -0
  39. package/dist/cast-lora-train.d.ts +18 -0
  40. package/dist/cast-lora-train.d.ts.map +1 -0
  41. package/dist/cast-lora-train.js +215 -0
  42. package/dist/cast-lora-train.js.map +1 -0
  43. package/dist/cast-loras.d.ts +21 -0
  44. package/dist/cast-loras.d.ts.map +1 -0
  45. package/dist/cast-loras.js +69 -0
  46. package/dist/cast-loras.js.map +1 -0
  47. package/dist/clip-content-validate.d.ts +43 -0
  48. package/dist/clip-content-validate.d.ts.map +1 -0
  49. package/dist/clip-content-validate.js +115 -0
  50. package/dist/clip-content-validate.js.map +1 -0
  51. package/dist/clip-job-model.d.ts +42 -0
  52. package/dist/clip-job-model.d.ts.map +1 -0
  53. package/dist/clip-job-model.js +8 -0
  54. package/dist/clip-job-model.js.map +1 -0
  55. package/dist/clip-validate.d.ts +33 -0
  56. package/dist/clip-validate.d.ts.map +1 -0
  57. package/dist/clip-validate.js +247 -0
  58. package/dist/clip-validate.js.map +1 -0
  59. package/dist/d1-retry.d.ts +20 -0
  60. package/dist/d1-retry.d.ts.map +1 -0
  61. package/dist/d1-retry.js +67 -0
  62. package/dist/d1-retry.js.map +1 -0
  63. package/dist/db-env.d.ts +6 -0
  64. package/dist/db-env.d.ts.map +1 -0
  65. package/dist/db-env.js +2 -0
  66. package/dist/db-env.js.map +1 -0
  67. package/dist/dialogue-lines.d.ts +23 -0
  68. package/dist/dialogue-lines.d.ts.map +1 -0
  69. package/dist/dialogue-lines.js +84 -0
  70. package/dist/dialogue-lines.js.map +1 -0
  71. package/dist/film-advance-lease.d.ts +9 -0
  72. package/dist/film-advance-lease.d.ts.map +1 -0
  73. package/dist/film-advance-lease.js +19 -0
  74. package/dist/film-advance-lease.js.map +1 -0
  75. package/dist/film-model.d.ts +415 -0
  76. package/dist/film-model.d.ts.map +1 -0
  77. package/dist/film-model.js +541 -0
  78. package/dist/film-model.js.map +1 -0
  79. package/dist/film-orchestrator.d.ts +216 -0
  80. package/dist/film-orchestrator.d.ts.map +1 -0
  81. package/dist/film-orchestrator.js +2204 -0
  82. package/dist/film-orchestrator.js.map +1 -0
  83. package/dist/film-render-bridge.d.ts +26 -0
  84. package/dist/film-render-bridge.d.ts.map +1 -0
  85. package/dist/film-render-bridge.js +176 -0
  86. package/dist/film-render-bridge.js.map +1 -0
  87. package/dist/finish-hash.d.ts +10 -0
  88. package/dist/finish-hash.d.ts.map +1 -0
  89. package/dist/finish-hash.js +60 -0
  90. package/dist/finish-hash.js.map +1 -0
  91. package/dist/index.d.ts +48 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +45 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/key-safety.d.ts +5 -0
  96. package/dist/key-safety.d.ts.map +1 -0
  97. package/dist/key-safety.js +23 -0
  98. package/dist/key-safety.js.map +1 -0
  99. package/dist/lora-bundle.d.ts +19 -0
  100. package/dist/lora-bundle.d.ts.map +1 -0
  101. package/dist/lora-bundle.js +75 -0
  102. package/dist/lora-bundle.js.map +1 -0
  103. package/dist/lora-keys.d.ts +2 -0
  104. package/dist/lora-keys.d.ts.map +1 -0
  105. package/dist/lora-keys.js +4 -0
  106. package/dist/lora-keys.js.map +1 -0
  107. package/dist/modules/conformance.d.ts +58 -0
  108. package/dist/modules/conformance.d.ts.map +1 -0
  109. package/dist/modules/conformance.js +369 -0
  110. package/dist/modules/conformance.js.map +1 -0
  111. package/dist/modules/manifest-validate.d.ts +5 -0
  112. package/dist/modules/manifest-validate.d.ts.map +1 -0
  113. package/dist/modules/manifest-validate.js +60 -0
  114. package/dist/modules/manifest-validate.js.map +1 -0
  115. package/dist/modules/registry.d.ts +184 -0
  116. package/dist/modules/registry.d.ts.map +1 -0
  117. package/dist/modules/registry.js +653 -0
  118. package/dist/modules/registry.js.map +1 -0
  119. package/dist/modules/render-pipeline.d.ts +38 -0
  120. package/dist/modules/render-pipeline.d.ts.map +1 -0
  121. package/dist/modules/render-pipeline.js +40 -0
  122. package/dist/modules/render-pipeline.js.map +1 -0
  123. package/dist/modules/types.d.ts +544 -0
  124. package/dist/modules/types.d.ts.map +1 -0
  125. package/dist/modules/types.js +57 -0
  126. package/dist/modules/types.js.map +1 -0
  127. package/dist/operator-config.d.ts +20 -0
  128. package/dist/operator-config.d.ts.map +1 -0
  129. package/dist/operator-config.js +85 -0
  130. package/dist/operator-config.js.map +1 -0
  131. package/dist/output-extract.d.ts +8 -0
  132. package/dist/output-extract.d.ts.map +1 -0
  133. package/dist/output-extract.js +129 -0
  134. package/dist/output-extract.js.map +1 -0
  135. package/dist/planner-prompt.d.ts +12 -0
  136. package/dist/planner-prompt.d.ts.map +1 -0
  137. package/dist/planner-prompt.js +337 -0
  138. package/dist/planner-prompt.js.map +1 -0
  139. package/dist/planner-yaml.d.ts +15 -0
  140. package/dist/planner-yaml.d.ts.map +1 -0
  141. package/dist/planner-yaml.js +157 -0
  142. package/dist/planner-yaml.js.map +1 -0
  143. package/dist/platform/fetcher.d.ts +3 -0
  144. package/dist/platform/fetcher.d.ts.map +1 -0
  145. package/dist/platform/fetcher.js +7 -0
  146. package/dist/platform/fetcher.js.map +1 -0
  147. package/dist/platform/index.d.ts +6 -0
  148. package/dist/platform/index.d.ts.map +1 -0
  149. package/dist/platform/index.js +5 -0
  150. package/dist/platform/index.js.map +1 -0
  151. package/dist/platform/object-store-r2.d.ts +26 -0
  152. package/dist/platform/object-store-r2.d.ts.map +1 -0
  153. package/dist/platform/object-store-r2.js +74 -0
  154. package/dist/platform/object-store-r2.js.map +1 -0
  155. package/dist/platform/orchestrator-context.d.ts +20 -0
  156. package/dist/platform/orchestrator-context.d.ts.map +1 -0
  157. package/dist/platform/orchestrator-context.js +28 -0
  158. package/dist/platform/orchestrator-context.js.map +1 -0
  159. package/dist/platform/r2-types.d.ts +41 -0
  160. package/dist/platform/r2-types.d.ts.map +1 -0
  161. package/dist/platform/r2-types.js +3 -0
  162. package/dist/platform/r2-types.js.map +1 -0
  163. package/dist/platform/types.d.ts +90 -0
  164. package/dist/platform/types.d.ts.map +1 -0
  165. package/dist/platform/types.js +23 -0
  166. package/dist/platform/types.js.map +1 -0
  167. package/dist/preflight.d.ts +57 -0
  168. package/dist/preflight.d.ts.map +1 -0
  169. package/dist/preflight.js +243 -0
  170. package/dist/preflight.js.map +1 -0
  171. package/dist/presign.d.ts +5 -0
  172. package/dist/presign.d.ts.map +1 -0
  173. package/dist/presign.js +24 -0
  174. package/dist/presign.js.map +1 -0
  175. package/dist/public-id.d.ts +8 -0
  176. package/dist/public-id.d.ts.map +1 -0
  177. package/dist/public-id.js +25 -0
  178. package/dist/public-id.js.map +1 -0
  179. package/dist/render-adopt.d.ts +3 -0
  180. package/dist/render-adopt.d.ts.map +1 -0
  181. package/dist/render-adopt.js +92 -0
  182. package/dist/render-adopt.js.map +1 -0
  183. package/dist/render-log.d.ts +25 -0
  184. package/dist/render-log.d.ts.map +1 -0
  185. package/dist/render-log.js +139 -0
  186. package/dist/render-log.js.map +1 -0
  187. package/dist/render-module-config.d.ts +28 -0
  188. package/dist/render-module-config.d.ts.map +1 -0
  189. package/dist/render-module-config.js +124 -0
  190. package/dist/render-module-config.js.map +1 -0
  191. package/dist/render-mux.d.ts +18 -0
  192. package/dist/render-mux.d.ts.map +1 -0
  193. package/dist/render-mux.js +69 -0
  194. package/dist/render-mux.js.map +1 -0
  195. package/dist/render-orchestrator.d.ts +71 -0
  196. package/dist/render-orchestrator.d.ts.map +1 -0
  197. package/dist/render-orchestrator.js +376 -0
  198. package/dist/render-orchestrator.js.map +1 -0
  199. package/dist/render-sweep.d.ts +5 -0
  200. package/dist/render-sweep.d.ts.map +1 -0
  201. package/dist/render-sweep.js +62 -0
  202. package/dist/render-sweep.js.map +1 -0
  203. package/dist/renders-db.d.ts +129 -0
  204. package/dist/renders-db.d.ts.map +1 -0
  205. package/dist/renders-db.js +752 -0
  206. package/dist/renders-db.js.map +1 -0
  207. package/dist/runpod-submit.d.ts +151 -0
  208. package/dist/runpod-submit.d.ts.map +1 -0
  209. package/dist/runpod-submit.js +469 -0
  210. package/dist/runpod-submit.js.map +1 -0
  211. package/dist/runpod-types.d.ts +2 -0
  212. package/dist/runpod-types.d.ts.map +1 -0
  213. package/dist/runpod-types.js +4 -0
  214. package/dist/runpod-types.js.map +1 -0
  215. package/dist/scatter-notify.d.ts +4 -0
  216. package/dist/scatter-notify.d.ts.map +1 -0
  217. package/dist/scatter-notify.js +43 -0
  218. package/dist/scatter-notify.js.map +1 -0
  219. package/dist/scatter-orchestrator-types.d.ts +56 -0
  220. package/dist/scatter-orchestrator-types.d.ts.map +1 -0
  221. package/dist/scatter-orchestrator-types.js +3 -0
  222. package/dist/scatter-orchestrator-types.js.map +1 -0
  223. package/dist/scatter-orchestrator.d.ts +70 -0
  224. package/dist/scatter-orchestrator.d.ts.map +1 -0
  225. package/dist/scatter-orchestrator.js +614 -0
  226. package/dist/scatter-orchestrator.js.map +1 -0
  227. package/dist/scatter.d.ts +35 -0
  228. package/dist/scatter.d.ts.map +1 -0
  229. package/dist/scatter.js +171 -0
  230. package/dist/scatter.js.map +1 -0
  231. package/dist/secret-store.d.ts +5 -0
  232. package/dist/secret-store.d.ts.map +1 -0
  233. package/dist/secret-store.js +19 -0
  234. package/dist/secret-store.js.map +1 -0
  235. package/dist/shot-durations-parse.d.ts +3 -0
  236. package/dist/shot-durations-parse.d.ts.map +1 -0
  237. package/dist/shot-durations-parse.js +43 -0
  238. package/dist/shot-durations-parse.js.map +1 -0
  239. package/dist/srt.d.ts +11 -0
  240. package/dist/srt.d.ts.map +1 -0
  241. package/dist/srt.js +54 -0
  242. package/dist/srt.js.map +1 -0
  243. package/dist/storyboard-ids.d.ts +2 -0
  244. package/dist/storyboard-ids.d.ts.map +1 -0
  245. package/dist/storyboard-ids.js +12 -0
  246. package/dist/storyboard-ids.js.map +1 -0
  247. package/dist/storyboard-projects-db.d.ts +31 -0
  248. package/dist/storyboard-projects-db.d.ts.map +1 -0
  249. package/dist/storyboard-projects-db.js +153 -0
  250. package/dist/storyboard-projects-db.js.map +1 -0
  251. package/dist/storyboard-validate.d.ts +72 -0
  252. package/dist/storyboard-validate.d.ts.map +1 -0
  253. package/dist/storyboard-validate.js +595 -0
  254. package/dist/storyboard-validate.js.map +1 -0
  255. package/dist/structured-events.d.ts +6 -0
  256. package/dist/structured-events.d.ts.map +1 -0
  257. package/dist/structured-events.js +15 -0
  258. package/dist/structured-events.js.map +1 -0
  259. package/dist/tar.d.ts +9 -0
  260. package/dist/tar.d.ts.map +1 -0
  261. package/dist/tar.js +151 -0
  262. package/dist/tar.js.map +1 -0
  263. package/dist/voices.d.ts +12 -0
  264. package/dist/voices.d.ts.map +1 -0
  265. package/dist/voices.js +37 -0
  266. package/dist/voices.js.map +1 -0
  267. package/package.json +71 -0
@@ -0,0 +1,85 @@
1
+ // operator-config: the persistent, INSTANCE-scoped store for config_schema fields marked
2
+ // `scope: "install"` (the operator-set-once knobs, e.g. notify-email's recipient address).
3
+ //
4
+ // The identity strip (#292) zeroed user_email, so this is NOT per-user: it is per-INSTALL, keyed on
5
+ // (module_name, field_key) in the operator_module_config D1 table (migration 0005). The operator sets
6
+ // these on the studio settings page (GET/PATCH /api/modules/:name/config); the core persists them
7
+ // here and INJECTS them into the module invoke at hook time, so a field like notify_email actually
8
+ // reaches the module at render.complete. Per-render fields (scope omitted / "render") are untouched
9
+ // by this store -- they still flow through the per-render config path.
10
+ //
11
+ // The pure helpers (install-subschema projection, clamp-on-write) are vitest-testable without a D1
12
+ // binding; the two query functions are verified live (matches the user-prefs / renders-db split).
13
+ import { validateConfig } from "./modules/registry.js";
14
+ /** Project a module's config_schema down to ONLY its install-scope fields. A field with no `scope`
15
+ * (or scope "render") is a per-render knob and is excluded -- so this store never owns render config. */
16
+ export function installSubschema(schema) {
17
+ const out = {};
18
+ if (!schema)
19
+ return out;
20
+ for (const [key, field] of Object.entries(schema)) {
21
+ if (field.scope === "install")
22
+ out[key] = field;
23
+ }
24
+ return out;
25
+ }
26
+ /** Does this module expose any install-scope config at all? */
27
+ export function hasInstallConfig(schema) {
28
+ return Object.keys(installSubschema(schema)).length > 0;
29
+ }
30
+ /** The set of writable install field keys for a module (what PATCH is allowed to set). */
31
+ export function installFieldKeys(schema) {
32
+ return Object.keys(installSubschema(schema));
33
+ }
34
+ /** Clamp an incoming patch to the module's install subschema, dropping unknown / render-scope keys.
35
+ * Reuses the SAME validateConfig clamp the invoke path uses, so a stored value can never violate the
36
+ * contract. Returns the full install-config (every install field, missing ones at their default). */
37
+ export function clampInstallPatch(schema, current, patch) {
38
+ const sub = installSubschema(schema);
39
+ const merged = { ...current, ...(patch ?? {}) };
40
+ return validateConfig(sub, merged);
41
+ }
42
+ // --------------------------------------------------------------------------- D1 (verified live)
43
+ /** Read the raw stored install values for a module (field_key -> decoded scalar). No schema applied;
44
+ * callers pass through validateConfig / the subschema to get a contract-clean, default-filled view. */
45
+ async function readStored(env, moduleName) {
46
+ const rs = await env.DB.prepare("SELECT field_key, value_json FROM operator_module_config WHERE module_name = ?")
47
+ .bind(moduleName)
48
+ .all();
49
+ const out = {};
50
+ for (const row of rs.results ?? []) {
51
+ try {
52
+ out[row.field_key] = JSON.parse(row.value_json);
53
+ }
54
+ catch {
55
+ // a corrupt row is ignored; the field falls back to its schema default downstream.
56
+ }
57
+ }
58
+ return out;
59
+ }
60
+ /** The module's install-config as the INVOKE path consumes it: every install field present, missing
61
+ * ones at their schema default, clamped to the contract. Empty schema -> {} (a clean no-op). */
62
+ export async function loadInstallConfig(env, moduleName, schema) {
63
+ const sub = installSubschema(schema);
64
+ if (Object.keys(sub).length === 0)
65
+ return {};
66
+ const stored = await readStored(env, moduleName);
67
+ return validateConfig(sub, stored);
68
+ }
69
+ /** Persist an operator PATCH: clamp to the install subschema (unknown / render keys dropped), then
70
+ * upsert each field. Returns the resulting full install-config view. */
71
+ export async function setInstallConfig(env, moduleName, schema, patch) {
72
+ const sub = installSubschema(schema);
73
+ const current = await readStored(env, moduleName);
74
+ const next = clampInstallPatch(schema, current, patch);
75
+ const now = Math.floor(Date.now() / 1000);
76
+ const stmt = env.DB.prepare(`INSERT INTO operator_module_config (module_name, field_key, value_json, updated_at)
77
+ VALUES (?, ?, ?, ?)
78
+ ON CONFLICT(module_name, field_key) DO UPDATE SET
79
+ value_json = excluded.value_json, updated_at = excluded.updated_at`);
80
+ const writes = Object.keys(sub).map((key) => stmt.bind(moduleName, key, JSON.stringify(next[key]), now));
81
+ if (writes.length)
82
+ await env.DB.batch(writes);
83
+ return next;
84
+ }
85
+ //# sourceMappingURL=operator-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operator-config.js","sourceRoot":"","sources":["../src/operator-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare function extractOutput(result: unknown): string;
2
+ export declare function extractUsage(result: unknown): {
3
+ in_: number | null;
4
+ out_: number | null;
5
+ };
6
+ export declare function detectProviderFailure(result: unknown): string | null;
7
+ export declare function extractProxiedImageUrl(result: unknown): string | null;
8
+ //# sourceMappingURL=output-extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-extract.d.ts","sourceRoot":"","sources":["../src/output-extract.ts"],"names":[],"mappings":"AAeA,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAmDrD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAqBzF;AAcD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CASpE;AAQD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAOrE"}
@@ -0,0 +1,129 @@
1
+ // Text-model output + usage extraction (v0.21.0).
2
+ //
3
+ // Extracted from src/index.ts so these pure functions are unit-testable
4
+ // without importing index.ts (which pulls in `cloudflare:workers` and so
5
+ // can't be loaded under the plain-Node vitest pool). Mirrors the same
6
+ // extraction discipline used for parsers, chunking, discord, and ai-binding.
7
+ //
8
+ // Both functions normalize the many response shapes that reach the
9
+ // non-streaming chat path. The shapes come from: Workers AI hosted models
10
+ // (env.AI.run), Anthropic Messages API, Gemini, and OpenAI-style responses
11
+ // (both chat-completions and the Responses API) returned by unified-billing
12
+ // proxied models routed through env.AI.run. New shapes should be added here
13
+ // with a unit test in tests/output-extract.test.ts rather than inlined at a
14
+ // call site.
15
+ export function extractOutput(result) {
16
+ if (typeof result === "string")
17
+ return result;
18
+ const r = result;
19
+ if (typeof r?.response === "string")
20
+ return r.response;
21
+ if (typeof r?.result === "string")
22
+ return r.result;
23
+ // OpenAI chat-completions shape (also what AI Gateway returns for most
24
+ // proxied OpenAI/OpenAI-compatible chat models): { choices: [{ message }] }
25
+ const choices = r?.choices;
26
+ if (Array.isArray(choices) && typeof choices[0]?.message?.content === "string") {
27
+ return choices[0].message.content;
28
+ }
29
+ // Anthropic Messages API: top-level content array
30
+ const content = r?.content;
31
+ if (Array.isArray(content)) {
32
+ const text = content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
33
+ if (text)
34
+ return text;
35
+ }
36
+ // OpenAI Responses API shape (used by gpt-5.x-pro and similar): a top-level
37
+ // `output` array of items, each with a `content` array of typed blocks.
38
+ // Output text lives in blocks of type "output_text" (or plain "text").
39
+ const out = r?.output;
40
+ if (Array.isArray(out)) {
41
+ const text = out
42
+ .flatMap((block) => {
43
+ const b = block;
44
+ return (b?.content ?? [])
45
+ .filter((c) => c?.type === "output_text" || c?.type === "text")
46
+ .map((c) => c.text ?? "");
47
+ })
48
+ .join("");
49
+ if (text)
50
+ return text;
51
+ }
52
+ // Gemini-native (google/gemini-* via the binding, v0.21.3):
53
+ // { candidates: [{ content: { parts: [{ text }] } }] }
54
+ const candidates = r?.candidates;
55
+ if (Array.isArray(candidates)) {
56
+ const text = (candidates[0]?.content?.parts ?? [])
57
+ .map((p) => p.text ?? "")
58
+ .join("");
59
+ if (text)
60
+ return text;
61
+ }
62
+ // LLaVA 1.5 image-to-text (@cf/llava-hf/llava-1.5-7b-hf): { description }
63
+ if (typeof r?.description === "string")
64
+ return r.description;
65
+ return JSON.stringify(result);
66
+ }
67
+ export function extractUsage(result) {
68
+ const r = result;
69
+ // OpenAI / Anthropic: usage object on result. OpenAI uses
70
+ // prompt_tokens/completion_tokens; Anthropic uses input_tokens/output_tokens.
71
+ const u = r?.usage;
72
+ if (u) {
73
+ return {
74
+ in_: u.prompt_tokens ?? u.input_tokens ?? null,
75
+ out_: u.completion_tokens ?? u.output_tokens ?? null,
76
+ };
77
+ }
78
+ // Gemini-native usage (v0.21.3): usageMetadata.promptTokenCount /
79
+ // candidatesTokenCount (thoughtsTokenCount is reasoning, not counted as out).
80
+ const um = r?.usageMetadata;
81
+ if (um) {
82
+ return {
83
+ in_: um.promptTokenCount ?? null,
84
+ out_: um.candidatesTokenCount ?? null,
85
+ };
86
+ }
87
+ return { in_: null, out_: null };
88
+ }
89
+ // Detect a provider failure envelope returned as a normal resolved value
90
+ // rather than thrown. Unified-billing proxied models (seen first with the
91
+ // OpenAI chat models in v0.21.0) can return { state: "Failed", error: "..." }
92
+ // from env.AI.run instead of rejecting; the gateway uses the same envelope
93
+ // the LongRunWorkflow already checks for video/music. Without catching it,
94
+ // extractOutput would JSON.stringify the envelope into chats.output and a
95
+ // failed turn would persist as if it succeeded.
96
+ //
97
+ // Sync chat responses (OpenAI {choices}, Anthropic {content}, Workers AI
98
+ // {response}) carry no `state` field, so a present `state` that isn't
99
+ // "Completed" is the signal. Returns the upstream error message (for the
100
+ // caller to surface as a 502) or null when the result looks normal.
101
+ export function detectProviderFailure(result) {
102
+ if (!result || typeof result !== "object")
103
+ return null;
104
+ const r = result;
105
+ if (typeof r.state === "string" && r.state !== "Completed") {
106
+ return typeof r.error === "string" && r.error.trim()
107
+ ? r.error
108
+ : `provider returned state "${r.state}"`;
109
+ }
110
+ return null;
111
+ }
112
+ // Pull the generated-image URL out of a proxied image-gen response. Proxied
113
+ // image models (the Google nano-banana family, added v0.21.2) return a URL,
114
+ // not base64, wrapped in the same { state, result } envelope as video/music:
115
+ // { state: "Completed", result: { image: "<url>" } }
116
+ // The bare { image: "<url>" } form (the documented Output schema, without the
117
+ // binding wrapper) is accepted too. Returns the URL string or null.
118
+ export function extractProxiedImageUrl(result) {
119
+ if (!result || typeof result !== "object")
120
+ return null;
121
+ const r = result;
122
+ const wrapped = r.result?.image;
123
+ if (typeof wrapped === "string" && wrapped.length > 0)
124
+ return wrapped;
125
+ if (typeof r.image === "string" && r.image.length > 0)
126
+ return r.image;
127
+ return null;
128
+ }
129
+ //# sourceMappingURL=output-extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-extract.js","sourceRoot":"","sources":["../src/output-extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { SlotId } from "./storyboard-validate.js";
2
+ export interface PlannerCharacter {
3
+ slot: SlotId;
4
+ name: string;
5
+ bible: string;
6
+ }
7
+ export declare function buildPlanningSystemPrompt(): string;
8
+ export declare function buildPlanningUserMessage(brief: string, characters: PlannerCharacter[], beatBlock?: string): string;
9
+ export declare function buildRefinementSystemPrompt(): string;
10
+ export declare function buildRefinementUserMessage(currentStoryboard: unknown, message: string): string;
11
+ export declare function stripJsonFences(raw: string): string;
12
+ //# sourceMappingURL=planner-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner-prompt.d.ts","sourceRoot":"","sources":["../src/planner-prompt.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAYvD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAQD,wBAAgB,yBAAyB,IAAI,MAAM,CA6JlD;AAMD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gBAAgB,EAAE,EAC9B,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAsBR;AAaD,wBAAgB,2BAA2B,IAAI,MAAM,CA+FpD;AAED,wBAAgB,0BAA0B,CACxC,iBAAiB,EAAE,OAAO,EAC1B,OAAO,EAAE,MAAM,GACd,MAAM,CAUR;AASD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAanD"}
@@ -0,0 +1,337 @@
1
+ // Pure string helpers for the storyboard planner (v0.28.0).
2
+ //
3
+ // Holds the system + user prompt builders and the JSON fence stripper.
4
+ // Extracted from planner.ts so the prompt text and the output cleanup are
5
+ // unit-testable without dragging Env, fetch, or env.AI into the test
6
+ // (same pattern as parsers/, longrun-params.ts, output-extract.ts).
7
+ import { DIALOGUE_MAX_CHARS, FULL_PROMPT_MAX_CHARS, SCENE_PROMPT_MAX_WORDS, STORYBOARD_MAX_SCENES, STYLE_PREFIX_MAX_CHARS, } from "./storyboard-validate.js";
8
+ // System prompt sent to the model. Constrains output to a JSON object
9
+ // matching StoryboardInput. Declares the slot id enum, the style_prefix
10
+ // once-only rule (style_prefix is prepended to every scene at manifest-
11
+ // build time, so repeating style words inside a scene prompt double-
12
+ // applies them), and the literal-"None" convention for style_category /
13
+ // style_preset (the renderer disables on the string, not on null).
14
+ export function buildPlanningSystemPrompt() {
15
+ return `You are the storyboard planner for a music-video / short-film AI pipeline.
16
+ Your output is consumed directly by a renderer that turns each scene into
17
+ a Wan I2V clip with an SDXL keyframe. Return ONE JSON object that exactly
18
+ matches the schema below. No prose. No markdown. No YAML. Do not wrap the
19
+ JSON in code fences.
20
+
21
+ SCHEMA:
22
+ {
23
+ "title": string,
24
+ "full_prompt": string,
25
+ "duration_seconds": number,
26
+ "clip_seconds": number,
27
+ "style_prefix": string,
28
+ "style_category": string,
29
+ "style_preset": string,
30
+ "use_characters": ["A" | "B" | "C" | "D", ...],
31
+ "cast_rules": string,
32
+ "scenes": [
33
+ {
34
+ "prompt": string,
35
+ "character_slots": ["A" | "B" | "C" | "D", ...],
36
+ "act": string,
37
+ "start": number,
38
+ "end": number,
39
+ "target_seconds": number,
40
+ "dialogue": { "slot": "A" | "B" | "C" | "D", "text": string }
41
+ },
42
+ ...
43
+ ]
44
+ }
45
+
46
+ FIELDS:
47
+ - title: short film title; spaces become underscores in the on-disk slug.
48
+ - full_prompt: one or two sentence film-level summary (optional).
49
+ - duration_seconds: total film length target in seconds (positive number).
50
+ - clip_seconds: per-shot target length in seconds (positive number).
51
+ - style_prefix: ALL style language goes here, EXACTLY ONCE. Palette, lens,
52
+ era, lighting register, film stock, color grade, key visual vocabulary.
53
+ The renderer prepends this string verbatim to every scene prompt at
54
+ manifest-build time, so any style word repeated inside a scene prompt
55
+ is double-applied and biases the keyframe.
56
+ - style_category, style_preset: lookups the renderer disables on the
57
+ literal string "None". When you do not want a category or preset
58
+ applied, emit the string "None", never null and never the empty string.
59
+ - use_characters: slot ids loaded for this render. Slot ids are exactly
60
+ the literal strings "A", "B", "C", "D". Nothing else is valid. Set this
61
+ to the slots you plan to feature; omit slots that will not appear.
62
+ - cast_rules: optional plain-text rules for cast cohesion (pairings,
63
+ outfit constraints, prop continuity).
64
+ - scenes: REQUIRED, at least one entry, one per shot.
65
+ - scenes[].prompt: SHOT CONTENT ONLY. Subject action, framing, moment,
66
+ emotional beat. Do NOT include style language; that lives in
67
+ style_prefix above. Do NOT repeat the film title or full_prompt.
68
+ - scenes[].character_slots: subset of use_characters. Omit the field
69
+ entirely for an empty-frame shot rather than send an unloaded slot.
70
+ - scenes[].act: optional act tag, one of "opening", "rising", "turn",
71
+ "climax", "resolution".
72
+ - scenes[].start, end, target_seconds: optional per-shot timing in
73
+ seconds. start may be 0; end must be strictly greater than start;
74
+ target_seconds must be positive.
75
+ - scenes[].dialogue: OPTIONAL spoken line for the shot (auto-direction).
76
+ Include it ONLY when a character actually speaks on camera in that shot.
77
+ "slot" is the speaking character and MUST be one of that scene's
78
+ character_slots. "text" is the line itself: natural spoken words only,
79
+ no quotation marks, no "Name:" speaker prefix, no stage directions. Keep
80
+ it short enough to be said within the shot's length (roughly 2-3 spoken
81
+ words per second). Omit the field entirely for a silent shot.
82
+
83
+ HARD RULES:
84
+ 1. style_prefix is the ONLY place style language belongs. Repeating style
85
+ words inside scenes[].prompt double-applies them because style_prefix
86
+ is prepended to every scene at manifest-build time.
87
+ 2. Every entry in a scene's character_slots must appear in the top-level
88
+ use_characters array. Never lock a scene to a slot you have not loaded.
89
+ 3. Slot ids are exactly "A", "B", "C", "D". No lowercase, no other letters.
90
+ 4. style_category and style_preset default to the literal string "None"
91
+ when you do not want a lookup. Never null. Never empty string.
92
+ 5. Numeric fields are plain JSON numbers, never strings. No units ("s",
93
+ "sec", "min"); seconds is implicit.
94
+ 6. Plan 3 to 12 scenes for a vignette / single-track music video unless
95
+ the brief specifies otherwise.
96
+ 6a. A scene's dialogue.slot MUST be one of that scene's character_slots
97
+ (the speaker has to be in the shot). Only one character speaks per
98
+ shot. Most shots have no dialogue; reserve it for genuine spoken beats.
99
+
100
+ LENGTH CAPS (the renderer rejects outputs over these caps because they
101
+ overflow SDXL's CLIP 77-token text encoder or break manifest builds):
102
+ 7. Each scenes[].prompt: at most ${SCENE_PROMPT_MAX_WORDS} words. The pod
103
+ prepends 2-4 LoRA trigger tokens plus the style_prefix to every scene,
104
+ leaving roughly ${SCENE_PROMPT_MAX_WORDS}-word headroom inside CLIP 77.
105
+ Move character appearance details to the cast bible (already loaded),
106
+ not into the scene prompt.
107
+ 8. style_prefix: at most ${STYLE_PREFIX_MAX_CHARS} characters. Compress.
108
+ Three or four palette / lens / lighting clauses is plenty; the model
109
+ reads ALL of it once per scene, so verbosity here costs every shot.
110
+ 9. full_prompt: at most ${FULL_PROMPT_MAX_CHARS} characters. This is the
111
+ film-level summary, not the script; one or two sentences.
112
+ 10. scenes array length: at most ${STORYBOARD_MAX_SCENES} entries. A 50-
113
+ shot render is already 25+ minutes of GPU time at typical clip
114
+ seconds; if the brief implies more, shorten clip_seconds or split.
115
+ 11. scenes[].dialogue.text: at most ${DIALOGUE_MAX_CHARS} characters. One
116
+ spoken line per shot; a clip is only a few seconds, so a sentence or
117
+ two is the ceiling, not a speech.
118
+
119
+ GOLDEN EXAMPLE (mirrors the renderer's storyboard.example.yaml). This
120
+ shape is the canonical output; produce JSON that matches its style:
121
+
122
+ {
123
+ "title": "morning_walk",
124
+ "full_prompt": "Three-shot vignette: Elena walks into a hilltop clearing at dawn.",
125
+ "duration_seconds": 21,
126
+ "clip_seconds": 7,
127
+ "style_prefix": "cinematic 35mm film, soft golden hour light, shallow depth of field",
128
+ "style_category": "None",
129
+ "style_preset": "None",
130
+ "use_characters": ["A"],
131
+ "cast_rules": "",
132
+ "scenes": [
133
+ {
134
+ "prompt": "Wide establishing shot of a quiet hilltop at dawn, mist over the valley below.",
135
+ "act": "opening"
136
+ },
137
+ {
138
+ "prompt": "Elena walks into frame from the left, looks out over the valley, wind in her coat.",
139
+ "character_slots": ["A"],
140
+ "act": "rising"
141
+ },
142
+ {
143
+ "prompt": "Close-up on Elena's face, soft side light, expression of quiet resolve, eyes catching the last warm light.",
144
+ "character_slots": ["A"],
145
+ "act": "turn"
146
+ }
147
+ ]
148
+ }
149
+
150
+ What that example demonstrates concretely:
151
+ - Each scene's prompt is ~15-25 words: subject + action + framing +
152
+ one beat of mood. Well inside the ${SCENE_PROMPT_MAX_WORDS}-word cap.
153
+ - The cast member is referenced by NAME ("Elena"), not by slot id.
154
+ The slot id only appears in scenes[].character_slots, never in prose.
155
+ - Scene 1 omits character_slots entirely because nothing in the prompt
156
+ references a character. Do NOT send character_slots:[] either; omit
157
+ the field. (The example.yaml puts ["A"] on every shot; both forms
158
+ are accepted, but omitting is clearer for empty-frame shots.)
159
+ - No appearance details in any scene prompt (no "red hair, green coat",
160
+ no "weathered older man"). The cast bible carries those; the renderer
161
+ prepends a LoRA trigger that injects the appearance vector for you.
162
+ - No style language in any scene prompt: "cinematic", "35mm",
163
+ "golden hour" all live in style_prefix and are prepended once per
164
+ shot. Repeating them inside a scene double-applies them.
165
+ - style_category and style_preset are literal "None" strings (never
166
+ null, never empty) because the renderer disables on the string.
167
+ - act values are lowercase: "opening", "rising", "turn", "climax",
168
+ "resolution". Each scene optionally tagged.
169
+
170
+ Return ONLY the JSON object. Nothing before it. Nothing after it.`;
171
+ }
172
+ // User-side message that carries the brief plus the cast bible. The model
173
+ // uses the cast to populate use_characters and per-scene character_slots.
174
+ // Characters are sorted by slot id so the same input always renders the
175
+ // same prompt, which keeps planner cache hits stable.
176
+ export function buildPlanningUserMessage(brief, characters, beatBlock) {
177
+ const sorted = [...characters].sort((a, b) => a.slot.localeCompare(b.slot));
178
+ const castLines = sorted.length === 0
179
+ ? ["(none)"]
180
+ : sorted.map((c) => `${c.slot}) ${c.name}: ${c.bible}`);
181
+ const parts = [
182
+ "BRIEF:",
183
+ brief.trim(),
184
+ "",
185
+ "CAST LOADED FOR THIS RENDER:",
186
+ ...castLines,
187
+ "",
188
+ ];
189
+ // Beat-synced timing block (built by beat-timing.buildBeatTimingBlock when
190
+ // the request carried an audio beat plan). It pins the shot count, so it
191
+ // goes BEFORE the final instruction the model acts on.
192
+ if (beatBlock && beatBlock.trim().length > 0) {
193
+ parts.push(beatBlock.trim(), "");
194
+ }
195
+ parts.push("Plan the storyboard and return the JSON now.");
196
+ return parts.join("\n");
197
+ }
198
+ // ---------- Refinement chat (v0.50.0) ----------
199
+ //
200
+ // The plan route is single-shot: brief in, storyboard out. /api/storyboard/refine
201
+ // is the iterative path: take the current storyboard plus a free-form user
202
+ // message ("make scene 2 darker", "add a fight before the ending", etc.) and
203
+ // return a new storyboard with the change applied. Each request is stateless
204
+ // from the model's perspective: the current storyboard is the entire state,
205
+ // and the user message is the delta to apply. The frontend keeps a chat
206
+ // history for display but does not replay it to the model; the assumption is
207
+ // that the storyboard already reflects all prior accepted changes.
208
+ export function buildRefinementSystemPrompt() {
209
+ return `You are refining an existing storyboard for a music-video / short-film AI
210
+ pipeline. The user will request specific changes (add a scene, rewrite a
211
+ prompt, shorten a shot, swap which character appears, etc.). Apply EXACTLY
212
+ the requested change and PRESERVE everything else unchanged. Return ONE
213
+ JSON object matching the same schema the planner uses:
214
+
215
+ {
216
+ "title": string,
217
+ "full_prompt": string,
218
+ "duration_seconds": number,
219
+ "clip_seconds": number,
220
+ "style_prefix": string,
221
+ "style_category": string,
222
+ "style_preset": string,
223
+ "use_characters": ["A" | "B" | "C" | "D", ...],
224
+ "cast_rules": string,
225
+ "scenes": [
226
+ {
227
+ "prompt": string,
228
+ "character_slots": ["A" | "B" | "C" | "D", ...],
229
+ "act": string,
230
+ "start": number,
231
+ "end": number,
232
+ "target_seconds": number,
233
+ "dialogue": { "slot": "A" | "B" | "C" | "D", "text": string }
234
+ },
235
+ ...
236
+ ]
237
+ }
238
+
239
+ REFINEMENT RULES:
240
+ - If the user is silent about a field, KEEP THE OLD VALUE BIT-FOR-BIT.
241
+ Do not paraphrase prompts the user did not ask you to touch. Do not
242
+ re-tune target_seconds the user did not mention. Stability matters.
243
+ - If the user asks for a new scene, place it at the position they request
244
+ ("before the ending", "after scene 2", "first"); when ambiguous, append.
245
+ - If the user asks to delete a scene, remove the entry; preserve the order
246
+ of the remaining scenes.
247
+ - character_slots on each scene must be a subset of use_characters. If
248
+ the user adds a new character or removes one, also update use_characters.
249
+ - dialogue: KEEP each scene's existing dialogue bit-for-bit unless the user
250
+ asks to change it. When you do edit or add a line, dialogue.slot must be
251
+ one of that scene's character_slots, "text" is plain spoken words (no
252
+ quotes, no speaker prefix), and at most ${DIALOGUE_MAX_CHARS} characters.
253
+ - style_prefix carries ALL style language. Do not add style words to
254
+ individual scene prompts; the renderer prepends style_prefix to every
255
+ scene at manifest-build time, so style words inside a scene double-apply.
256
+ - style_category and style_preset are "None" literal strings unless the
257
+ user explicitly names a category / preset. Never null. Never empty.
258
+
259
+ LENGTH CAPS (same caps as the planner; the renderer rejects outputs
260
+ over these because they overflow SDXL's CLIP 77-token text encoder):
261
+ - Each scenes[].prompt: at most ${SCENE_PROMPT_MAX_WORDS} words. If the
262
+ user asks you to add detail to a scene, tighten the existing wording
263
+ rather than letting the word count drift past the cap.
264
+ - style_prefix: at most ${STYLE_PREFIX_MAX_CHARS} characters. If the user
265
+ asks to expand the style, compress earlier clauses to make room.
266
+ - full_prompt: at most ${FULL_PROMPT_MAX_CHARS} characters.
267
+ - scenes array length: at most ${STORYBOARD_MAX_SCENES} entries. If the
268
+ user asks for more shots than the cap allows, add as many as fit
269
+ under the cap and keep the remaining requested scenes for a future
270
+ refinement turn; never let the array exceed the cap.
271
+
272
+ CANONICAL SHAPE (any new or edited scene must match this style;
273
+ mirrors the renderer's storyboard.example.yaml):
274
+
275
+ {
276
+ "scenes": [
277
+ {
278
+ "prompt": "Wide establishing shot of a quiet hilltop at dawn, mist over the valley below.",
279
+ "act": "opening"
280
+ },
281
+ {
282
+ "prompt": "Elena walks into frame from the left, looks out over the valley, wind in her coat.",
283
+ "character_slots": ["A"],
284
+ "act": "rising"
285
+ },
286
+ {
287
+ "prompt": "Close-up on Elena's face, soft side light, expression of quiet resolve, eyes catching the last warm light.",
288
+ "character_slots": ["A"],
289
+ "act": "turn"
290
+ }
291
+ ]
292
+ }
293
+
294
+ What that example demonstrates:
295
+ - Each prompt is ~15-25 words; subject + action + framing + one beat
296
+ of mood. No style language, no appearance descriptors.
297
+ - Cast referenced by NAME in prose; slot id only in character_slots.
298
+ - character_slots omitted entirely for empty-frame shots (don't send
299
+ an empty array).
300
+ - act tags lowercase: opening / rising / turn / climax / resolution.
301
+
302
+ Return ONLY the JSON object. No prose, no markdown, no fences.`;
303
+ }
304
+ export function buildRefinementUserMessage(currentStoryboard, message) {
305
+ return [
306
+ "CURRENT STORYBOARD:",
307
+ JSON.stringify(currentStoryboard, null, 2),
308
+ "",
309
+ "USER REQUEST:",
310
+ message.trim(),
311
+ "",
312
+ "Return the updated storyboard JSON now.",
313
+ ].join("\n");
314
+ }
315
+ // Pulls a JSON object out of a model completion. Handles:
316
+ // - bare JSON;
317
+ // - one or more ```json (or bare ```) code fences (prefers the LAST fence,
318
+ // because models often give an example block before the final answer);
319
+ // - prose before or after the JSON / fence.
320
+ // Returns the input unchanged when no JSON-looking content is found, so
321
+ // JSON.parse downstream surfaces a clear error.
322
+ export function stripJsonFences(raw) {
323
+ let s = raw.trim();
324
+ const fences = [...s.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)];
325
+ if (fences.length > 0) {
326
+ s = fences[fences.length - 1][1];
327
+ }
328
+ else {
329
+ const firstBrace = s.indexOf("{");
330
+ const lastBrace = s.lastIndexOf("}");
331
+ if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
332
+ s = s.slice(firstBrace, lastBrace + 1);
333
+ }
334
+ }
335
+ return s.trim();
336
+ }
337
+ //# sourceMappingURL=planner-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner-prompt.js","sourceRoot":"","sources":["../src/planner-prompt.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,EAAE;AACF,uEAAuE;AACvE,0EAA0E;AAC1E,qEAAqE;AACrE,oEAAoE;AAGpE,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,0BAA0B,CAAC;AAWlC,sEAAsE;AACtE,wEAAwE;AACxE,wEAAwE;AACxE,qEAAqE;AACrE,wEAAwE;AACxE,mEAAmE;AACnE,MAAM,UAAU,yBAAyB;IACvC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCAuF0B,sBAAsB;;qBAEpC,sBAAsB;;;2BAGhB,sBAAsB;;;0BAGvB,qBAAqB;;mCAEZ,qBAAqB;;;sCAGlB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sCAqClB,sBAAsB;;;;;;;;;;;;;;;;;;kEAkBM,CAAC;AACnE,CAAC;AAED,0EAA0E;AAC1E,0EAA0E;AAC1E,wEAAwE;AACxE,sDAAsD;AACtD,MAAM,UAAU,wBAAwB,CACtC,KAAa,EACb,UAA8B,EAC9B,SAAkB;IAElB,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5E,MAAM,SAAS,GACb,MAAM,CAAC,MAAM,KAAK,CAAC;QACjB,CAAC,CAAC,CAAC,QAAQ,CAAC;QACZ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG;QACZ,QAAQ;QACR,KAAK,CAAC,IAAI,EAAE;QACZ,EAAE;QACF,8BAA8B;QAC9B,GAAG,SAAS;QACZ,EAAE;KACH,CAAC;IACF,2EAA2E;IAC3E,yEAAyE;IACzE,uDAAuD;IACvD,IAAI,SAAS,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7C,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,kDAAkD;AAClD,EAAE;AACF,kFAAkF;AAClF,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,wEAAwE;AACxE,6EAA6E;AAC7E,mEAAmE;AAEnE,MAAM,UAAU,2BAA2B;IACzC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4CA2CmC,kBAAkB;;;;;;;;;kCAS5B,sBAAsB;;;0BAG9B,sBAAsB;;yBAEvB,qBAAqB;iCACb,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+DAmCS,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,iBAA0B,EAC1B,OAAe;IAEf,OAAO;QACL,qBAAqB;QACrB,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,EAAE;QACF,eAAe;QACf,OAAO,CAAC,IAAI,EAAE;QACd,EAAE;QACF,yCAAyC;KAC1C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,0DAA0D;AAC1D,iBAAiB;AACjB,6EAA6E;AAC7E,2EAA2E;AAC3E,8CAA8C;AAC9C,wEAAwE;AACxE,gDAAgD;AAChD,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,+BAA+B,CAAC,CAAC,CAAC;IAChE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;SAAM,CAAC;QACN,MAAM,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,UAAU,KAAK,CAAC,CAAC,IAAI,SAAS,KAAK,CAAC,CAAC,IAAI,SAAS,GAAG,UAAU,EAAE,CAAC;YACpE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;AAClB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { StoryboardValidated } from "./storyboard-validate.js";
2
+ export { parseShotDurations } from "./shot-durations-parse.js";
3
+ export interface ParsedBundleScene {
4
+ shot_id: string;
5
+ prompt: string;
6
+ seconds: number;
7
+ dialogue?: {
8
+ slot: string;
9
+ text: string;
10
+ };
11
+ }
12
+ /** Extract { shot_id, prompt, seconds } from an emitted storyboard.yaml. */
13
+ export declare function parseStoryboardScenes(yaml: string, defaultSeconds?: number): ParsedBundleScene[];
14
+ export declare function serializeStoryboardYaml(value: StoryboardValidated): string;
15
+ //# sourceMappingURL=planner-yaml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner-yaml.d.ts","sourceRoot":"","sources":["../src/planner-yaml.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAoD/D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAIhB,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,SAAI,GAAG,iBAAiB,EAAE,CA4D3F;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CA4B1E"}