vidspotai-shared 1.0.95 → 1.0.97

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 (143) hide show
  1. package/lib/globals/aiModels/enums.d.ts +3 -0
  2. package/lib/globals/aiModels/enums.d.ts.map +1 -1
  3. package/lib/globals/aiModels/enums.js +9 -0
  4. package/lib/globals/aiModels/index.d.ts.map +1 -1
  5. package/lib/globals/aiModels/index.js +2 -0
  6. package/lib/globals/aiModels/providers/hostedAvatars.d.ts +22 -0
  7. package/lib/globals/aiModels/providers/hostedAvatars.d.ts.map +1 -0
  8. package/lib/globals/aiModels/providers/hostedAvatars.js +129 -0
  9. package/lib/globals/aiModels/providers/kling.d.ts.map +1 -1
  10. package/lib/globals/aiModels/providers/kling.js +85 -1
  11. package/lib/globals/creditUnit.d.ts +29 -0
  12. package/lib/globals/creditUnit.d.ts.map +1 -1
  13. package/lib/globals/creditUnit.js +30 -1
  14. package/lib/globals/types.d.ts +58 -1
  15. package/lib/globals/types.d.ts.map +1 -1
  16. package/lib/globals/types.js +61 -1
  17. package/lib/models/demoVideoProject.model.d.ts +244 -0
  18. package/lib/models/demoVideoProject.model.d.ts.map +1 -0
  19. package/lib/models/demoVideoProject.model.js +2 -0
  20. package/lib/models/githubConnection.model.d.ts +35 -0
  21. package/lib/models/githubConnection.model.d.ts.map +1 -0
  22. package/lib/models/githubConnection.model.js +2 -0
  23. package/lib/models/index.d.ts +3 -0
  24. package/lib/models/index.d.ts.map +1 -1
  25. package/lib/models/index.js +3 -0
  26. package/lib/models/outreach.model.d.ts +175 -0
  27. package/lib/models/outreach.model.d.ts.map +1 -0
  28. package/lib/models/outreach.model.js +2 -0
  29. package/lib/models/video.model.d.ts +4 -0
  30. package/lib/models/video.model.d.ts.map +1 -1
  31. package/lib/schemas/brandKit.schema.d.ts +4 -0
  32. package/lib/schemas/brandKit.schema.d.ts.map +1 -1
  33. package/lib/schemas/brandKit.schema.js +10 -0
  34. package/lib/schemas/demoQuestion.schema.d.ts +286 -0
  35. package/lib/schemas/demoQuestion.schema.d.ts.map +1 -0
  36. package/lib/schemas/demoQuestion.schema.js +210 -0
  37. package/lib/schemas/demoSpec.schema.d.ts +109 -6
  38. package/lib/schemas/demoSpec.schema.d.ts.map +1 -1
  39. package/lib/schemas/demoSpec.schema.js +95 -7
  40. package/lib/schemas/index.d.ts +1 -0
  41. package/lib/schemas/index.d.ts.map +1 -1
  42. package/lib/schemas/index.js +1 -0
  43. package/lib/services/agent/providerFallback/chains.d.ts.map +1 -1
  44. package/lib/services/agent/providerFallback/chains.js +9 -1
  45. package/lib/services/agent/taskPoller.d.ts.map +1 -1
  46. package/lib/services/agent/taskPoller.js +17 -6
  47. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts +21 -9
  48. package/lib/services/agent/tools/generateAvatarVideo.tool.d.ts.map +1 -1
  49. package/lib/services/agent/tools/generateAvatarVideo.tool.js +135 -39
  50. package/lib/services/aiGen/aiGenFactory.service.d.ts +1 -1
  51. package/lib/services/aiGen/aiGenFactory.service.d.ts.map +1 -1
  52. package/lib/services/aiGen/aiGenFactory.service.js +11 -2
  53. package/lib/services/aiGen/providers/kling/kling.service.d.ts +36 -1
  54. package/lib/services/aiGen/providers/kling/kling.service.d.ts.map +1 -1
  55. package/lib/services/aiGen/providers/kling/kling.service.js +62 -5
  56. package/lib/services/aiGen/providers/kling/klingCredits.d.ts.map +1 -1
  57. package/lib/services/aiGen/providers/kling/klingCredits.js +12 -0
  58. package/lib/services/aiGen/providers/kling/klingRequests.d.ts +14 -1
  59. package/lib/services/aiGen/providers/kling/klingRequests.d.ts.map +1 -1
  60. package/lib/services/aiGen/providers/kling/klingRequests.js +44 -0
  61. package/lib/services/aiGen/providers/kling/klingStatus.d.ts +13 -1
  62. package/lib/services/aiGen/providers/kling/klingStatus.d.ts.map +1 -1
  63. package/lib/services/aiGen/providers/kling/klingStatus.js +111 -0
  64. package/lib/services/aiGen/providers/kling/types.d.ts +71 -0
  65. package/lib/services/aiGen/providers/kling/types.d.ts.map +1 -1
  66. package/lib/services/avatarGen/IAvatarVideoProvider.d.ts +136 -0
  67. package/lib/services/avatarGen/IAvatarVideoProvider.d.ts.map +1 -0
  68. package/lib/services/avatarGen/IAvatarVideoProvider.js +42 -0
  69. package/lib/services/avatarGen/avatarProviderFactory.d.ts +18 -0
  70. package/lib/services/avatarGen/avatarProviderFactory.d.ts.map +1 -0
  71. package/lib/services/avatarGen/avatarProviderFactory.js +54 -0
  72. package/lib/services/avatarGen/index.d.ts +6 -0
  73. package/lib/services/avatarGen/index.d.ts.map +1 -0
  74. package/lib/services/avatarGen/index.js +21 -0
  75. package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts +45 -0
  76. package/lib/services/avatarGen/providers/heygenAvatarProvider.d.ts.map +1 -0
  77. package/lib/services/avatarGen/providers/heygenAvatarProvider.js +190 -0
  78. package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts +32 -0
  79. package/lib/services/avatarGen/providers/klingAvatarProvider.d.ts.map +1 -0
  80. package/lib/services/avatarGen/providers/klingAvatarProvider.js +69 -0
  81. package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts +56 -0
  82. package/lib/services/avatarGen/providers/tavusAvatarProvider.d.ts.map +1 -0
  83. package/lib/services/avatarGen/providers/tavusAvatarProvider.js +183 -0
  84. package/lib/services/demo/demoAuthor.d.ts +45 -0
  85. package/lib/services/demo/demoAuthor.d.ts.map +1 -0
  86. package/lib/services/demo/demoAuthor.js +75 -0
  87. package/lib/services/demo/demoAuthorNormalize.d.ts +49 -0
  88. package/lib/services/demo/demoAuthorNormalize.d.ts.map +1 -0
  89. package/lib/services/demo/demoAuthorNormalize.js +327 -0
  90. package/lib/services/demo/demoAuthorPrompt.d.ts +102 -0
  91. package/lib/services/demo/demoAuthorPrompt.d.ts.map +1 -0
  92. package/lib/services/demo/demoAuthorPrompt.js +243 -0
  93. package/lib/services/demo/demoCredits.d.ts +20 -0
  94. package/lib/services/demo/demoCredits.d.ts.map +1 -0
  95. package/lib/services/demo/demoCredits.js +51 -0
  96. package/lib/services/demo/demoMediaGuard.d.ts +43 -0
  97. package/lib/services/demo/demoMediaGuard.d.ts.map +1 -0
  98. package/lib/services/demo/demoMediaGuard.js +207 -0
  99. package/lib/services/demo/demoMusic.d.ts +13 -0
  100. package/lib/services/demo/demoMusic.d.ts.map +1 -0
  101. package/lib/services/demo/demoMusic.js +93 -0
  102. package/lib/services/demo/demoPrefill.d.ts +76 -0
  103. package/lib/services/demo/demoPrefill.d.ts.map +1 -0
  104. package/lib/services/demo/demoPrefill.js +435 -0
  105. package/lib/services/demo/demoRevisePrompt.d.ts +17 -0
  106. package/lib/services/demo/demoRevisePrompt.d.ts.map +1 -0
  107. package/lib/services/demo/demoRevisePrompt.js +85 -0
  108. package/lib/services/demo/demoReviser.d.ts +63 -0
  109. package/lib/services/demo/demoReviser.d.ts.map +1 -0
  110. package/lib/services/demo/demoReviser.js +81 -0
  111. package/lib/services/demo/demoScriptPrompt.d.ts +63 -0
  112. package/lib/services/demo/demoScriptPrompt.d.ts.map +1 -0
  113. package/lib/services/demo/demoScriptPrompt.js +234 -0
  114. package/lib/services/demo/demoScriptWriter.d.ts +65 -0
  115. package/lib/services/demo/demoScriptWriter.d.ts.map +1 -0
  116. package/lib/services/demo/demoScriptWriter.js +211 -0
  117. package/lib/services/demo/demoTriage.d.ts +65 -0
  118. package/lib/services/demo/demoTriage.d.ts.map +1 -0
  119. package/lib/services/demo/demoTriage.js +130 -0
  120. package/lib/services/demo/githubOAuth.d.ts +78 -0
  121. package/lib/services/demo/githubOAuth.d.ts.map +1 -0
  122. package/lib/services/demo/githubOAuth.js +209 -0
  123. package/lib/services/demo/githubOAuthState.d.ts +10 -0
  124. package/lib/services/demo/githubOAuthState.d.ts.map +1 -0
  125. package/lib/services/demo/githubOAuthState.js +63 -0
  126. package/lib/services/demo/index.d.ts +24 -0
  127. package/lib/services/demo/index.d.ts.map +1 -0
  128. package/lib/services/demo/index.js +39 -0
  129. package/lib/services/firestore.service.d.ts +11 -0
  130. package/lib/services/firestore.service.d.ts.map +1 -1
  131. package/lib/services/firestore.service.js +24 -0
  132. package/lib/services/index.d.ts +2 -0
  133. package/lib/services/index.d.ts.map +1 -1
  134. package/lib/services/index.js +2 -0
  135. package/lib/services/tts/providers/elevenlabs.service.d.ts.map +1 -1
  136. package/lib/services/tts/providers/elevenlabs.service.js +16 -8
  137. package/lib/utils/index.d.ts +1 -0
  138. package/lib/utils/index.d.ts.map +1 -1
  139. package/lib/utils/index.js +1 -0
  140. package/lib/utils/outreachTemplate.d.ts +46 -0
  141. package/lib/utils/outreachTemplate.d.ts.map +1 -0
  142. package/lib/utils/outreachTemplate.js +174 -0
  143. package/package.json +1 -1
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authorDemoSpec = authorDemoSpec;
4
+ const demoSpec_schema_1 = require("../../schemas/demoSpec.schema");
5
+ const llmCallerRegistry_1 = require("../agent/llmCallerRegistry");
6
+ const modelRouter_1 = require("../agent/modelRouter");
7
+ const demoAuthorPrompt_1 = require("./demoAuthorPrompt");
8
+ const demoAuthorNormalize_1 = require("./demoAuthorNormalize");
9
+ /**
10
+ * DemoSpec author — the "plan" stage of the SaaS Demo Video pipeline (plan
11
+ * Phase 3 / §4 stage 2).
12
+ *
13
+ * ONE Claude `.structured<DemoSpec>()` call turns the user's product facts +
14
+ * brand tokens + product screenshots (as vision) + wizard answers into a valid,
15
+ * render-ready DemoSpec that the Layer-A engine renders to MP4. This is the
16
+ * equivalent of the agent Planner (`bibleBuilder`/`planner`) but for the demo
17
+ * generator: provider-agnostic (injected `LlmCaller`), model-routed, and
18
+ * hardened by a deterministic normalize pass (see demoAuthorNormalize.ts) that
19
+ * enforces the invariants a prompt can only request.
20
+ *
21
+ * The orchestration worker (Phase 4) calls `authorDemoSpec` at the plan stage;
22
+ * the returned `spec` is persisted on `demoVideoProjects.demoSpec` and rendered.
23
+ */
24
+ /** Cap screenshots sent as vision (and thus citable) to bound token cost (plan §10). */
25
+ const MAX_VISION_SCREENSHOTS = 6;
26
+ /**
27
+ * Author a render-ready DemoSpec from inputs. Pure orchestration: build the
28
+ * prompt, attach screenshots as vision, call `.structured(DemoSpec)`, then
29
+ * normalize. Throws if the caller is unconfigured or the model returns an
30
+ * unparseable / scene-less spec (the normalize pass is the last guard).
31
+ */
32
+ async function authorDemoSpec(input, deps = {}) {
33
+ const llm = deps.llm ?? (0, llmCallerRegistry_1.getLlmCaller)();
34
+ const router = deps.router ?? new modelRouter_1.ModelRouter();
35
+ const model = router.pickFor("planner", { premium: deps.premium });
36
+ const fallbackModel = router.fallbackFor("planner");
37
+ // Cap the screenshot set BEFORE prompting so the citable list matches exactly
38
+ // what the model can see (never advertise a URL we didn't attach as vision).
39
+ const visionScreenshots = (input.screenshotUrls ?? []).slice(0, MAX_VISION_SCREENSHOTS);
40
+ const cappedInput = {
41
+ ...input,
42
+ screenshotUrls: visionScreenshots.length ? visionScreenshots : undefined,
43
+ };
44
+ const system = (0, demoAuthorPrompt_1.buildDemoAuthorSystemPrompt)({
45
+ allowCustomScenes: deps.allowCustomScenes,
46
+ allowGeneratedAssets: deps.allowGeneratedAssets,
47
+ });
48
+ const userText = (0, demoAuthorPrompt_1.buildDemoAuthorUserText)(cappedInput);
49
+ const result = await llm.structured({
50
+ model,
51
+ fallbackModel,
52
+ messages: [
53
+ { role: "system", content: system },
54
+ {
55
+ role: "user",
56
+ content: userText,
57
+ imageUrls: visionScreenshots.length ? visionScreenshots : undefined,
58
+ },
59
+ ],
60
+ schema: demoSpec_schema_1.DemoSpecSchema,
61
+ schemaName: "DemoSpec",
62
+ temperature: deps.temperature ?? 0.5,
63
+ maxTokens: deps.maxTokens ?? 8000,
64
+ });
65
+ // The caller already validated against DemoSpecSchema; parse again defensively
66
+ // to get a concrete typed value before the deterministic normalize pass.
67
+ const raw = demoSpec_schema_1.DemoSpecSchema.parse(result.data);
68
+ const normalized = (0, demoAuthorNormalize_1.normalizeDemoSpec)(raw, cappedInput);
69
+ return {
70
+ ...normalized,
71
+ modelUsed: result.usedFallback && fallbackModel ? fallbackModel : model,
72
+ usedFallback: result.usedFallback,
73
+ usage: result.usage,
74
+ };
75
+ }
@@ -0,0 +1,49 @@
1
+ import { DemoScene, DemoSpec } from "../../schemas/demoSpec.schema";
2
+ import { DemoAuthorInput } from "./demoAuthorPrompt";
3
+ /**
4
+ * Deterministic post-processing for the authored DemoSpec (plan Phase 3).
5
+ *
6
+ * The prompt REQUESTS these invariants; this pass GUARANTEES them, because an
7
+ * LLM can (and occasionally will) drift on exactly the things that break a
8
+ * render or the brand:
9
+ * 1. Wizard pins — any axis the user explicitly locked wins over the model's pick.
10
+ * 2. Screenshot URLs — the model may only cite real uploaded URLs; anything else
11
+ * is stripped (and screenshot-only scenes with no valid URL are dropped).
12
+ * 3. Brand colors — forced back to the user's tokens (copy is the model's, palette isn't).
13
+ * 4. Duration budget — total timeline scaled to land inside the target window.
14
+ * 5. Structural — >=1 scene, correct productName, schema-valid (defaults applied).
15
+ *
16
+ * Everything here is pure + typed (no `any`), so it's unit-testable without an LLM.
17
+ */
18
+ export interface NormalizeResult {
19
+ spec: DemoSpec;
20
+ /** Human-readable notes on what the normalize pass changed (for logs/observability). */
21
+ adjustments: string[];
22
+ }
23
+ /** Hard caps on planner-requested AI media per spec (cost + wall-clock bound). */
24
+ export declare const DEMO_GEN_ASSET_MAX_IMAGES = 4;
25
+ export declare const DEMO_GEN_ASSET_MAX_VIDEOS = 1;
26
+ /** Is this URL-slot value a reference to a declared generated asset? */
27
+ export declare function isGenAssetRef(url: string | undefined): url is string;
28
+ /** `gen://hero-shot` → `hero-shot` (undefined when not a gen ref). */
29
+ export declare function genAssetIdFromRef(url: string): string;
30
+ export interface GenAssetCitations {
31
+ /** Asset ids cited via `gen://<id>`. */
32
+ ids: Set<string>;
33
+ /** Raw string values cited in media slots — lets a READY asset that scenes
34
+ * now cite by its resolved URL still count as referenced. */
35
+ urls: Set<string>;
36
+ }
37
+ /** Every `gen://` ref AND every raw media string cited anywhere in a scene list
38
+ * (typed media slots + custom-scene prop bags, walked deeply so codegen props
39
+ * count as usage). */
40
+ export declare function collectGenAssetRefs(scenes: DemoScene[]): GenAssetCitations;
41
+ /**
42
+ * Run the full deterministic normalize pipeline on a raw authored spec and
43
+ * return a schema-valid, render-ready DemoSpec plus the list of adjustments.
44
+ * Throws only if the model produced something with zero scenes (unrecoverable).
45
+ */
46
+ export declare function normalizeDemoSpec(raw: DemoSpec, input: DemoAuthorInput): NormalizeResult;
47
+ /** Convenience — total timeline of a normalized spec (ms). */
48
+ export declare function specTotalMs(spec: DemoSpec): number;
49
+ //# sourceMappingURL=demoAuthorNormalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demoAuthorNormalize.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoAuthorNormalize.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,QAAQ,EAIT,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAmC,MAAM,oBAAoB,CAAC;AAEtF;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,CAAC;IACf,wFAAwF;IACxF,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AA6CD,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,IAAI,MAAM,CAEpE;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACjB;kEAC8D;IAC9D,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnB;AAED;;uBAEuB;AACvB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,iBAAiB,CA2C1E;AA0MD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,GAAG,eAAe,CAsExF;AAED,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAElD"}
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEMO_GEN_ASSET_MAX_VIDEOS = exports.DEMO_GEN_ASSET_MAX_IMAGES = void 0;
4
+ exports.isGenAssetRef = isGenAssetRef;
5
+ exports.genAssetIdFromRef = genAssetIdFromRef;
6
+ exports.collectGenAssetRefs = collectGenAssetRefs;
7
+ exports.normalizeDemoSpec = normalizeDemoSpec;
8
+ exports.specTotalMs = specTotalMs;
9
+ const demoSpec_schema_1 = require("../../schemas/demoSpec.schema");
10
+ const demoAuthorPrompt_1 = require("./demoAuthorPrompt");
11
+ // ─── 1. wizard pins ──────────────────────────────────────────────────────────
12
+ /** Overwrite style/narrative/aspect axes the user explicitly locked. */
13
+ function applyWizardPins(spec, answers, notes) {
14
+ if (!answers)
15
+ return spec;
16
+ const style = { ...spec.style };
17
+ let narrative = spec.narrative;
18
+ let aspect = spec.aspect;
19
+ if (answers.visualStyle && style.visual !== answers.visualStyle) {
20
+ notes.push(`visual: ${style.visual} → ${answers.visualStyle} (wizard pin)`);
21
+ style.visual = answers.visualStyle;
22
+ }
23
+ if (answers.pace && style.pace !== answers.pace) {
24
+ notes.push(`pace: ${style.pace} → ${answers.pace} (wizard pin)`);
25
+ style.pace = answers.pace;
26
+ }
27
+ if (answers.colorMood && style.colorMood !== answers.colorMood) {
28
+ notes.push(`colorMood: ${style.colorMood} → ${answers.colorMood} (wizard pin)`);
29
+ style.colorMood = answers.colorMood;
30
+ }
31
+ if (answers.audioIntent && style.audioIntent !== answers.audioIntent) {
32
+ notes.push(`audioIntent: ${style.audioIntent} → ${answers.audioIntent} (wizard pin)`);
33
+ style.audioIntent = answers.audioIntent;
34
+ }
35
+ if (answers.narrative && narrative !== answers.narrative) {
36
+ notes.push(`narrative: ${narrative} → ${answers.narrative} (wizard pin)`);
37
+ narrative = answers.narrative;
38
+ }
39
+ if (answers.aspect && aspect !== answers.aspect) {
40
+ notes.push(`aspect: ${aspect} → ${answers.aspect} (wizard pin)`);
41
+ aspect = answers.aspect;
42
+ }
43
+ return { ...spec, style, narrative, aspect };
44
+ }
45
+ // ─── 1.5 generated-asset requests (caps + reference accounting) ──────────────
46
+ /** Hard caps on planner-requested AI media per spec (cost + wall-clock bound). */
47
+ exports.DEMO_GEN_ASSET_MAX_IMAGES = 4;
48
+ exports.DEMO_GEN_ASSET_MAX_VIDEOS = 1;
49
+ /** Is this URL-slot value a reference to a declared generated asset? */
50
+ function isGenAssetRef(url) {
51
+ return !!url && url.startsWith(demoSpec_schema_1.GEN_ASSET_URI_PREFIX);
52
+ }
53
+ /** `gen://hero-shot` → `hero-shot` (undefined when not a gen ref). */
54
+ function genAssetIdFromRef(url) {
55
+ return url.slice(demoSpec_schema_1.GEN_ASSET_URI_PREFIX.length);
56
+ }
57
+ /** Every `gen://` ref AND every raw media string cited anywhere in a scene list
58
+ * (typed media slots + custom-scene prop bags, walked deeply so codegen props
59
+ * count as usage). */
60
+ function collectGenAssetRefs(scenes) {
61
+ const ids = new Set();
62
+ const urls = new Set();
63
+ const visit = (value, depth) => {
64
+ if (depth > 6 || value == null)
65
+ return;
66
+ if (typeof value === "string") {
67
+ if (isGenAssetRef(value))
68
+ ids.add(genAssetIdFromRef(value));
69
+ else if (value)
70
+ urls.add(value);
71
+ return;
72
+ }
73
+ if (Array.isArray(value)) {
74
+ for (const v of value)
75
+ visit(v, depth + 1);
76
+ return;
77
+ }
78
+ if (typeof value === "object") {
79
+ for (const v of Object.values(value)) {
80
+ visit(v, depth + 1);
81
+ }
82
+ }
83
+ };
84
+ for (const scene of scenes) {
85
+ switch (scene.type) {
86
+ case "deviceMockup":
87
+ scene.screenshotUrls.forEach((u) => visit(u, 0));
88
+ visit(scene.videoUrl, 0);
89
+ break;
90
+ case "featureShowcase":
91
+ case "splitFeature":
92
+ visit(scene.screenshotUrl, 0);
93
+ visit(scene.videoUrl, 0);
94
+ break;
95
+ case "mediaShot":
96
+ visit(scene.imageUrl, 0);
97
+ visit(scene.videoUrl, 0);
98
+ break;
99
+ case "custom":
100
+ visit(scene.props, 0);
101
+ break;
102
+ default:
103
+ break;
104
+ }
105
+ }
106
+ return { ids, urls };
107
+ }
108
+ /** Dedupe ids (first declaration wins) + enforce the per-kind caps. READY
109
+ * assets (already generated + paid for on a prior pass/revise) take cap
110
+ * priority over pending requests; within each group, declaration order wins —
111
+ * the planner is told to list by importance. Original order is preserved. */
112
+ function capGeneratedAssets(assets, notes) {
113
+ const seen = new Set();
114
+ const deduped = [];
115
+ for (const asset of assets) {
116
+ if (seen.has(asset.id)) {
117
+ notes.push(`generatedAssets — dropped duplicate id "${asset.id}"`);
118
+ continue;
119
+ }
120
+ seen.add(asset.id);
121
+ deduped.push(asset);
122
+ }
123
+ const kept = new Set();
124
+ const counts = { image: 0, video: 0 };
125
+ const caps = { image: exports.DEMO_GEN_ASSET_MAX_IMAGES, video: exports.DEMO_GEN_ASSET_MAX_VIDEOS };
126
+ const isReady = (a) => a.status === "ready" && !!a.url;
127
+ for (const asset of [...deduped.filter(isReady), ...deduped.filter((a) => !isReady(a))]) {
128
+ if (counts[asset.kind] >= caps[asset.kind]) {
129
+ notes.push(`generatedAssets — dropped ${asset.kind} "${asset.id}" (cap ${caps[asset.kind]})`);
130
+ continue;
131
+ }
132
+ counts[asset.kind]++;
133
+ kept.add(asset.id);
134
+ }
135
+ return deduped.filter((a) => kept.has(a.id));
136
+ }
137
+ function sanitizeScreenshots(scenes, allowed, notes) {
138
+ // Placement policy: deviceMockup + featureShowcase present media AS the
139
+ // product's real screen, so they accept REAL media only — a generated image
140
+ // there would fabricate product UI. splitFeature side visuals and mediaShot
141
+ // full-bleed moments may cite generated assets (that's what they're for).
142
+ const images = allowed.realImages;
143
+ const videos = allowed.realVideos;
144
+ const anyImages = new Set([...allowed.realImages, ...allowed.genImages]);
145
+ const anyVideos = new Set([...allowed.realVideos, ...allowed.genVideos]);
146
+ const isImage = (u) => !!u && images.has(u);
147
+ const isVideo = (u) => !!u && videos.has(u);
148
+ const isAnyImage = (u) => !!u && anyImages.has(u);
149
+ const isAnyVideo = (u) => !!u && anyVideos.has(u);
150
+ const out = [];
151
+ for (const scene of scenes) {
152
+ switch (scene.type) {
153
+ case "deviceMockup": {
154
+ const valid = scene.screenshotUrls.filter((u) => images.has(u));
155
+ const validVideo = isVideo(scene.videoUrl) ? scene.videoUrl : undefined;
156
+ if (valid.length === 0 && !validVideo) {
157
+ notes.push(`dropped deviceMockup "${scene.id}" — no valid media URLs`);
158
+ continue; // drop the scene entirely
159
+ }
160
+ if (valid.length !== scene.screenshotUrls.length) {
161
+ notes.push(`deviceMockup "${scene.id}" — removed ${scene.screenshotUrls.length - valid.length} invalid screenshot URL(s)`);
162
+ }
163
+ if (scene.videoUrl && !validVideo) {
164
+ notes.push(`deviceMockup "${scene.id}" — stripped invented videoUrl`);
165
+ }
166
+ out.push({
167
+ ...scene,
168
+ screenshotUrls: valid,
169
+ ...(validVideo ? { videoUrl: validVideo } : { videoUrl: undefined }),
170
+ });
171
+ break;
172
+ }
173
+ case "featureShowcase":
174
+ case "splitFeature": {
175
+ // splitFeature side visuals may cite generated assets; featureShowcase
176
+ // (media inside the fake-app surface = "the product's screen") may not.
177
+ const okImage = scene.type === "splitFeature" ? isAnyImage : isImage;
178
+ const okVideo = scene.type === "splitFeature" ? isAnyVideo : isVideo;
179
+ let next = scene;
180
+ if (scene.screenshotUrl && !okImage(scene.screenshotUrl)) {
181
+ notes.push(`${scene.type} "${scene.id}" — stripped invented screenshotUrl (falls back to coded surface)`);
182
+ const { screenshotUrl: _drop, ...rest } = next;
183
+ next = rest;
184
+ }
185
+ if (scene.videoUrl && !okVideo(scene.videoUrl)) {
186
+ notes.push(`${scene.type} "${scene.id}" — stripped invented videoUrl`);
187
+ const { videoUrl: _dropV, ...rest } = next;
188
+ next = rest;
189
+ }
190
+ out.push(next);
191
+ break;
192
+ }
193
+ case "mediaShot": {
194
+ // The full-bleed media home: real uploads AND generated assets both fine.
195
+ const validImage = isAnyImage(scene.imageUrl) ? scene.imageUrl : undefined;
196
+ const validVideo = isAnyVideo(scene.videoUrl) ? scene.videoUrl : undefined;
197
+ if (!validImage && !validVideo) {
198
+ notes.push(`dropped mediaShot "${scene.id}" — no valid media URLs`);
199
+ continue;
200
+ }
201
+ if (scene.imageUrl && !validImage) {
202
+ notes.push(`mediaShot "${scene.id}" — stripped invented imageUrl`);
203
+ }
204
+ if (scene.videoUrl && !validVideo) {
205
+ notes.push(`mediaShot "${scene.id}" — stripped invented videoUrl`);
206
+ }
207
+ out.push({ ...scene, imageUrl: validImage, videoUrl: validVideo });
208
+ break;
209
+ }
210
+ default:
211
+ out.push(scene);
212
+ }
213
+ }
214
+ return out;
215
+ }
216
+ // ─── 3. brand colors ─────────────────────────────────────────────────────────
217
+ /** Force the user's brand tokens back onto the spec (the model authors copy, not palette). */
218
+ function forceBrandColors(spec, input, notes) {
219
+ const given = input.brand.colors;
220
+ if (!given)
221
+ return spec;
222
+ const colors = { ...spec.brand.colors };
223
+ let changed = false;
224
+ if (given.primary && colors.primary !== given.primary) {
225
+ colors.primary = given.primary;
226
+ changed = true;
227
+ }
228
+ if (given.accent && colors.accent !== given.accent) {
229
+ colors.accent = given.accent;
230
+ changed = true;
231
+ }
232
+ if (given.palette?.length) {
233
+ colors.palette = given.palette;
234
+ }
235
+ if (changed)
236
+ notes.push(`brand colors forced to user tokens`);
237
+ return { ...spec, brand: { ...spec.brand, colors } };
238
+ }
239
+ // ─── 4. duration budget ──────────────────────────────────────────────────────
240
+ /**
241
+ * Scale every scene's duration by a single factor so the total lands inside the
242
+ * budget window. Proportional scaling preserves the author's intended pacing
243
+ * (a 4s hero beat stays ~2× a 2s cut). Only scales when the total is outside
244
+ * [min,max]; snaps to `target` when it does. Enforces a per-scene floor.
245
+ */
246
+ const MIN_SCENE_MS = 700;
247
+ function fitDuration(scenes, budget, notes) {
248
+ const total = scenes.reduce((acc, s) => acc + s.durationMs, 0);
249
+ if (total >= budget.min && total <= budget.max)
250
+ return scenes;
251
+ const factor = budget.target / Math.max(total, 1);
252
+ notes.push(`total duration ${Math.round(total / 1000)}s outside ${Math.round(budget.min / 1000)}–${Math.round(budget.max / 1000)}s → scaled ×${factor.toFixed(2)} to ~${Math.round(budget.target / 1000)}s`);
253
+ return scenes.map((s) => ({
254
+ ...s,
255
+ durationMs: Math.max(MIN_SCENE_MS, Math.round(s.durationMs * factor)),
256
+ }));
257
+ }
258
+ // ─── orchestration ───────────────────────────────────────────────────────────
259
+ /**
260
+ * Run the full deterministic normalize pipeline on a raw authored spec and
261
+ * return a schema-valid, render-ready DemoSpec plus the list of adjustments.
262
+ * Throws only if the model produced something with zero scenes (unrecoverable).
263
+ */
264
+ function normalizeDemoSpec(raw, input) {
265
+ const notes = [];
266
+ const budget = (0, demoAuthorPrompt_1.budgetForLength)(input.answers?.targetLength);
267
+ // productName is authoritative from the caller, never the model.
268
+ let spec = { ...raw, productName: input.productName };
269
+ spec = applyWizardPins(spec, input.answers, notes);
270
+ spec = forceBrandColors(spec, input, notes);
271
+ // Planner-requested AI media: dedupe + cap FIRST so refs to dropped requests
272
+ // are treated as invented URLs by the sanitizer below.
273
+ const generatedAssets = capGeneratedAssets(spec.generatedAssets ?? [], notes);
274
+ // Allowed media sets: images = uploaded/captured screenshots + image assets;
275
+ // videos = the user's video assets. Only these URLs may survive on scene props.
276
+ // `gen://<id>` refs are allowed iff they point at a surviving declared asset of
277
+ // the matching kind — the asset-generation stage resolves them to real URLs.
278
+ // Which slots accept gen refs is decided per scene type in sanitizeScreenshots.
279
+ const imageAssetUrls = (input.mediaAssets ?? [])
280
+ .filter((a) => a.kind === "image")
281
+ .map((a) => a.url);
282
+ // Ready assets' RESOLVED urls are also allowed (in the gen sets, so placement
283
+ // policy still holds): on revise, the incoming spec's scenes already carry the
284
+ // substituted GCS URLs, and stripping them as "invented" would throw away
285
+ // media we already paid for.
286
+ const readyGenUrls = (kind) => generatedAssets
287
+ .filter((a) => a.kind === kind && a.status === "ready" && !!a.url)
288
+ .map((a) => a.url);
289
+ const genRef = (a) => `${demoSpec_schema_1.GEN_ASSET_URI_PREFIX}${a.id}`;
290
+ const allowed = {
291
+ realImages: new Set([...(input.screenshotUrls ?? []), ...imageAssetUrls]),
292
+ realVideos: new Set((input.mediaAssets ?? []).filter((a) => a.kind === "video").map((a) => a.url)),
293
+ genImages: new Set([
294
+ ...generatedAssets.filter((a) => a.kind === "image").map(genRef),
295
+ ...readyGenUrls("image"),
296
+ ]),
297
+ genVideos: new Set([
298
+ ...generatedAssets.filter((a) => a.kind === "video").map(genRef),
299
+ ...readyGenUrls("video"),
300
+ ]),
301
+ };
302
+ let scenes = sanitizeScreenshots(spec.scenes, allowed, notes);
303
+ if (scenes.length === 0) {
304
+ throw new Error("normalizeDemoSpec: authored spec has no renderable scenes after media sanitation");
305
+ }
306
+ scenes = fitDuration(scenes, budget, notes);
307
+ // Drop asset requests nothing cites anymore (refs may have been stripped or
308
+ // their scene dropped) — an unreferenced request is pure provider spend. A
309
+ // ready asset cited by its RESOLVED url (post-materialization spec) counts.
310
+ const cited = collectGenAssetRefs(scenes);
311
+ const referencedAssets = generatedAssets.filter((a) => {
312
+ if (cited.ids.has(a.id))
313
+ return true;
314
+ if (a.status === "ready" && a.url && cited.urls.has(a.url))
315
+ return true;
316
+ notes.push(`generatedAssets — dropped unreferenced "${a.id}"`);
317
+ return false;
318
+ });
319
+ spec = { ...spec, scenes, generatedAssets: referencedAssets };
320
+ // Final schema pass applies all defaults + guarantees a valid contract.
321
+ const parsed = demoSpec_schema_1.DemoSpecSchema.parse(spec);
322
+ return { spec: parsed, adjustments: notes };
323
+ }
324
+ /** Convenience — total timeline of a normalized spec (ms). */
325
+ function specTotalMs(spec) {
326
+ return (0, demoSpec_schema_1.demoSpecDurationMs)(spec);
327
+ }
@@ -0,0 +1,102 @@
1
+ import { DemoBrand } from "../../schemas/demoSpec.schema";
2
+ import { IDemoClarifyAnswerValue, IDemoMediaAsset, IDemoVideoWizardAnswers } from "../../models/demoVideoProject.model";
3
+ /**
4
+ * Prompt construction for the SaaS Demo Video author (plan Phase 3 / §4 stage 2).
5
+ *
6
+ * The author is a single Claude `.structured<DemoSpec>()` call: it turns the
7
+ * user's product facts + brand tokens + product screenshots (as vision) + the
8
+ * clarifying-wizard answers into a valid, render-ready `DemoSpec` that the
9
+ * Layer-A scene-template engine renders to MP4 with zero manual effort.
10
+ *
11
+ * Kept separate from `demoAuthor.ts` because the system prompt is the largest,
12
+ * most-iterated surface — it encodes the scene vocabulary, the §2.6 style
13
+ * taxonomy, the anti-overfit rule, and the screenshot-vs-coded-chrome guidance.
14
+ * The prompt only DESCRIBES the contract; the zod schema is what actually
15
+ * constrains the output (passed as the tool `input_schema`), and a
16
+ * deterministic normalize pass (demoAuthor.ts) enforces the invariants a prompt
17
+ * can only request (wizard pins, real screenshot URLs, duration budget).
18
+ */
19
+ export interface DurationBudget {
20
+ min: number;
21
+ target: number;
22
+ max: number;
23
+ /** Rough scene-count guidance handed to the author for pacing. */
24
+ sceneHint: string;
25
+ }
26
+ export declare const DURATION_BUDGETS: Record<NonNullable<IDemoVideoWizardAnswers["targetLength"]>, DurationBudget>;
27
+ /** Default when the user left targetLength on "decide for me". */
28
+ export declare const DEFAULT_DURATION_BUDGET: DurationBudget;
29
+ export declare function budgetForLength(targetLength: IDemoVideoWizardAnswers["targetLength"] | undefined): DurationBudget;
30
+ /**
31
+ * Everything the author needs to write a DemoSpec. Product facts + resolved
32
+ * brand + the raw inputs (screenshots/website) + wizard answers. Brand colors
33
+ * are honored verbatim (never re-invented); copy + scene structure are authored.
34
+ */
35
+ export interface DemoAuthorInput {
36
+ /** Product display name — overlay text only, never AI-rendered into pixels. */
37
+ productName: string;
38
+ /** One-line value proposition, if the user gave one. */
39
+ tagline?: string;
40
+ /** Short product description / what it does. */
41
+ description?: string;
42
+ /** Feature labels to highlight (from the wizard "which features" multi-select). */
43
+ features?: string[];
44
+ /** Target-audience hint for script tone. */
45
+ audience?: string;
46
+ /** Resolved brand tokens (colors/fonts/identity). Colors are used as-is. */
47
+ brand: DemoBrand;
48
+ /** Real product screenshot URLs (GCS). Fed as vision AND the ONLY image URLs the author may cite. */
49
+ screenshotUrls?: string[];
50
+ /**
51
+ * Media the user wants FEATURED in the video (images + video clips). Unlike
52
+ * screenshots (reference the author MAY use), these SHOULD appear on the
53
+ * timeline. Video clips are placed via *.videoUrl scene props; image assets are
54
+ * merged into the screenshot set upstream. The author is told they exist + must
55
+ * surface them. See [[IDemoMediaAsset]].
56
+ */
57
+ mediaAssets?: IDemoMediaAsset[];
58
+ /** Product website, for context. The worker's gather stage fetches it (see `pageSummary`). */
59
+ websiteUrl?: string;
60
+ /**
61
+ * A compact, LLM-free summary of the product page's copy (title/description +
62
+ * visible-text lead), extracted by the worker during gather even when the user
63
+ * never clicked "Autofill". Steering context for the script — NOT rendered
64
+ * verbatim on screen. See [[gatherPageSignals]].
65
+ */
66
+ pageSummary?: string;
67
+ /** User's free-text notes about the repo/product — steering context, not rendered verbatim. */
68
+ repoNotes?: string;
69
+ /** Clarifying-wizard answers. Omitted fields === "decide for me" (author picks). */
70
+ answers?: IDemoVideoWizardAnswers;
71
+ /**
72
+ * Answers to the agent's DYNAMIC clarifying questions (triage/mid-run loop),
73
+ * keyed by question id. Authoritative steering context — the author should honor
74
+ * them. See [[DemoQuestion]].
75
+ */
76
+ clarifyAnswers?: Record<string, IDemoClarifyAnswerValue>;
77
+ }
78
+ /**
79
+ * The system prompt. Deliberately opinionated about quality (invented realistic
80
+ * domain data, cursor-synced walkthroughs, staggered reveals, on-brand color)
81
+ * and about the two hard reliability rules a prompt CAN enforce by instruction:
82
+ * never invent screenshot URLs, never render the product name as pixels.
83
+ */
84
+ export interface DemoAuthorPromptOpts {
85
+ /**
86
+ * Allow the planner to PROPOSE `custom` scenes (with an `intent` brief the
87
+ * Layer-B codegen agent later authors). Mirrors the worker's
88
+ * `DEMO_CODEGEN_ENABLED` gate — default false, so the prompt keeps the hard
89
+ * "no custom scenes" rule and behavior is unchanged until the flag is on.
90
+ */
91
+ allowCustomScenes?: boolean;
92
+ /**
93
+ * Allow the planner to COMMISSION AI-generated media (`generatedAssets` +
94
+ * `gen://` refs). Default true — the deterministic caps bound the cost; the
95
+ * worker passes false only under the DEMO_GEN_ASSETS_DISABLED kill-switch.
96
+ */
97
+ allowGeneratedAssets?: boolean;
98
+ }
99
+ export declare function buildDemoAuthorSystemPrompt(opts?: DemoAuthorPromptOpts): string;
100
+ /** Build the user-turn text (images are attached separately as imageUrls). */
101
+ export declare function buildDemoAuthorUserText(input: DemoAuthorInput): string;
102
+ //# sourceMappingURL=demoAuthorPrompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demoAuthorPrompt.d.ts","sourceRoot":"","sources":["../../../src/services/demo/demoAuthorPrompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,uBAAuB,EACxB,MAAM,qCAAqC,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AAMH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CACnC,WAAW,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC,EACpD,cAAc,CAQf,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,EAAE,cAA6C,CAAC;AAEpF,wBAAgB,eAAe,CAC7B,YAAY,EAAE,uBAAuB,CAAC,cAAc,CAAC,GAAG,SAAS,GAChE,cAAc,CAEhB;AAID;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,KAAK,EAAE,SAAS,CAAC;IACjB,qGAAqG;IACrG,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAClC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;CAC1D;AAwFD;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,2BAA2B,CAAC,IAAI,GAAE,oBAAyB,GAAG,MAAM,CA0CnF;AAmED,8EAA8E;AAC9E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CA+CtE"}