@nodaro/prompts 1.39.0 → 1.41.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 (66) hide show
  1. package/dist/{chunk-XIWWDGT5.cjs → chunk-2EW5B7ZV.cjs} +7 -7
  2. package/dist/{chunk-XIWWDGT5.cjs.map → chunk-2EW5B7ZV.cjs.map} +1 -1
  3. package/dist/{chunk-22KZOAGL.cjs → chunk-C3NHNECH.cjs} +86 -48
  4. package/dist/chunk-C3NHNECH.cjs.map +1 -0
  5. package/dist/{chunk-XU54YJXQ.js → chunk-HQTHDTHN.js} +85 -49
  6. package/dist/chunk-HQTHDTHN.js.map +1 -0
  7. package/dist/{chunk-KUBZVWME.cjs → chunk-LXPKTI2K.cjs} +12 -5
  8. package/dist/chunk-LXPKTI2K.cjs.map +1 -0
  9. package/dist/{chunk-3INXNDV7.cjs → chunk-LZ53F5BN.cjs} +8 -8
  10. package/dist/chunk-LZ53F5BN.cjs.map +1 -0
  11. package/dist/{chunk-7XMTVYS5.js → chunk-N6VMPG5S.js} +12 -6
  12. package/dist/chunk-N6VMPG5S.js.map +1 -0
  13. package/dist/{chunk-XRLOMTBZ.js → chunk-QBUSJGV3.js} +84 -5
  14. package/dist/chunk-QBUSJGV3.js.map +1 -0
  15. package/dist/{chunk-6MPZHFIS.js → chunk-RB2F5JQF.js} +5 -5
  16. package/dist/chunk-RB2F5JQF.js.map +1 -0
  17. package/dist/{chunk-NMWKXAYA.cjs → chunk-XTOSNRWT.cjs} +84 -5
  18. package/dist/chunk-XTOSNRWT.cjs.map +1 -0
  19. package/dist/{chunk-Z3SZXWRU.js → chunk-YS6HOWMW.js} +3 -3
  20. package/dist/{chunk-Z3SZXWRU.js.map → chunk-YS6HOWMW.js.map} +1 -1
  21. package/dist/entries/assembly.cjs +30 -22
  22. package/dist/entries/assembly.d.cts +90 -34
  23. package/dist/entries/assembly.d.ts +90 -34
  24. package/dist/entries/assembly.js +1 -1
  25. package/dist/entries/doctrine.cjs +46 -46
  26. package/dist/entries/doctrine.js +1 -1
  27. package/dist/entries/presets.cjs +32 -32
  28. package/dist/entries/presets.d.cts +2 -0
  29. package/dist/entries/presets.d.ts +2 -0
  30. package/dist/entries/presets.js +2 -2
  31. package/dist/entries/prompt-fields.cjs +26 -26
  32. package/dist/entries/prompt-fields.d.cts +2 -1
  33. package/dist/entries/prompt-fields.d.ts +2 -1
  34. package/dist/entries/prompt-fields.js +2 -2
  35. package/dist/index.cjs +133 -125
  36. package/dist/index.d.cts +1 -1
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.js +5 -5
  39. package/package.json +2 -2
  40. package/src/__tests__/factory-presets-prototype-keys.test.ts +43 -0
  41. package/src/__tests__/frame-reference-slots.test.ts +43 -0
  42. package/src/__tests__/gemini-omni-inputs.test.ts +12 -10
  43. package/src/__tests__/provider-prompt-doctrine.test.ts +14 -0
  44. package/src/__tests__/veo-i2v-inputs.test.ts +16 -11
  45. package/src/entries/assembly.ts +1 -0
  46. package/src/factory-presets/index.ts +4 -1
  47. package/src/factory-presets/references.ts +2 -1
  48. package/src/frame-reference-slots.ts +92 -0
  49. package/src/gemini-omni-inputs.ts +23 -22
  50. package/src/node-prompt-fields.ts +5 -3
  51. package/src/own-entry.ts +11 -0
  52. package/src/prompt-wizard-categories.ts +6 -0
  53. package/src/provider-prompt-doctrine.ts +89 -3
  54. package/src/ref-binding.ts +28 -3
  55. package/src/resolve-prompt.ts +2 -1
  56. package/src/seedance-2-inputs.ts +15 -40
  57. package/src/veo-i2v-inputs.ts +24 -17
  58. package/src/video-reference-resolver.ts +1 -1
  59. package/dist/chunk-22KZOAGL.cjs.map +0 -1
  60. package/dist/chunk-3INXNDV7.cjs.map +0 -1
  61. package/dist/chunk-6MPZHFIS.js.map +0 -1
  62. package/dist/chunk-7XMTVYS5.js.map +0 -1
  63. package/dist/chunk-KUBZVWME.cjs.map +0 -1
  64. package/dist/chunk-NMWKXAYA.cjs.map +0 -1
  65. package/dist/chunk-XRLOMTBZ.js.map +0 -1
  66. package/dist/chunk-XU54YJXQ.js.map +0 -1
@@ -3,6 +3,12 @@ import { resolveNodeRefs } from '@nodaro/shared/workflow';
3
3
  import { readPromptAffixes } from '@nodaro/shared/nodes';
4
4
  import { SOCIAL_POST_NODE_TYPES } from '@nodaro/shared/social';
5
5
 
6
+ // src/own-entry.ts
7
+ function ownEntry(record, key) {
8
+ return key !== void 0 && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
9
+ }
10
+
11
+ // src/resolve-prompt.ts
6
12
  var present = (s) => typeof s === "string" && s.trim().length > 0;
7
13
  var rr = (s, m) => m.size > 0 ? resolveNodeRefs(s, m) : s;
8
14
  var GLUE_PUNCTUATION = /* @__PURE__ */ new Set([",", ".", ";", ":", "!", "?", ")"]);
@@ -74,7 +80,7 @@ function computeNodePrompt(nodeType, data, { override, wired, refMap, appendWire
74
80
  if (nodeType === "text-to-speech") {
75
81
  typed = data.textSource === "direct" || !present(wired) ? [data.directText] : [];
76
82
  } else {
77
- const fields = NODE_PROMPT_CANDIDATE_FIELDS[nodeType] ?? ["prompt"];
83
+ const fields = ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? ["prompt"];
78
84
  typed = fields.map((f) => data[f]);
79
85
  }
80
86
  return resolvePrompt({ override, typed, wired, refMap, appendWired, affixes: readPromptAffixes(data) });
@@ -164,7 +170,7 @@ var NODE_PROMPT_FIELDS = {
164
170
  "edit-plan": { prompt: "instructions", promptLabel: "Editing instructions", media: "video", inline: false }
165
171
  };
166
172
  function getPromptFields(nodeType) {
167
- return nodeType ? NODE_PROMPT_FIELDS[nodeType] : void 0;
173
+ return nodeType ? ownEntry(NODE_PROMPT_FIELDS, nodeType) : void 0;
168
174
  }
169
175
  function nodeHasPromptField(nodeType) {
170
176
  return getPromptFields(nodeType) !== void 0;
@@ -188,12 +194,12 @@ function promptFieldCarriesAffixes(nodeType, promptField) {
188
194
  const core = promptAffixCoreField(nodeType);
189
195
  if (core === void 0) return false;
190
196
  if (promptField === void 0) return getPromptFields(nodeType).prompt === core;
191
- return promptField === core || (NODE_PROMPT_CANDIDATE_FIELDS[nodeType] ?? []).includes(promptField);
197
+ return promptField === core || (ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? []).includes(promptField);
192
198
  }
193
199
  var PROMPT_AFFIX_NODE_TYPES = new Set(
194
200
  Object.entries(NODE_PROMPT_FIELDS).filter(([, s]) => s.affixes !== false).map(([t]) => t)
195
201
  );
196
202
 
197
- export { NODE_PROMPT_CANDIDATE_FIELDS, NODE_PROMPT_FIELDS, PROMPT_AFFIX_CORE_FIELD_OVERRIDES, PROMPT_AFFIX_NODE_TYPES, applyPromptAffixes, composeNegative, computeLlmChatFields, computeNodePrompt, computeScriptTopic, getPromptFields, getSnippetMedia, joinPromptParts, nodeHasInlinePrompt, nodeHasPromptField, nodeSupportsPromptAffixes, promptAffixCoreField, promptFieldCarriesAffixes, promptPartSeparator, resolvePrompt };
198
- //# sourceMappingURL=chunk-7XMTVYS5.js.map
199
- //# sourceMappingURL=chunk-7XMTVYS5.js.map
203
+ export { NODE_PROMPT_CANDIDATE_FIELDS, NODE_PROMPT_FIELDS, PROMPT_AFFIX_CORE_FIELD_OVERRIDES, PROMPT_AFFIX_NODE_TYPES, applyPromptAffixes, composeNegative, computeLlmChatFields, computeNodePrompt, computeScriptTopic, getPromptFields, getSnippetMedia, joinPromptParts, nodeHasInlinePrompt, nodeHasPromptField, nodeSupportsPromptAffixes, ownEntry, promptAffixCoreField, promptFieldCarriesAffixes, promptPartSeparator, resolvePrompt };
204
+ //# sourceMappingURL=chunk-N6VMPG5S.js.map
205
+ //# sourceMappingURL=chunk-N6VMPG5S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/own-entry.ts","../src/resolve-prompt.ts","../src/node-prompt-fields.ts"],"names":[],"mappings":"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","file":"chunk-N6VMPG5S.js","sourcesContent":["/**\n * A registry keyed by node type is a plain object, and the key is\n * caller-supplied (`?nodeType=` on a public route, a preset row's `node_type`,\n * workflow JSON). A plain `record[key] ?? fallback` reads an Object.prototype\n * member for `\"constructor\"`, `\"toString\"` or `\"__proto__\"` — a function or\n * the prototype itself, which the caller then maps or iterates and throws on.\n * This reads an OWN entry only, so such a name is \"no such type\".\n */\nexport function ownEntry<T>(record: Readonly<Record<string, T>>, key: string | undefined): T | undefined {\n return key !== undefined && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined\n}\n","import { resolveNodeRefs } from \"@nodaro/shared/workflow\"\nimport { readPromptAffixes, type PromptAffixes } from \"@nodaro/shared/nodes\"\nimport { SOCIAL_POST_NODE_TYPES } from \"@nodaro/shared/social\"\nimport { joinSentences } from \"./hint-join.js\"\nimport { ownEntry } from \"./own-entry.js\"\n\nexport interface ResolvePromptArgs {\n override?: string\n typed?: ReadonlyArray<string | undefined>\n wired?: string\n refMap: ReadonlyMap<string, string>\n /** Opt-in (generate-image / generate-video only): APPEND the wired\n * (connected-prompt) value to the TYPED base instead of treating wired as a\n * fallback, so a connected prompt auto-injects alongside the typed prompt. An\n * `override` (list fan-out item) still fully replaces — no wired append.\n * Off/undefined = exact legacy precedence (override > typed > wired) for every\n * other node type. */\n appendWired?: boolean\n /** Pre/post text wrapped around WHICHEVER core precedence produced (override,\n * typed, wired, or the appendWired combination). Applied last. */\n affixes?: PromptAffixes\n}\nconst present = (s?: string): s is string => typeof s === \"string\" && s.trim().length > 0\nconst rr = (s: string, m: ReadonlyMap<string, string>) => (m.size > 0 ? resolveNodeRefs(s, m) : s)\n\n// ---------------------------------------------------------------------------\n// Prompt affixes (pre/post text) — see docs/prompt-pre-post-text.md (join rule, empty-core, no-op guarantee)\n// ---------------------------------------------------------------------------\n\nconst GLUE_PUNCTUATION = new Set([\",\", \".\", \";\", \":\", \"!\", \"?\", \")\"])\n\n/** The ONE join rule between two non-blank prompt parts: a single space unless\n * the boundary already has whitespace on either side or the right part opens\n * with sentence punctuation. Shared by `joinPromptParts` and the editor's\n * Final-view segment builder so preview and run agree byte-for-byte. */\nexport function promptPartSeparator(left: string, right: string): \"\" | \" \" {\n if (left.length === 0 || right.length === 0) return \"\"\n if (/\\s$/.test(left) || /^\\s/.test(right)) return \"\"\n if (GLUE_PUNCTUATION.has(right[0])) return \"\"\n return \" \"\n}\n\n/** Join prompt parts with `promptPartSeparator`; blank parts are dropped,\n * non-blank parts are used verbatim (no trimming). */\nexport function joinPromptParts(parts: ReadonlyArray<string | undefined>): string {\n let out = \"\"\n for (const part of parts) {\n if (!present(part)) continue\n out = out.length === 0 ? part : out + promptPartSeparator(out, part) + part\n }\n return out\n}\n\n/**\n * Wrap `core` with the node's pre/post text. `{Label}` refs are resolved in the\n * AFFIXES only — the core arrives exactly as the caller's precedence produced it.\n * NO-OP GUARANTEE: with no non-blank affix the core is returned unchanged (same\n * reference) — every existing workflow stays byte-identical.\n * EMPTY CORE: a blank core with affixes yields the joined affixes alone.\n */\nexport function applyPromptAffixes(core: string, affixes: PromptAffixes | undefined, refMap: ReadonlyMap<string, string>): string\nexport function applyPromptAffixes(core: string | undefined, affixes: PromptAffixes | undefined, refMap: ReadonlyMap<string, string>): string | undefined\nexport function applyPromptAffixes(\n core: string | undefined,\n affixes: PromptAffixes | undefined,\n refMap: ReadonlyMap<string, string>,\n): string | undefined {\n const prefix = present(affixes?.prefix) ? rr(affixes!.prefix!, refMap) : undefined\n const suffix = present(affixes?.suffix) ? rr(affixes!.suffix!, refMap) : undefined\n if (prefix === undefined && suffix === undefined) return core\n return joinPromptParts([prefix, core, suffix])\n}\n\n/** SINGLE SOURCE OF TRUTH for prompt precedence across both DAG engines:\n * override (list fan-out) > first present typed candidate > wired > \"\".\n * \"present\" = non-empty after trim. {Label} refs are resolved on the chosen\n * branch via the shared resolveNodeRefs. With `appendWired`, the chosen base\n * AND the wired value are both emitted, joined as sentences (`joinSentences`:\n * \". \", or a space after one that already ends). Affixes wrap the result. */\nexport function resolvePrompt({ override, typed = [], wired, refMap, appendWired, affixes }: ResolvePromptArgs): string {\n return applyPromptAffixes(resolveCore({ override, typed, wired, refMap, appendWired }), affixes, refMap)\n}\n\nfunction resolveCore({ override, typed = [], wired, refMap, appendWired }: Omit<ResolvePromptArgs, \"affixes\">): string {\n // appendWired: a connected prompt APPENDS to the TYPED base. An `override`\n // (list fan-out item) still fully REPLACES — it never receives a wired append,\n // so per-item fan-out prompts are unchanged.\n if (appendWired && !present(override)) {\n const base = typed.find(present)\n return joinSentences([base, wired].filter(present).map((s) => rr(s, refMap)))\n }\n if (present(override)) return rr(override, refMap)\n for (const t of typed) if (present(t)) return rr(t, refMap)\n if (present(wired)) return rr(wired, refMap)\n return \"\"\n}\n\n/** Compose a final NEGATIVE prompt from a TYPED base + a WIRED (connected\n * negative-handle) value: both are emitted, joined as sentences (`joinSentences`, mirrors `appendWired`\n * for the positive prompt). Pure join — the caller resolves `{label}` refs on\n * the typed value first, and the wired value is already a resolved output that\n * the input-resolver has filtered (referenced / Inject-Negative-off dropped).\n * Generate-image / generate-video only; empty parts are dropped. */\nexport function composeNegative(typed?: string, wired?: string): string {\n return joinSentences([typed, wired])\n}\n\n/** Ordered typed-candidate fields per node type — the precedence source of\n * truth (NOT NODE_MAPPABLE_FIELDS, which is field-mapping eligibility, omits\n * video-retake, and orders llm-chat wrong). */\nexport const NODE_PROMPT_CANDIDATE_FIELDS: Readonly<Record<string, readonly string[]>> = {\n \"generate-image\": [\"prompt\"],\n // motionPrompt is here so generate-video re-typed to text-to-video keeps a\n // prompt stored in data.motionPrompt (the inline picker's legacy field). Safe\n // for a standalone text-to-video node — it never carries data.motionPrompt.\n \"text-to-video\": [\"prompt\", \"motionPrompt\"],\n \"video-to-video\": [\"prompt\"],\n \"generate-music\": [\"prompt\"],\n \"speech-to-video\": [\"prompt\"],\n \"cinematic-avatar\": [\"prompt\"],\n \"extend-video\": [\"prompt\"],\n \"video-retake\": [\"prompt\"],\n \"suno-replace-section\": [\"prompt\"],\n \"image-to-video\": [\"prompt\", \"motionPrompt\"],\n \"generate-video\": [\"prompt\", \"motionPrompt\"],\n \"text-to-audio\": [\"prompt\", \"text\"],\n // Social posts: the orchestrator + frontend executor match per-platform node\n // types (node.type is \"instagram-post\", \"telegram-post\", …), NOT a unified\n // \"social-publish\" type — so every platform needs its own [\"caption\"] entry.\n // Derived from the shared SOCIAL_POST_NODE_TYPES single source of truth so a\n // new platform can't drift out of the precedence map.\n ...Object.fromEntries(\n [...SOCIAL_POST_NODE_TYPES].map((t) => [t, [\"caption\"] as readonly string[]]),\n ),\n // Kept for any caller that passes the aggregate type instead of a platform.\n \"social-publish\": [\"caption\"],\n}\n\nexport interface ComputeNodePromptArgs {\n override?: string\n wired?: string\n refMap: ReadonlyMap<string, string>\n /** See ResolvePromptArgs.appendWired — generate-image / generate-video only. */\n appendWired?: boolean\n}\n/** Resolve a single-prompt node's final prompt (typed-primary). Both engines\n * call this so field-selection + precedence are structurally identical. */\nexport function computeNodePrompt(\n nodeType: string,\n data: Record<string, unknown>,\n { override, wired, refMap, appendWired }: ComputeNodePromptArgs,\n): string {\n let typed: ReadonlyArray<string | undefined>\n if (nodeType === \"text-to-speech\") {\n // data.text is a phantom field on TTS; only directText (gated) is real.\n //\n // The gate is a PREFERENCE, not a lock: when textSource is \"connected\" we\n // still fall back to typed text if nothing is wired. Writers flip the gate\n // (PromptFieldSpec.promptGate), but data reaches nodes from places no\n // writer touches — workflows saved before that fix, JSON imports, MCP\n // writes, templates — and there the text sat visible in the node while the\n // run failed with \"no text found\" (founder, 2026-08-14). Coerce rather\n // than reject, same principle as normalizeModelInput.\n typed =\n data.textSource === \"direct\" || !present(wired)\n ? [data.directText as string | undefined]\n : []\n } else {\n const fields = ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? [\"prompt\"]\n typed = fields.map((f) => data[f] as string | undefined)\n }\n return resolvePrompt({ override, typed, wired, refMap, appendWired, affixes: readPromptAffixes(data) })\n}\n\nexport interface LlmChatFieldArgs {\n override?: string\n wiredUserInput?: string\n wiredSystemPrompt?: string\n refMap: ReadonlyMap<string, string>\n}\n/** llm-chat resolves TWO independent fields. override applies to userInput only. */\nexport function computeLlmChatFields(\n data: Record<string, unknown>,\n { override, wiredUserInput, wiredSystemPrompt, refMap }: LlmChatFieldArgs,\n): { userInput: string; systemPrompt: string } {\n return {\n userInput: resolvePrompt({ override, typed: [data.userInput as string | undefined], wired: wiredUserInput, refMap, affixes: readPromptAffixes(data) }),\n systemPrompt: resolvePrompt({ typed: [data.systemPrompt as string | undefined], wired: wiredSystemPrompt, refMap }),\n }\n}\n\n/** generate-script's topic. Both engines call this, so a wired Text node reads\n * the same on a single-node run and a server run.\n *\n * Precedence is override (a list fan-out item) > wired > `data.prompt`. Wired\n * comes BEFORE the typed field, the reverse of `computeNodePrompt`, because\n * `data.prompt` has no editor: the panel's text field is the style guide. A\n * value there was written by the old connect-time `{Label}` auto-fill or by an\n * agent, and it is kept only as the fallback that saved workflows rely on. */\nexport function computeScriptTopic(\n data: Record<string, unknown>,\n { override, wired, refMap }: Omit<ComputeNodePromptArgs, \"appendWired\">,\n): string {\n const core = [override, wired, data.prompt as string | undefined].find(present)\n return applyPromptAffixes(core === undefined ? \"\" : rr(core, refMap), readPromptAffixes(data), refMap)\n}\n","/**\n * Single source of truth: which data field(s) hold a node's user-editable\n * prompt text. Drives the quick-edit Prompt modal so it can work generically\n * across every AI node without each call site hardcoding a field name.\n *\n * The registry LIVES HERE (`@nodaro/prompts`) so the backend orchestrator, the\n * `/v1/nodes` registry, the prompt-affix totality tests and the docs tooling\n * read the SAME list the editor does. `frontend/src/lib/prompt-fields.ts` is a\n * pure re-export of this module — frontend code keeps importing\n * `@/lib/prompt-fields` unchanged.\n *\n * Most nodes store the prompt in `data.prompt`, but several don't\n * (`text-prompt` → `text`, `image-to-text` → `customPrompt`, …) — this map is\n * where that knowledge lives, once.\n *\n * INVARIANT: every node that exposes a user-editable prompt MUST have an entry\n * here, or the quick-edit modal silently does nothing for it. A guard test\n * (`prompt-fields.test.ts`) keeps this in sync with the node set.\n *\n * This module is intentionally pure data (no React/lucide imports) so it can be\n * pulled into the app-runtime bundle and any test without dragging in the icon\n * library. The string `icon` kind is mapped to a concrete lucide component in\n * `prompt-edit-button.tsx` (`getPromptIcon`), the only place that renders it.\n */\n\nimport type { SnippetMedia } from \"./factory-snippets/types.js\"\nimport { NODE_PROMPT_CANDIDATE_FIELDS } from \"./resolve-prompt.js\"\nimport { ownEntry } from \"./own-entry.js\"\n\n/** Which lucide glyph a node's prompt affordance uses. Kept as a string so this\n * module stays icon-library-free; mapped to a component in the strip button. */\nexport type PromptIconKind = \"pencil\" | \"paintbrush\"\n\nexport interface PromptFieldSpec {\n /** Data key holding the primary prompt (e.g. \"prompt\", \"text\", \"customPrompt\"). */\n readonly prompt: string\n /** Data key holding the negative prompt, when the node supports one. */\n readonly negative?: string\n /** Label override for the primary field (defaults to \"Prompt\"). */\n readonly promptLabel?: string\n /** Icon for the prompt affordance (strip button + modal title). Defaults to a\n * pencil; image-editing nodes use a paintbrush to read as \"edit\". */\n readonly icon?: PromptIconKind\n /** Node modality for prompt-snippet scoping — drives which snippet pool the\n * \"/\" menu and Snippets button show for this node's prompt fields. REQUIRED\n * so a new node cannot forget to declare it (compile error). */\n readonly media: SnippetMedia\n /** True when this node renders a media-result preview body and therefore\n * participates in inline-prompt mode (the `InlineNodePrompt` editor on the\n * node face, centralized in `BaseNode`). REQUIRED (like `media`) so a new\n * prompt node must consciously choose — compile error if omitted. The set of\n * `inline: true` types is guarded in `prompt-fields.test.ts`. */\n readonly inline: boolean\n /** Some nodes only READ their prompt field when a sibling discriminator\n * selects it — TTS keeps `directText` but resolves it only while\n * `textSource === \"direct\"`, defaulting to `\"connected\"`. Writing the text\n * without flipping the discriminator saved the value and then failed with\n * \"no text found\" (founder hit it live, 2026-08-14). Declare the pair here\n * and every writer flips it automatically. */\n readonly promptGate?: { readonly field: string; readonly value: string }\n /** Prompt pre/post text (`promptPrefix` / `promptSuffix`) support. Defaults to\n * ON for every prompt node; set `false` ONLY for a node whose \"prompt\" is not\n * a model prompt (the plain Text input node). Read via `nodeSupportsPromptAffixes`. */\n readonly affixes?: false\n}\n\nexport const NODE_PROMPT_FIELDS: Readonly<Record<string, PromptFieldSpec>> = {\n // ── Image ──\n // (`edit-image` / `image-to-image` are legacy types consolidated into\n // `modify-image`; they're not in the creatable node set, so a node of that\n // type never mounts and needs no entry here. The guard test enforces that.)\n \"generate-image\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"image\", inline: true },\n \"modify-image\": { prompt: \"prompt\", negative: \"negativePrompt\", icon: \"paintbrush\", media: \"image\", inline: true },\n \"generate-mask\": { prompt: \"prompt\", promptLabel: \"What to mask\", media: \"image\", inline: true },\n // ── Video ──\n \"generate-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n // Trimmed multi-segment stitch sibling of generate-video — no negativePrompt field.\n \"generate-video-pro\": { prompt: \"prompt\", media: \"video\", inline: true },\n // Span-replace sibling of generate-video-pro — no negativePrompt field.\n \"edit-video-pro\": { prompt: \"prompt\", media: \"video\", inline: true },\n \"text-to-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n \"image-to-video\": { prompt: \"prompt\", negative: \"negativePrompt\", promptLabel: \"Motion prompt\", media: \"video\", inline: true },\n \"video-to-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n \"switchx\": { prompt: \"prompt\", promptLabel: \"Look prompt\", media: \"video\", inline: true },\n \"extend-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n \"speech-to-video\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n \"motion-transfer\": { prompt: \"prompt\", negative: \"negativePrompt\", media: \"video\", inline: true },\n // Cinematic Avatar (HeyGen) — generative prompt (NOT a verbatim script),\n // so it participates in the quick-edit Prompt modal like other AI video nodes.\n \"cinematic-avatar\": { prompt: \"prompt\", media: \"video\", inline: true },\n \"video-sfx\": { prompt: \"prompt\", negative: \"negativePrompt\", promptLabel: \"Sound prompt\", media: \"audio\", inline: true },\n \"video-retake\": { prompt: \"prompt\", promptLabel: \"Retake prompt\", media: \"video\", inline: true },\n // Video Mask (SAM 3) — the subject to track, like Generate Mask's \"what to mask\".\n \"video-mask\": { prompt: \"prompt\", promptLabel: \"What to mask\", media: \"video\", inline: true },\n // ── Audio / music ──\n \"generate-music\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"suno-generate\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"text-to-audio\": { prompt: \"prompt\", media: \"audio\", inline: true },\n // ── Text / LLM (no media-result preview body → no inline editor) ──\n \"text-prompt\": { prompt: \"text\", promptLabel: \"Text\", media: \"text\", inline: false, affixes: false },\n \"image-to-text\": { prompt: \"customPrompt\", promptLabel: \"Question\", media: \"text\", inline: false },\n \"llm-chat\": { prompt: \"userInput\", promptLabel: \"Prompt\", media: \"text\", inline: false },\n // ── Speech / voice ──\n \"text-to-speech\": { prompt: \"directText\", promptLabel: \"Text\", media: \"audio\", inline: true, promptGate: { field: \"textSource\", value: \"direct\" } },\n \"voice-design\": { prompt: \"voiceDescription\", promptLabel: \"Voice description\", media: \"audio\", inline: true },\n \"voice-remix\": { prompt: \"voiceDescription\", promptLabel: \"Voice description\", media: \"audio\", inline: true },\n \"lip-sync\": { prompt: \"prompt\", media: \"audio\", inline: true },\n // ── Suno (music) ──\n \"suno-cover\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"suno-extend\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"suno-replace-section\": { prompt: \"prompt\", media: \"audio\", inline: true },\n \"suno-upload-extend\": { prompt: \"prompt\", media: \"audio\", inline: true },\n // NOTE: `suno-add-vocals` is intentionally absent — its node has no\n // user-editable prompt (SunoAddVocalsData declares only `model`; the config\n // panel + backend route take taskId/audioId/model, never a prompt). A stale\n // entry here rendered a phantom Prompt editor in the quick-edit modal that\n // wrote to a `data.prompt` key nothing ever reads (the dead-field class the\n // guard test now catches via defaultData ownership).\n \"suno-lyrics\": { prompt: \"prompt\", media: \"audio\", inline: false },\n \"suno-style-boost\": { prompt: \"content\", promptLabel: \"Style\", media: \"audio\", inline: false },\n // ── Composition / FX (compact, no media-result preview → no inline editor) ──\n \"image-critic\": { prompt: \"prompt\", promptLabel: \"Criteria\", media: \"image\", inline: false },\n \"motion-graphics\": { prompt: \"motionPrompt\", promptLabel: \"Motion prompt\", media: \"video\", inline: false },\n \"pro-3d-render\": { prompt: \"scenePrompt\", promptLabel: \"Scene\", media: \"text\", inline: false },\n \"generate-3d-scene\": { prompt: \"scenePrompt\", promptLabel: \"Scene\", media: \"text\", inline: false },\n \"edit-3d-scene\": { prompt: \"editPrompt\", promptLabel: \"Edit instruction\", media: \"text\", inline: false },\n \"3d-title\": { prompt: \"titlePrompt\", promptLabel: \"Title\", media: \"text\", inline: false },\n // ── Script / alignment (their primary text field) ──\n \"generate-script\": { prompt: \"styleGuide\", promptLabel: \"Style guide\", media: \"text\", inline: false },\n \"forced-alignment\": { prompt: \"transcript\", promptLabel: \"Transcript\", media: \"audio\", inline: false },\n // Video Analysis — its focus hint is the editable prompt (renders a JSON scene\n // table, not a media preview → no inline editor).\n \"video-analysis\": { prompt: \"analysisFocus\", promptLabel: \"Analysis focus\", media: \"video\", inline: false },\n // Edit Plan (podcast editing) — its free-text editing instructions are the\n // editable prompt; the affixes wrap them. Emits an EDL (JSON), not a media\n // preview, so no inline editor.\n \"edit-plan\": { prompt: \"instructions\", promptLabel: \"Editing instructions\", media: \"video\", inline: false },\n}\n\n/** The prompt-field spec for a node type, or undefined if it has none (an\n * Object.prototype member name such as `constructor` has none). */\nexport function getPromptFields(nodeType: string | undefined): PromptFieldSpec | undefined {\n return nodeType ? ownEntry(NODE_PROMPT_FIELDS, nodeType) : undefined\n}\n\n/** True when this node type has a registered, quick-editable prompt field. */\nexport function nodeHasPromptField(nodeType: string | undefined): boolean {\n return getPromptFields(nodeType) !== undefined\n}\n\n/** Snippet modality for a node type (drives the snippet pool), or undefined\n * when the node has no prompt field. */\nexport function getSnippetMedia(nodeType: string | undefined): SnippetMedia | undefined {\n return getPromptFields(nodeType)?.media\n}\n\n/** True when this node type renders the inline on-node prompt editor — the\n * media-preview nodes (image/video/audio result body). Single source for\n * BaseNode's centralized `InlineNodePrompt` rendering and the gold nodes'\n * `showInline` derivation (`useInlinePromptActive`). */\nexport function nodeHasInlinePrompt(nodeType: string | undefined): boolean {\n return getPromptFields(nodeType)?.inline === true\n}\n\n/** True when the node type carries `promptPrefix` / `promptSuffix` — every\n * registered prompt node except explicit opt-outs (`affixes: false`). The single\n * predicate the config panel, Final view, `/v1/nodes` registry, totality tests\n * and docs tooling all gate on. */\nexport function nodeSupportsPromptAffixes(nodeType: string | undefined): boolean {\n const spec = getPromptFields(nodeType)\n return spec !== undefined && spec.affixes !== false\n}\n\n/**\n * Where the RUN-TIME prompt lives when it differs from the editor's prompt\n * field (spec §7). `generate-script`'s editable prompt is its `styleGuide`, but\n * the run wraps the topic `prompt` with the affixes — so previewing the style\n * guide must NOT show them.\n */\nexport const PROMPT_AFFIX_CORE_FIELD_OVERRIDES: Readonly<Record<string, string>> = { \"generate-script\": \"prompt\" }\n\n/** The data key whose value the run wraps with promptPrefix/promptSuffix, or\n * undefined when the node has no affixes. */\nexport function promptAffixCoreField(nodeType: string | undefined): string | undefined {\n if (!nodeSupportsPromptAffixes(nodeType)) return undefined\n return PROMPT_AFFIX_CORE_FIELD_OVERRIDES[nodeType!] ?? getPromptFields(nodeType)!.prompt\n}\n\n/**\n * True when a preview of `promptField` on this node must show the affixes —\n * i.e. that key is what the run actually wraps. THE gate for the editor's Final\n * view, so a sibling field (llm-chat's `systemPrompt`, generate-script's\n * `styleGuide`) never renders a wrap the run doesn't perform.\n *\n * Two ways a key qualifies:\n * - it IS the core field ({@link promptAffixCoreField}), or\n * - it is a run-time FALLBACK candidate for the type\n * ({@link NODE_PROMPT_CANDIDATE_FIELDS}) — `computeNodePrompt` picks the\n * first present candidate and wraps THAT, so i2v's legacy `motionPrompt`\n * genuinely receives the affixes when `data.prompt` is empty.\n *\n * The two rules never collide: no override type declares candidates (guarded in\n * `node-prompt-fields.test.ts`).\n *\n * `undefined` means \"the node's primary prompt field\" — the common call.\n */\nexport function promptFieldCarriesAffixes(nodeType: string | undefined, promptField: string | undefined): boolean {\n const core = promptAffixCoreField(nodeType)\n if (core === undefined) return false\n if (promptField === undefined) return getPromptFields(nodeType)!.prompt === core\n return promptField === core || (ownEntry(NODE_PROMPT_CANDIDATE_FIELDS, nodeType) ?? []).includes(promptField)\n}\n\n/** The affix-capable node types, derived from the registry (never hand-listed). */\nexport const PROMPT_AFFIX_NODE_TYPES: ReadonlySet<string> = new Set(\n Object.entries(NODE_PROMPT_FIELDS).filter(([, s]) => s.affixes !== false).map(([t]) => t),\n)\n"]}
@@ -285,7 +285,7 @@ var GEMINI_OMNI_DOCTRINE = {
285
285
  "Multimodal Google video with native audio: text-to-video, image-to-video, and video-edit through the same prompt surface. 4/6/8/10s; 720p/1080p or 4K tier.",
286
286
  "Structure like the platform default: subject \u2192 action \u2192 scene \u2192 lighting \u2192 camera \u2192 style. Quote dialogue lines to have them spoken; describe SFX/ambience plainly in the prompt.",
287
287
  "Text-to-video REQUIRES a concrete aspect ratio (the API hard-rejects a missing one); image runs infer aspect from the input.",
288
- "Reference images ride along as additional imageUrls \u2014 bind them in the prompt ('the woman from the first image'). Video-edit: wire a source clip and describe the change, not the whole scene."
288
+ "Reference images ride in image_urls, your references first \u2014 bind them by ordinal ('the woman from @image_1'); a wired start frame is appended after them and bound as the opening frame. Video-edit: describe the change."
289
289
  ],
290
290
  doctrine: `Prompt structure (no public Google prompt guide exists for the Omni video endpoint \u2014
291
291
  the API contract is the doctrine source, like MiniMax H3; structure guidance mirrors the
@@ -294,7 +294,7 @@ subject \u2192 action \u2192 scene/environment \u2192 lighting \u2192 camera mov
294
294
 
295
295
  **Modes (picked from the wired inputs)**
296
296
  - Nothing visual \u2192 text-to-video. A concrete aspect ratio is REQUIRED \u2014 the API hard-rejects a missing one (Nodaro sends the node's ratio; there is no adaptive).
297
- - Image(s) wired \u2192 image-to-video: the first image anchors the scene; extra images are references \u2014 bind each in the prompt ("the woman from the first image", "the interior from the second image").
297
+ - Image(s) wired \u2192 image-to-video. Your reference images come FIRST, in your order (\`@image_1\`, \`@image_2\`, \u2026) \u2014 bind each in the prompt ("the woman from @image_1", "the interior from @image_2"); a wired start frame is appended after them and the platform names it as the opening frame.
298
298
  - Source video wired \u2192 video-edit (served through the same handle): describe the CHANGE ("replace the daylight with dusk, keep the motion and framing"), not a full re-description.
299
299
 
300
300
  **Audio (native)**
@@ -323,6 +323,7 @@ descriptions of static/unchanged parts \u2014 spend the words on what MOVES.
323
323
  **Image-to-video (the primary mode)**
324
324
  - The input image is the FIRST FRAME, not a loose reference: composition, subject identity, and visual style carry over. Describe motion and camera only ("she turns toward the window as the camera slowly pushes in"); re-describing the still wastes adherence.
325
325
  - grok-imagine-video-1.5 accepts up to 7 images (identity/scene references beyond the first frame); at 1080p only ONE image is allowed.
326
+ - grok-i2v with reference images: images are addressed \`@image(n)\` in array order. Your references come first (\`@image(1)\` is your first reference); a wired start frame is appended after them and the platform names it as the opening frame.
326
327
 
327
328
  **Audio (video-1.5)**
328
329
  - Native audio generates with the clip \u2014 background music, SFX, and lip-synced dialogue. Quote the spoken line; describe the music/SFX plainly. There is no audio toggle on the KIE contract \u2014 cue (or exclude) sound in the prompt text.
@@ -411,6 +412,76 @@ subject \u2192 action \u2192 scene/environment \u2192 lighting \u2192 camera mov
411
412
  Source: KIE Wan 3.0 market contract (docs.kie.ai/market/wan/3-0-video,
412
413
  docs.kie.ai/market/wan/3-0-video-prime). Captured 2026-09-01.`
413
414
  };
415
+ var VIDU_Q4_DOCTRINE = {
416
+ providers: ["vidu-q4"],
417
+ heading: "Vidu Q4 (vidu-q4)",
418
+ tips: [
419
+ "A start frame alone animates that frame (prompt optional). Any reference image or audio switches to reference mode: the prompt is REQUIRED and the start frame joins the reference images.",
420
+ 'Reference mode: 1-15 images, up to 3 audio clips (MP3 only, 3-12s each). Name each by ordinal \u2014 "the man from @image_2 says \u2026 in the voice of @audio_1".',
421
+ "No text-only runs and no end frame: wire a start frame or at least one reference image.",
422
+ "3-16s (default 5); 540p / 720p (default) / 1080p / 2K / 4K; reference runs take 16:9 / 9:16 / 1:1 / 4:3 / 3:4. Prompt cap 20,000 chars.",
423
+ "`audio` is ON by default (synchronized sound). Cue the sound you want or state the exclusion; dialogue is not verified here, so plan key lines as a TTS + lip-sync pass."
424
+ ],
425
+ doctrine: `Prompt structure (no public Vidu Q4 prompt guide is captured \u2014 the KIE API contract is the
426
+ doctrine source; platform-standard structure applies):
427
+ subject \u2192 action \u2192 scene/environment \u2192 lighting \u2192 camera movement \u2192 style \u2192 constraints.
428
+
429
+ **Modes (picked by what is wired)**
430
+ - Start frame only \u2192 image-to-video. The frame anchors the shot and its aspect ratio; describe MOTION and camera, not the still. The prompt is optional.
431
+ - Any reference image or reference audio \u2192 reference-to-video. The prompt is REQUIRED. A wired start frame cannot be sent as a frame here, so the platform adds it to the reference images (after the caller's own, ordinals unchanged) and names it in the prompt as the opening frame.
432
+ - There is no text-only mode and no first+last frame mode.
433
+
434
+ **Reference binding**
435
+ - Refer to inputs by ordinal in attachment order. In the editor the reference tokens emit "@image_N" / "@audio_N" \u2014 write the binding explicitly ("the woman from @image_1 turns to the man from @image_2 and says \u2026, in the voice of @audio_1").
436
+ - Caps: 1-15 reference images (JPG / PNG / WEBP) and 0-3 reference audio clips. Audio must be MP3, each 3-12 seconds \u2014 the platform refuses any other format or length before the run starts.
437
+
438
+ **Duration, resolution, aspect**
439
+ - 3-16 seconds (default 5). 540p / 720p (default) / 1080p / 2K / 4K.
440
+ - Image-to-video has no aspect setting \u2014 the output inherits the start frame's shape. Reference runs take 16:9 (default) / 9:16 / 1:1 / 4:3 / 3:4.
441
+ - Prompts up to 20,000 characters.
442
+
443
+ **Audio**
444
+ - The "audio" boolean defaults ON and generates synchronized sound with the clip (the contract says "dialogue and sound effects"). Describe the soundscape plainly or state the exclusion, or turn the toggle off for a silent clip.
445
+
446
+ Source: KIE Vidu Q4 market contracts (docs.kie.ai/market/vidu-q4/image-to-video,
447
+ docs.kie.ai/market/vidu-q4/reference-to-video). Captured 2026-10-17.`
448
+ };
449
+ var KLING_4_DOCTRINE = {
450
+ providers: ["kling-4", "kling-4-keyframes"],
451
+ heading: "Kling 4.0 (kling-4, kling-4-keyframes)",
452
+ tips: [
453
+ "kling-4: text only, or up to 10 images, 5 videos (2-30s), 7 characters and 3 voices (15 in all). Name each: @image_1, @video_1, a character's @Name, a voice clip @audio_1.",
454
+ "kling-4-keyframes: start frame, up to 8 in-between frames (Image References, in order), end frame \u2014 @first_frame, @keyframe_1 \u2026, @last_frame. 3s takes 4 frames in all, 9s or longer 10.",
455
+ "3-30s (default 5), 720p (default) / 1080p, native audio always on. A reference video lends its camera moves and pacing; keep-original-sound keeps its own audio.",
456
+ "Prompt cap 8,000 chars. Aspect auto / 16:9 / 9:16 / 1:1 / 21:9 \u2014 Keyframes follows the start frame's shape once a frame is attached."
457
+ ],
458
+ doctrine: `Prompt structure (no public Kling 4.0 prompt guide is captured \u2014 the KIE API contract is the
459
+ doctrine source; platform-standard structure applies):
460
+ subject \u2192 action \u2192 scene/environment \u2192 lighting \u2192 camera movement \u2192 style \u2192 constraints.
461
+
462
+ **Two models**
463
+ - Kling 4.0 (kling-4) \u2014 reference-to-video. With no materials it is plain text-to-video. Materials: up to 10 reference images, 5 reference videos (2-30 seconds each), 7 subjects (characters) and 3 voices, at most 15 together. A wired start frame cannot be a frame here, so the platform adds it to the reference images and names it as the opening frame.
464
+ - Kling 4.0 Keyframes (kling-4-keyframes) \u2014 keyframe-to-video. A start frame, up to 8 in-between frames in time order, and an end frame; the story passes through each. With no frame it is text-to-video.
465
+
466
+ **Naming materials in the prompt**
467
+ - Every material is named with "@": @image_1 \u2026 for reference images, @video_1 \u2026 for reference videos, a character by its own name (@Maya), a voice clip as @audio_1 (sent to Kling as a named voice). Keyframes: @first_frame, @keyframe_1 \u2026 @keyframe_8, @last_frame.
468
+ - Kling refuses a prompt that names a material the request does not carry; the platform removes the "@" from such a mention so the run still starts \u2014 wire the material if the mention matters.
469
+ - Write the binding explicitly: "@Maya walks through the street from @image_1 and greets the camera in the voice of @audio_1".
470
+
471
+ **Keyframe rules**
472
+ - Total frames (start + in-between + end) are capped by duration: 3s \u2192 4, 4s \u2192 5, 5s \u2192 6, 6s \u2192 7, 7s \u2192 8, 8s \u2192 9, 9s or longer \u2192 10. Describe how the shot moves from one frame to the next.
473
+ - Once any frame is attached the output takes the start frame's shape; aspect ratio applies to text-only runs.
474
+
475
+ **Reference videos**
476
+ - A reference video lends its camera moves and pacing \u2014 swap the characters or products in the prompt ("recreate the camera move of @video_1 with \u2026"). Keep original sound keeps the clip's own audio; otherwise new sound is generated.
477
+
478
+ **Duration, resolution, audio**
479
+ - 3-30 seconds (default 5); 720p (default) or 1080p; aspect auto (follows the materials, 16:9 with none) / 16:9 / 9:16 / 1:1 / 21:9. Prompts up to 8,000 characters.
480
+ - Native audio is always generated (the contract cites lip-sync and dialogue in 10+ languages); not yet verified for scripted lines here, so plan key dialogue as a TTS + lip-sync pass.
481
+
482
+ Source: KIE Kling 4.0 market contracts (docs.kie.ai/market/kling/v4-reference-to-video,
483
+ docs.kie.ai/market/kling/v4-keyframe-to-video), live-probed 2026-10-23.`
484
+ };
414
485
  var HAPPYHORSE_DOCTRINE = {
415
486
  providers: ["happyhorse", "happyhorse-i2v", "happyhorse-ref2v", "happyhorse-edit"],
416
487
  heading: "HappyHorse 1.1 (happyhorse, happyhorse-i2v, happyhorse-ref2v)",
@@ -427,7 +498,7 @@ subject \u2192 action \u2192 scene/environment \u2192 lighting \u2192 camera mov
427
498
  **Contract facts (KIE, per-mode pages)**
428
499
  - Prompts: any language, up to 5000 non-Chinese / 2500 Chinese characters \u2014 excess is TRUNCATED silently, so put the load-bearing content first.
429
500
  - Duration 3-15s (default 5), billed per second. Resolution 720p / 1080p (default). Aspect 16:9 (default) / 9:16 / 1:1 / 4:3 / 3:4.
430
- - Modes: text-to-video (happyhorse), image-to-video (happyhorse-i2v), reference-to-video (happyhorse-ref2v) \u2014 ref2v preserves wired identities; name each reference in the prompt so the binding is explicit.
501
+ - Modes: text-to-video (happyhorse), image-to-video (happyhorse-i2v), reference-to-video (happyhorse-ref2v) \u2014 ref2v preserves wired identities; name each reference in the prompt so the binding is explicit, as \`[Image N]\` in array order: your references come first (\`[Image 1]\` is your first), and a wired start frame is appended after them and named as the opening frame.
431
502
 
432
503
  **Style guidance (platform-standard, honestly generic)**
433
504
  - One camera movement per shot; physical, quantified action ("slowly raises a hand") over abstract emotion words; state exclusions ("no on-screen text, no watermark") in the prompt.
@@ -467,6 +538,8 @@ var PROVIDER_PROMPT_DOCTRINES = [
467
538
  GROK_IMAGINE_DOCTRINE,
468
539
  WAN_DOCTRINE,
469
540
  WAN_3_DOCTRINE,
541
+ VIDU_Q4_DOCTRINE,
542
+ KLING_4_DOCTRINE,
470
543
  HAPPYHORSE_DOCTRINE,
471
544
  RUNWAY_KIE_DOCTRINE
472
545
  ];
@@ -803,6 +876,9 @@ var PROVIDER_CAPABILITIES = {
803
876
  "gemini-omni-flash": "Google Gemini Omni Flash \u2014 faster, cheaper Omni tier; multimodal video with native audio, 4\u201310s, up to 4K.",
804
877
  "wan-3": "Wan 3.0 \u2014 multimodal refs (10 images / 5 videos / 5 audio) or first+last frame, native audio, 2\u201330s, 480p/720p/1080p",
805
878
  "wan-3-prime": "Wan 3.0 Prime \u2014 high-speed Wan 3.0 tier; same surface, faster turnaround at a higher rate",
879
+ "vidu-q4": "Vidu Q4 \u2014 start frame or up to 15 reference images + 3 MP3 audio refs, native audio, 3\u201316s, 540p\u20134K (image required)",
880
+ "kling-4": "Kling 4.0 \u2014 text, or up to 10 reference images / 5 videos / 7 characters / 3 voices; native audio, 3\u201330s, 720p/1080p",
881
+ "kling-4-keyframes": "Kling 4.0 Keyframes \u2014 start frame, up to 8 in-between frames and an end frame; native audio, 3\u201330s, 720p/1080p",
806
882
  "grok-imagine-video-1.5": "Grok Imagine 1.5 \u2014 image-to-video only; requires an input image"
807
883
  },
808
884
  "image-to-video": {
@@ -839,6 +915,9 @@ var PROVIDER_CAPABILITIES = {
839
915
  "gemini-omni-flash": "Google Gemini Omni Flash \u2014 faster, cheaper Omni tier; multimodal video with native audio, 4\u201310s, up to 4K.",
840
916
  "wan-3": "Wan 3.0 \u2014 multimodal refs (10 images / 5 videos / 5 audio) or first+last frame, native audio, 2\u201330s, 480p/720p/1080p",
841
917
  "wan-3-prime": "Wan 3.0 Prime \u2014 high-speed Wan 3.0 tier; same surface, faster turnaround at a higher rate",
918
+ "vidu-q4": "Vidu Q4 \u2014 animate a start frame, or direct up to 15 reference images + 3 MP3 audio refs; native audio, 3\u201316s, 540p\u20134K",
919
+ "kling-4": "Kling 4.0 \u2014 a start frame joins up to 10 reference images, 5 videos, 7 characters and 3 voices; native audio, 3\u201330s, 720p/1080p",
920
+ "kling-4-keyframes": "Kling 4.0 Keyframes \u2014 start frame, up to 8 in-between frames and an end frame; native audio, 3\u201330s, 720p/1080p",
842
921
  "grok-imagine-video-1.5": "Grok Imagine 1.5 \u2014 stylized animation, 1\u201315s, 480p/720p (image required)"
843
922
  },
844
923
  "video-to-video": {
@@ -954,5 +1033,5 @@ Analyst focus from the user: ${input.focus}`);
954
1033
  }
955
1034
 
956
1035
  export { AD_CREATIVE_ANALYSIS_FIELDS, AD_CREATIVE_ANALYSIS_SYSTEM_PROMPT, AUDIO_WIZARD_CATEGORIES, DEFAULT_OWN_MOTION, IMAGE_REFERENCE_PROMPT_DOCTRINE, IMAGE_WIZARD_CATEGORIES, LLM_CHAT_WIZARD_CATEGORIES, MUSIC_WIZARD_CATEGORIES, OWN_MOTION_NODE_TYPES, POST_CONTENT_ANALYSIS_SYSTEM_PROMPT, PROVIDER_CAPABILITIES, PROVIDER_PROMPT_DOCTRINES, REFERENCE_IMAGE_ROLES, SCENE3D_FIGURE_REFERENCE_RULE, SCENE3D_FREE_PLATE_SLOTS, SCENE3D_LAYOUT_REFERENCE_SCOPING_FIXTURE, SCENE3D_LAYOUT_REFERENCE_SCOPING_LINE, SCENE3D_LAYOUT_SCOPING_FOR, SCENE3D_LAYOUT_SCOPING_IGNORE, SCENE3D_LAYOUT_SCOPING_LOOK_SOURCE, SCENE3D_LAYOUT_SCOPING_MARKER, SCENE3D_UNREFERENCED_FIGURES_WARNING_CODE, TEXT_WIZARD_CATEGORIES, VIDEO_WIZARD_CATEGORIES, appendScene3DStillScopingLines, buildAdCreativeAnalysisUserText, buildCharacterPrompt, buildCreaturePrompt, buildFaceTemplateInputs, buildLocationMotionPrompt, buildLocationPrompt, buildLocationRefinePrompt, buildMotionPrompt, buildObjectMotionPrompt, buildObjectPrompt, buildScene3DLayoutScopingLine, buildScene3DUnreferencedFiguresWarning, getCategoriesForNodeType, getPromptDoctrine, getPromptTips, hasScene3DLayoutScopingLine, isWizardSupported, ownMotionHint, renderScene3DLayoutScopingLine, scene3DLayoutVideoCaptions };
957
- //# sourceMappingURL=chunk-XRLOMTBZ.js.map
958
- //# sourceMappingURL=chunk-XRLOMTBZ.js.map
1036
+ //# sourceMappingURL=chunk-QBUSJGV3.js.map
1037
+ //# sourceMappingURL=chunk-QBUSJGV3.js.map