@koda-sl/baker-cli 0.199.0-dev.6d2f498f5 → 0.199.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 (28) hide show
  1. package/README.md +32 -2
  2. package/dist/{chunk-ZS6BR7CU.js → chunk-2R2DEPG7.js} +3 -3
  3. package/dist/{chunk-MMP7BRV7.js → chunk-5MPIOGRO.js} +259 -202
  4. package/dist/chunk-5MPIOGRO.js.map +1 -0
  5. package/dist/{chunk-6B5RAIAC.js → chunk-6NZG2TCM.js} +82 -4
  6. package/dist/chunk-6NZG2TCM.js.map +1 -0
  7. package/dist/{chunk-CVIGWKG2.js → chunk-KHHD26LZ.js} +3 -3
  8. package/dist/{chunk-VFRDAUQH.js → chunk-SVHFT7AN.js} +4 -4
  9. package/dist/{chunk-PGPHKRK4.js → chunk-UZYVSPCA.js} +2 -2
  10. package/dist/cli.js +5787 -5180
  11. package/dist/cli.js.map +1 -1
  12. package/dist/client-MP7BKFKM.js +15 -0
  13. package/dist/engine/index.js +3 -3
  14. package/dist/{env-S4LMU6EN.js → env-FF7ZB5W5.js} +2 -2
  15. package/dist/{output-VCUZ4EBX.js → output-YGPWST77.js} +5 -5
  16. package/dist/{shared-WG5FCIHH.js → shared-CRWB5B7P.js} +6 -6
  17. package/package.json +1 -1
  18. package/dist/chunk-6B5RAIAC.js.map +0 -1
  19. package/dist/chunk-MMP7BRV7.js.map +0 -1
  20. package/dist/client-BBH4AYOK.js +0 -15
  21. /package/dist/{chunk-ZS6BR7CU.js.map → chunk-2R2DEPG7.js.map} +0 -0
  22. /package/dist/{chunk-CVIGWKG2.js.map → chunk-KHHD26LZ.js.map} +0 -0
  23. /package/dist/{chunk-VFRDAUQH.js.map → chunk-SVHFT7AN.js.map} +0 -0
  24. /package/dist/{chunk-PGPHKRK4.js.map → chunk-UZYVSPCA.js.map} +0 -0
  25. /package/dist/{client-BBH4AYOK.js.map → client-MP7BKFKM.js.map} +0 -0
  26. /package/dist/{env-S4LMU6EN.js.map → env-FF7ZB5W5.js.map} +0 -0
  27. /package/dist/{output-VCUZ4EBX.js.map → output-YGPWST77.js.map} +0 -0
  28. /package/dist/{shared-WG5FCIHH.js.map → shared-CRWB5B7P.js.map} +0 -0
@@ -1,22 +1,23 @@
1
1
  import {
2
2
  debugLogHttp,
3
3
  readBodyForLog
4
- } from "./chunk-PGPHKRK4.js";
4
+ } from "./chunk-UZYVSPCA.js";
5
5
  import {
6
6
  __commonJS,
7
7
  __toESM,
8
8
  captureProxyCredentials,
9
9
  childEnvWith,
10
10
  isChallengeBody,
11
+ isPrivateAddress,
11
12
  isProxyFailure,
12
13
  plannedRoutes,
13
14
  refuseNonPublicUrl,
14
15
  shouldEscalate
15
- } from "./chunk-6B5RAIAC.js";
16
+ } from "./chunk-6NZG2TCM.js";
16
17
 
17
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js
18
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
18
19
  var require_safe_stable_stringify = __commonJS({
19
- "../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js"(exports, module) {
20
+ "../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
20
21
  "use strict";
21
22
  var { hasOwnProperty } = Object.prototype;
22
23
  var stringify = configure2();
@@ -1090,7 +1091,7 @@ function resolveAdaptFormats(params) {
1090
1091
  return params.formats ?? [];
1091
1092
  }
1092
1093
 
1093
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/esm/wrapper.js
1094
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
1094
1095
  var import__ = __toESM(require_safe_stable_stringify(), 1);
1095
1096
  var configure = import__.default.configure;
1096
1097
  var wrapper_default = import__.default;
@@ -4975,35 +4976,6 @@ function shouldSniffChallenge(contentType, contentLength, maxSniffBytes) {
4975
4976
  return type.includes("text/") || type.includes("html") || type.includes("json") || type.includes("xml");
4976
4977
  }
4977
4978
 
4978
- // ../proxy/src/privateAddress.ts
4979
- function isPrivateIpv4(ip) {
4980
- const parts = ip.split(".");
4981
- if (parts.length !== 4) return false;
4982
- const octets = parts.map((part) => Number(part));
4983
- if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) return false;
4984
- const [a, b] = octets;
4985
- if (a === 0) return true;
4986
- if (a === 10) return true;
4987
- if (a === 127) return true;
4988
- if (a === 169 && b === 254) return true;
4989
- if (a === 172 && b >= 16 && b <= 31) return true;
4990
- if (a === 192 && b === 168) return true;
4991
- if (a === 100 && b >= 64 && b <= 127) return true;
4992
- if (a >= 224) return true;
4993
- return false;
4994
- }
4995
- function isPrivateAddress(ip) {
4996
- const address = ip.trim().toLowerCase();
4997
- if (!address) return true;
4998
- const mapped = address.startsWith("::ffff:") ? address.slice("::ffff:".length) : null;
4999
- if (mapped?.includes(".")) return isPrivateIpv4(mapped);
5000
- if (address.includes(".")) return isPrivateIpv4(address);
5001
- if (address === "::" || address === "::1") return true;
5002
- if (/^f[cd][0-9a-f]{2}:/.test(address)) return true;
5003
- if (/^fe[89ab][0-9a-f]:/.test(address)) return true;
5004
- return false;
5005
- }
5006
-
5007
4979
  // ../proxy/src/fetch.ts
5008
4980
  var DEFAULT_TIMEOUT_MS = 6e4;
5009
4981
  var MAX_SNIFF_BYTES = 64 * 1024;
@@ -5037,6 +5009,39 @@ function directAgent() {
5037
5009
  });
5038
5010
  }
5039
5011
  var PRIVATE_ADDRESS_CODE = "EPRIVATEADDR";
5012
+ var MAX_REDIRECTS = 20;
5013
+ var REDIRECT_STATUS = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
5014
+ function originOf(url) {
5015
+ try {
5016
+ return new URL(url).origin;
5017
+ } catch {
5018
+ return null;
5019
+ }
5020
+ }
5021
+ function refuseRedirect(entry, to) {
5022
+ if (refuseNonPublicUrl(to) === null) return false;
5023
+ const destination = originOf(to);
5024
+ return destination === null || destination !== originOf(entry);
5025
+ }
5026
+ async function followRedirects(url, init) {
5027
+ let current = url;
5028
+ let request = { ...init, redirect: "manual" };
5029
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
5030
+ const response = await undiciFetch(current, request);
5031
+ const location = response.headers.get("location");
5032
+ if (!REDIRECT_STATUS.has(response.status) || location === null) return response;
5033
+ await response.body?.cancel();
5034
+ const next = new URL(location, current).toString();
5035
+ if (refuseRedirect(url, next)) {
5036
+ throw new Error(`${PRIVATE_ADDRESS_CODE}: redirected to ${next}`);
5037
+ }
5038
+ const method = (request.method ?? "GET").toUpperCase();
5039
+ const downgrade = response.status === 303 || (response.status === 301 || response.status === 302) && method === "POST";
5040
+ request = downgrade ? { ...request, method: "GET", body: void 0 } : request;
5041
+ current = next;
5042
+ }
5043
+ throw new Error(`too many redirects following ${url}`);
5044
+ }
5040
5045
  var NULL_BODY_STATUS = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
5041
5046
  function rebuild(response, body) {
5042
5047
  if (response.status < 200 || response.status > 599 || NULL_BODY_STATUS.has(response.status)) {
@@ -5072,7 +5077,7 @@ async function attemptRoute(url, init, route, options) {
5072
5077
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5073
5078
  const signal = init.signal ? AbortSignal.any([init.signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs);
5074
5079
  try {
5075
- const raw = await undiciFetch(url, { ...init, dispatcher: agent ?? void 0, signal });
5080
+ const raw = await followRedirects(url, { ...init, dispatcher: agent ?? void 0, signal });
5076
5081
  const read = await readOnce(raw, options.sniffChallenge ?? true, options.maxBytes);
5077
5082
  const outcome = classifyFetchOutcome({ status: raw.status, challenge: read.challenge });
5078
5083
  return {
@@ -5151,6 +5156,7 @@ var BROWSER_HEADERS = {
5151
5156
  "Accept-Language": "en-US,en;q=0.9"
5152
5157
  };
5153
5158
  var NOT_PUBLIC_MESSAGE = "That address is not reachable from the public web, so there is nothing to read.";
5159
+ var MAX_REMOTE_IMAGE_BYTES = 25e6;
5154
5160
  var ExternalFetchError = class extends Error {
5155
5161
  /** True when the fault was ours — a route we could not open, not a dead host. */
5156
5162
  ourFault;
@@ -5190,7 +5196,22 @@ async function fetchExternal(url, options = {}) {
5190
5196
  }
5191
5197
  return { response: result.response, blocked: endedBlocked(result) };
5192
5198
  }
5193
- async function fetchExternalBytes(url, options = {}) {
5199
+ function refuseOversize(bytes, maxBytes) {
5200
+ if (bytes === null || bytes <= maxBytes) return null;
5201
+ return `That file is ${humanBytes(bytes)} \u2014 the limit is ${humanBytes(maxBytes)}.`;
5202
+ }
5203
+ function humanBytes(n) {
5204
+ if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(1)} GB`;
5205
+ if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
5206
+ return `${Math.max(1, Math.round(n / 1024))} KB`;
5207
+ }
5208
+ function declaredBytes(response) {
5209
+ const raw = response.headers.get("content-length");
5210
+ if (raw === null) return null;
5211
+ const value = Number(raw);
5212
+ return Number.isFinite(value) ? value : null;
5213
+ }
5214
+ async function fetchExternalBytes(url, options) {
5194
5215
  const { response, blocked } = await fetchExternal(url, options);
5195
5216
  if (!response.ok) {
5196
5217
  throw new ExternalFetchError(`${response.status} ${response.statusText}`, false);
@@ -5198,10 +5219,16 @@ async function fetchExternalBytes(url, options = {}) {
5198
5219
  if (blocked) {
5199
5220
  throw new ExternalFetchError("That host served a security check instead of the file.", false);
5200
5221
  }
5201
- return {
5202
- buffer: Buffer.from(await response.arrayBuffer()),
5203
- contentType: response.headers.get("content-type")
5204
- };
5222
+ const declared = refuseOversize(declaredBytes(response), options.maxBytes);
5223
+ if (declared !== null) {
5224
+ throw new ExternalFetchError(declared, false);
5225
+ }
5226
+ const buffer = Buffer.from(await response.arrayBuffer());
5227
+ const actual = refuseOversize(buffer.byteLength, options.maxBytes);
5228
+ if (actual !== null) {
5229
+ throw new ExternalFetchError(actual, false);
5230
+ }
5231
+ return { buffer, contentType: response.headers.get("content-type") };
5205
5232
  }
5206
5233
 
5207
5234
  // src/engine/schema/refs.ts
@@ -5541,9 +5568,6 @@ async function ingestImageUrl(url, ctx) {
5541
5568
  } catch (e) {
5542
5569
  throw localExecError(ctx, `fetch ${url} \u2192 ${e.message}`);
5543
5570
  }
5544
- if (bytes.byteLength > MAX_ASSET_BYTES) {
5545
- throw localExecError(ctx, `file_too_large: ${url} is ${bytes.byteLength} bytes (limit ${MAX_ASSET_BYTES})`);
5546
- }
5547
5571
  let normalized;
5548
5572
  try {
5549
5573
  normalized = await toModelSafeImage(bytes);
@@ -7893,20 +7917,50 @@ var imageDescribeNode = delegated({
7893
7917
  cost: () => ({ credits: 2, seconds_estimate: 10 })
7894
7918
  });
7895
7919
 
7896
- // src/engine/nodes/remote/imageReferenceSheet.ts
7920
+ // src/engine/nodes/remote/imageLayerize.ts
7897
7921
  import { z as z21 } from "zod";
7922
+ var ImageLayerizeParams = z21.object({
7923
+ /**
7924
+ * What to separate. Read as extraction instructions, not as a generation
7925
+ * prompt — "keep the bottle and its shadow as one layer", not "make it blue".
7926
+ */
7927
+ instructions: z21.string().max(600).optional()
7928
+ }).strict();
7929
+ var imageLayerizeNode = delegated({
7930
+ id: "image_layerize",
7931
+ version: "1.0.0",
7932
+ category: "image",
7933
+ summary: "Split an image into editable layers: a transparent PNG per element, plus any copy baked into the picture recovered as real text with its typography. Powered by Seedream 5.0 Pro Layerize, with Ideogram lifting the text off first.",
7934
+ when_to_use: "Use when you need ONE element of a picture on its own (a product with no background, a logo lockup), or when a shot is right but its copy is wrong and regenerating would change the picture too. The manifest gives you each layer's box and, for text, its exact wording, font, size and colour \u2014 enough to re-set a headline in the original's type. For a plain subject cutout with no text involved, `image_background_remove` is cheaper and one call.",
7935
+ inputs: z21.object({
7936
+ image: ImageRef
7937
+ }).strict(),
7938
+ params: ImageLayerizeParams,
7939
+ outputs: z21.object({
7940
+ layers: z21.array(ImageRef),
7941
+ base: ImageRef,
7942
+ manifest: JsonRef
7943
+ }).strict(),
7944
+ outputKinds: { layers: "image", base: "image", manifest: "json" },
7945
+ // Billed per returned layer (2-17) plus an optional text pass; the backend
7946
+ // charges the real count on completion. This is the pre-run estimate.
7947
+ cost: () => ({ credits: 30, seconds_estimate: 100 })
7948
+ });
7949
+
7950
+ // src/engine/nodes/remote/imageReferenceSheet.ts
7951
+ import { z as z22 } from "zod";
7898
7952
  var REFERENCE_SHEET_MODELS = ["google/gemini-3-pro-image-preview", "google/gemini-3.1-flash-image-preview"];
7899
- var ImageReferenceSheetParams = z21.object({
7900
- model: z21.enum(REFERENCE_SHEET_MODELS),
7901
- subject_description: z21.string().min(1),
7953
+ var ImageReferenceSheetParams = z22.object({
7954
+ model: z22.enum(REFERENCE_SHEET_MODELS),
7955
+ subject_description: z22.string().min(1),
7902
7956
  // `location` = a set/room shown from several camera ANGLES (not a rotated subject),
7903
7957
  // so a multi-scene shoot keeps one consistent set.
7904
- subject_type: z21.enum(["character", "person", "product", "location"]),
7905
- views: z21.array(z21.string().min(1)).min(2).max(8).optional(),
7906
- style: z21.string().optional(),
7907
- prompt_override: z21.string().min(1).optional(),
7908
- aspect_ratio: z21.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9", "1:4", "4:1", "1:8", "8:1"]).optional(),
7909
- image_size: z21.enum(OPENROUTER_IMAGE_SIZES).optional()
7958
+ subject_type: z22.enum(["character", "person", "product", "location"]),
7959
+ views: z22.array(z22.string().min(1)).min(2).max(8).optional(),
7960
+ style: z22.string().optional(),
7961
+ prompt_override: z22.string().min(1).optional(),
7962
+ aspect_ratio: z22.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9", "1:4", "4:1", "1:8", "8:1"]).optional(),
7963
+ image_size: z22.enum(OPENROUTER_IMAGE_SIZES).optional()
7910
7964
  }).strict();
7911
7965
  var imageReferenceSheetNode = delegated({
7912
7966
  id: "image_reference_sheet",
@@ -7914,9 +7968,9 @@ var imageReferenceSheetNode = delegated({
7914
7968
  category: "image",
7915
7969
  summary: "Fuse 1\u20136 images of a single subject (person, character, product, or location/set) into ONE multi-view reference sheet \u2014 a labeled grid in consistent style and lighting: a turnaround (FRONT / SIDE / BACK\u2026) for a person/character/product, or several camera angles of the same room (WIDE / REVERSE / DETAIL\u2026) for a location. Curated models: Gemini 3 Pro Image (best fusion + labels), Gemini 3.1 Flash Image (cheap iteration).",
7916
7970
  when_to_use: "Use before image_generate / video_generate when a subject must stay consistent across many creatives \u2014 wire the `sheet` output into their `reference` input instead of re-describing the subject per prompt. `subject_description` should be the exact wording you reuse downstream. Pick `google/gemini-3-pro-image-preview` for final 6-view sheets at 2K+, `google/gemini-3.1-flash-image-preview` while iterating.",
7917
- inputs: z21.object({ references: z21.array(ImageRef).min(1).max(6) }).loose(),
7971
+ inputs: z22.object({ references: z22.array(ImageRef).min(1).max(6) }).loose(),
7918
7972
  params: ImageReferenceSheetParams,
7919
- outputs: z21.object({ sheet: ImageRef }).strict(),
7973
+ outputs: z22.object({ sheet: ImageRef }).strict(),
7920
7974
  outputKinds: { sheet: "image" },
7921
7975
  cost: ({ params }) => ({
7922
7976
  credits: params?.model === "google/gemini-3-pro-image-preview" ? 20 : 5,
@@ -7925,10 +7979,10 @@ var imageReferenceSheetNode = delegated({
7925
7979
  });
7926
7980
 
7927
7981
  // src/engine/nodes/remote/imageSearch.ts
7928
- import { z as z22 } from "zod";
7929
- var ImageSearchParams = z22.object({
7930
- prompt: z22.string().min(1),
7931
- count: z22.number().int().min(1).max(20).default(5)
7982
+ import { z as z23 } from "zod";
7983
+ var ImageSearchParams = z23.object({
7984
+ prompt: z23.string().min(1),
7985
+ count: z23.number().int().min(1).max(20).default(5)
7932
7986
  }).strict();
7933
7987
  var imageSearchNode = delegated({
7934
7988
  id: "image_search",
@@ -7936,15 +7990,15 @@ var imageSearchNode = delegated({
7936
7990
  category: "image",
7937
7991
  summary: "Agentic image search across Google Images, stock photography (Freepik), and Pinterest. An LLM agent picks the search tools and queries, selects the best matches, and the results are downloaded into canvas assets.",
7938
7992
  when_to_use: "Use to gather real-world reference or inspiration images for a prompt (e.g. several photos of an australian shepherd) so a later step or the user can pick the best one. Not for creating new imagery \u2014 use image_generate for that.",
7939
- inputs: z22.object({}).loose(),
7993
+ inputs: z23.object({}).loose(),
7940
7994
  params: ImageSearchParams,
7941
- outputs: z22.object({ images: z22.array(ImageRef).min(1) }).strict(),
7995
+ outputs: z23.object({ images: z23.array(ImageRef).min(1) }).strict(),
7942
7996
  outputKinds: { images: "image" },
7943
7997
  cost: ({ params }) => ({ credits: Math.ceil(2 + params.count / 2), seconds_estimate: 30 })
7944
7998
  });
7945
7999
 
7946
8000
  // src/engine/nodes/remote/imageSelect.ts
7947
- import { z as z23 } from "zod";
8001
+ import { z as z24 } from "zod";
7948
8002
  var IMAGE_SELECT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
7949
8003
  var imageSelectNode = delegated({
7950
8004
  id: "image_select",
@@ -7952,15 +8006,15 @@ var imageSelectNode = delegated({
7952
8006
  category: "vision",
7953
8007
  summary: "Pick the best `count` images out of 2+ candidates with a vision LLM, judged against a prompt. Outputs a passthrough subset of the input refs (no new pixels) plus the model's comparative reasoning.",
7954
8008
  when_to_use: "Use after fanning out several image_generate variants (or any pool of 2+ images) to keep only the strongest before expensive downstream steps \u2014 video generation, reference sheets, final delivery. `count` fixes the output size, so `images#0`\u2026`images#count-1` are always safe to wire. Pick `~google/gemini-flash-latest` for cheap/fast picks and `~google/gemini-pro-latest` for harder aesthetic judgement.",
7955
- inputs: z23.object({ images: z23.array(ImageRef).min(2) }).loose(),
7956
- params: z23.object({
7957
- model: z23.enum(IMAGE_SELECT_MODELS),
7958
- prompt: z23.string().min(1),
7959
- count: z23.number().int().min(1).default(1),
7960
- temperature: z23.number().min(0).max(2).optional(),
7961
- max_tokens: z23.number().int().positive().optional()
8009
+ inputs: z24.object({ images: z24.array(ImageRef).min(2) }).loose(),
8010
+ params: z24.object({
8011
+ model: z24.enum(IMAGE_SELECT_MODELS),
8012
+ prompt: z24.string().min(1),
8013
+ count: z24.number().int().min(1).default(1),
8014
+ temperature: z24.number().min(0).max(2).optional(),
8015
+ max_tokens: z24.number().int().positive().optional()
7962
8016
  }).strict(),
7963
- outputs: z23.object({ images: z23.array(ImageRef).min(1), reasoning: TextRef }).strict(),
8017
+ outputs: z24.object({ images: z24.array(ImageRef).min(1), reasoning: TextRef }).strict(),
7964
8018
  outputKinds: { images: "image", reasoning: "text" },
7965
8019
  cost: () => ({ credits: 1, seconds_estimate: 5 }),
7966
8020
  // Arity is only knowable at validate time when `images` is a literal array
@@ -7985,34 +8039,34 @@ var imageSelectNode = delegated({
7985
8039
  });
7986
8040
 
7987
8041
  // src/engine/nodes/remote/music.ts
7988
- import { z as z24 } from "zod";
8042
+ import { z as z25 } from "zod";
7989
8043
  var MUSIC_MODELS = ["elevenlabs/music-v1", "elevenlabs/video-background-music-v1"];
7990
- var MusicParams = z24.object({
7991
- model: z24.enum(MUSIC_MODELS),
8044
+ var MusicParams = z25.object({
8045
+ model: z25.enum(MUSIC_MODELS),
7992
8046
  /** Free-form prompt. Used by `elevenlabs/music-v1` (compose-detailed). */
7993
- prompt: z24.string().optional(),
8047
+ prompt: z25.string().optional(),
7994
8048
  /**
7995
8049
  * Structured composition plan (intro / hook / verse / outro sections with
7996
8050
  * per-section styles + durations). Mutually exclusive with `prompt`.
7997
8051
  */
7998
- composition_plan: z24.record(z24.string(), z24.unknown()).optional(),
8052
+ composition_plan: z25.record(z25.string(), z25.unknown()).optional(),
7999
8053
  /** Target length when using `prompt`. 3000–454545ms (capped by the $10 per-node cost limit). */
8000
- music_length_ms: z24.number().int().min(3e3).max(ELEVENLABS_MAX_MUSIC_LENGTH_MS).optional(),
8001
- seed: z24.number().int().optional(),
8054
+ music_length_ms: z25.number().int().min(3e3).max(ELEVENLABS_MAX_MUSIC_LENGTH_MS).optional(),
8055
+ seed: z25.number().int().optional(),
8002
8056
  /** Prompt mode only — forces an instrumental (no vocals) track. */
8003
- force_instrumental: z24.boolean().optional(),
8057
+ force_instrumental: z25.boolean().optional(),
8004
8058
  /** composition_plan only — honor exact section durations. */
8005
- respect_sections_durations: z24.boolean().optional(),
8059
+ respect_sections_durations: z25.boolean().optional(),
8006
8060
  /** Emit word-level timestamps alongside the audio. */
8007
- with_timestamps: z24.boolean().optional(),
8061
+ with_timestamps: z25.boolean().optional(),
8008
8062
  /**
8009
8063
  * video-to-music only — short description of the desired score
8010
8064
  * ("upbeat synth, fast cuts, 80s") used to bias the model.
8011
8065
  */
8012
- description: z24.string().max(1e3).optional(),
8066
+ description: z25.string().max(1e3).optional(),
8013
8067
  /** video-to-music only — up to 10 style tags. */
8014
- tags: z24.array(z24.string()).max(10).optional(),
8015
- output_format: z24.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
8068
+ tags: z25.array(z25.string()).max(10).optional(),
8069
+ output_format: z25.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
8016
8070
  }).strict();
8017
8071
  var musicNode = delegated({
8018
8072
  id: "music",
@@ -8020,9 +8074,9 @@ var musicNode = delegated({
8020
8074
  category: "audio",
8021
8075
  summary: "Generate music for ad creatives and website video content. `elevenlabs/music-v1` composes from a text prompt or structured composition plan; `elevenlabs/video-background-music-v1` scores an existing video clip provided via `inputs.video`.",
8022
8076
  when_to_use: "Use to produce background music or a full score for video ads, hero-section reels, or any motion content. Prefer the video-to-music model when you already have a cut and want music timed to it; use compose-detailed when you have only a brief or want section-level control (intro / hook / outro). Pair the resulting audio with `video_generate` or `video_lipsync` at compose time.",
8023
- inputs: z24.object({ video: VideoRef.optional() }).loose(),
8077
+ inputs: z25.object({ video: VideoRef.optional() }).loose(),
8024
8078
  params: MusicParams,
8025
- outputs: z24.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
8079
+ outputs: z25.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
8026
8080
  outputKinds: { audio: "audio", timestamps: "json" },
8027
8081
  cost: ({ params }) => {
8028
8082
  const seconds = params.music_length_ms ? Math.ceil(params.music_length_ms / 1e3) : 30;
@@ -8053,25 +8107,25 @@ var musicNode = delegated({
8053
8107
  });
8054
8108
 
8055
8109
  // src/engine/nodes/remote/soundEffect.ts
8056
- import { z as z25 } from "zod";
8110
+ import { z as z26 } from "zod";
8057
8111
  var SOUND_EFFECT_MODELS = ["elevenlabs/eleven_text_to_sound_v2"];
8058
- var SoundEffectParams = z25.object({
8059
- model: z25.enum(SOUND_EFFECT_MODELS),
8112
+ var SoundEffectParams = z26.object({
8113
+ model: z26.enum(SOUND_EFFECT_MODELS),
8060
8114
  /** Prompt describing the SFX ("metal door slam", "soft UI tap", "ocean waves"). */
8061
- text: z25.string().min(1),
8115
+ text: z26.string().min(1),
8062
8116
  /**
8063
8117
  * Target length in seconds. 0.5–30. Leave unset to let the model pick the
8064
8118
  * natural length for the described effect.
8065
8119
  */
8066
- duration_seconds: z25.number().min(0.5).max(30).optional(),
8120
+ duration_seconds: z26.number().min(0.5).max(30).optional(),
8067
8121
  /**
8068
8122
  * 0–1. Higher = stick closer to the prompt at the cost of variety; lower
8069
8123
  * = let the model interpret more freely. Defaults to 0.3 on the provider.
8070
8124
  */
8071
- prompt_influence: z25.number().min(0).max(1).optional(),
8125
+ prompt_influence: z26.number().min(0).max(1).optional(),
8072
8126
  /** Only valid on `eleven_text_to_sound_v2` — produce a seamless loop. */
8073
- loop: z25.boolean().optional(),
8074
- output_format: z25.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
8127
+ loop: z26.boolean().optional(),
8128
+ output_format: z26.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
8075
8129
  }).strict();
8076
8130
  var soundEffectNode = delegated({
8077
8131
  id: "sound_effect",
@@ -8079,9 +8133,9 @@ var soundEffectNode = delegated({
8079
8133
  category: "audio",
8080
8134
  summary: "Generate short sound effects from a text prompt via ElevenLabs Text-to-Sound. Use for whooshes, impacts, UI clicks, ambient beds, or signature stingers in ad creatives and product videos.",
8081
8135
  when_to_use: "Reach for this when you need a punch-in SFX layered against `video_generate` or `hyperframe_render` output \u2014 e.g. a logo whoosh on a hero shot, a click on a CTA cut, a swelling ambient bed under VO. Set `loop: true` for atmospheric beds that need to tile under longer footage; leave `duration_seconds` unset and the model picks a natural length.",
8082
- inputs: z25.object({}).loose(),
8136
+ inputs: z26.object({}).loose(),
8083
8137
  params: SoundEffectParams,
8084
- outputs: z25.object({ audio: AudioRef }).strict(),
8138
+ outputs: z26.object({ audio: AudioRef }).strict(),
8085
8139
  outputKinds: { audio: "audio" },
8086
8140
  cost: ({ params }) => {
8087
8141
  const seconds = params.duration_seconds ?? 5;
@@ -8090,7 +8144,7 @@ var soundEffectNode = delegated({
8090
8144
  });
8091
8145
 
8092
8146
  // src/engine/nodes/remote/textGenerate.ts
8093
- import { z as z26 } from "zod";
8147
+ import { z as z27 } from "zod";
8094
8148
  var TEXT_GENERATE_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
8095
8149
  var textGenerateNode = delegated({
8096
8150
  id: "text_generate",
@@ -8098,58 +8152,58 @@ var textGenerateNode = delegated({
8098
8152
  category: "language",
8099
8153
  summary: "Single-turn LLM text generation via OpenRouter. Returns a text response.",
8100
8154
  when_to_use: 'Use for any short text generation step in a canvas \u2014 ad copy, hooks, headlines, JSON outputs for downstream nodes. Pick `~google/gemini-flash-latest` for cheap/fast work and `~google/gemini-pro-latest` for harder reasoning. When the output must be JSON for a downstream `{{slot}}` (e.g. the ad-blueprint transform), set `response_format: "json_object"` so the model returns clean JSON with no markdown fences or prose. Set `web_search: true` to let the model search the live web first (OpenRouter `:online`) \u2014 useful when the transform must adapt copy to the target brand\'s real facts (current pricing, the trust signals it actually has) rather than guess.',
8101
- inputs: z26.object({}).loose(),
8102
- params: z26.object({
8103
- model: z26.enum(TEXT_GENERATE_MODELS),
8104
- prompt: z26.string().min(1),
8105
- system: z26.string().optional(),
8106
- response_format: z26.enum(["text", "json_object"]).optional(),
8107
- web_search: z26.boolean().optional(),
8108
- temperature: z26.number().min(0).max(2).optional(),
8109
- max_tokens: z26.number().int().positive().optional()
8155
+ inputs: z27.object({}).loose(),
8156
+ params: z27.object({
8157
+ model: z27.enum(TEXT_GENERATE_MODELS),
8158
+ prompt: z27.string().min(1),
8159
+ system: z27.string().optional(),
8160
+ response_format: z27.enum(["text", "json_object"]).optional(),
8161
+ web_search: z27.boolean().optional(),
8162
+ temperature: z27.number().min(0).max(2).optional(),
8163
+ max_tokens: z27.number().int().positive().optional()
8110
8164
  }).strict(),
8111
- outputs: z26.object({ text: TextRef }).strict(),
8165
+ outputs: z27.object({ text: TextRef }).strict(),
8112
8166
  outputKinds: { text: "text" },
8113
8167
  cost: () => ({ credits: 1, seconds_estimate: 3 })
8114
8168
  });
8115
8169
 
8116
8170
  // src/engine/nodes/remote/tts.ts
8117
- import { z as z27 } from "zod";
8171
+ import { z as z28 } from "zod";
8118
8172
  var TTS_MODELS = ["elevenlabs/eleven_v3"];
8119
- var TtsVoiceSettings = z27.object({
8120
- stability: z27.number().min(0).max(1).optional(),
8121
- similarity_boost: z27.number().min(0).max(1).optional(),
8122
- style: z27.number().min(0).max(1).optional(),
8123
- use_speaker_boost: z27.boolean().optional(),
8124
- speed: z27.number().min(0.25).max(4).optional()
8173
+ var TtsVoiceSettings = z28.object({
8174
+ stability: z28.number().min(0).max(1).optional(),
8175
+ similarity_boost: z28.number().min(0).max(1).optional(),
8176
+ style: z28.number().min(0).max(1).optional(),
8177
+ use_speaker_boost: z28.boolean().optional(),
8178
+ speed: z28.number().min(0.25).max(4).optional()
8125
8179
  }).strict();
8126
- var TtsPronunciationLocator = z27.object({
8127
- pronunciation_dictionary_id: z27.string().min(1),
8128
- version_id: z27.string().nullable().optional()
8180
+ var TtsPronunciationLocator = z28.object({
8181
+ pronunciation_dictionary_id: z28.string().min(1),
8182
+ version_id: z28.string().nullable().optional()
8129
8183
  }).strict();
8130
- var TtsParams = z27.object({
8131
- model: z27.enum(TTS_MODELS),
8132
- text: z27.string().min(1).max(ELEVENLABS_MAX_TEXT_CHARS),
8133
- voice: z27.string().min(1),
8184
+ var TtsParams = z28.object({
8185
+ model: z28.enum(TTS_MODELS),
8186
+ text: z28.string().min(1).max(ELEVENLABS_MAX_TEXT_CHARS),
8187
+ voice: z28.string().min(1),
8134
8188
  /** Provider output_format (mp3 family only — assets are stored as audio/mpeg). */
8135
- output_format: z27.enum(ELEVENLABS_OUTPUT_FORMATS).optional(),
8136
- seed: z27.number().int().min(0).max(4294967295).optional(),
8189
+ output_format: z28.enum(ELEVENLABS_OUTPUT_FORMATS).optional(),
8190
+ seed: z28.number().int().min(0).max(4294967295).optional(),
8137
8191
  // Top-level shortcuts; structured form is `voice_settings`.
8138
- stability: z27.number().min(0).max(1).optional(),
8139
- similarity_boost: z27.number().min(0).max(1).optional(),
8192
+ stability: z28.number().min(0).max(1).optional(),
8193
+ similarity_boost: z28.number().min(0).max(1).optional(),
8140
8194
  voice_settings: TtsVoiceSettings.optional(),
8141
8195
  /** ISO 639-1 language code. eleven_v3 supports language hints. */
8142
- language_code: z27.string().optional(),
8143
- pronunciation_dictionary_locators: z27.array(TtsPronunciationLocator).max(3).optional(),
8144
- apply_text_normalization: z27.enum(["auto", "on", "off"]).optional(),
8196
+ language_code: z28.string().optional(),
8197
+ pronunciation_dictionary_locators: z28.array(TtsPronunciationLocator).max(3).optional(),
8198
+ apply_text_normalization: z28.enum(["auto", "on", "off"]).optional(),
8145
8199
  /** Currently Japanese-only. Adds latency. */
8146
- apply_language_text_normalization: z27.boolean().optional(),
8200
+ apply_language_text_normalization: z28.boolean().optional(),
8147
8201
  /**
8148
8202
  * When true, hits `/v1/text-to-speech/{voice_id}/with-timestamps` and
8149
8203
  * adds a `timestamps` output (character-level alignment) for caption
8150
8204
  * rendering, lipsync, and beat-matched cuts.
8151
8205
  */
8152
- with_timestamps: z27.boolean().optional()
8206
+ with_timestamps: z28.boolean().optional()
8153
8207
  }).strict();
8154
8208
  var ttsNode = delegated({
8155
8209
  id: "tts",
@@ -8157,9 +8211,9 @@ var ttsNode = delegated({
8157
8211
  category: "audio",
8158
8212
  summary: "Single-voice text-to-speech via ElevenLabs Eleven v3. Optional character-level timestamps for caption rendering and beat-matched cuts.",
8159
8213
  when_to_use: "Use for single-speaker VO \u2014 ad reads, hero-section narration, product walkthroughs. Reach for `dialogue` when you need multiple voices in one stitched track. Set `with_timestamps: true` when downstream needs character-level alignment (captions, lipsync).",
8160
- inputs: z27.object({}).loose(),
8214
+ inputs: z28.object({}).loose(),
8161
8215
  params: TtsParams,
8162
- outputs: z27.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
8216
+ outputs: z28.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
8163
8217
  outputKinds: { audio: "audio", timestamps: "json" },
8164
8218
  cost: ({ params }) => ({
8165
8219
  credits: Math.max(1, Math.ceil(params.text.length * 15e-4)),
@@ -8168,25 +8222,25 @@ var ttsNode = delegated({
8168
8222
  });
8169
8223
 
8170
8224
  // src/engine/nodes/remote/video.ts
8171
- import { z as z28 } from "zod";
8172
- var videoModelEnum = z28.enum(VIDEO_GENERATE_MODELS);
8173
- var VideoGenerateParams = z28.object({
8225
+ import { z as z29 } from "zod";
8226
+ var videoModelEnum = z29.enum(VIDEO_GENERATE_MODELS);
8227
+ var VideoGenerateParams = z29.object({
8174
8228
  model: videoModelEnum,
8175
- prompt: z28.string().min(1),
8176
- duration: z28.number().int().positive().optional(),
8177
- resolution: z28.string().optional(),
8229
+ prompt: z29.string().min(1),
8230
+ duration: z29.number().int().positive().optional(),
8231
+ resolution: z29.string().optional(),
8178
8232
  // Union of ratios accepted by at least one curated model (registry gates
8179
8233
  // per-model). 3:2/2:3 are deliberately absent: no registered model takes them.
8180
- aspect_ratio: z28.enum(["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"]).optional(),
8181
- generate_audio: z28.boolean().optional(),
8182
- seed: z28.number().int().nonnegative().optional(),
8234
+ aspect_ratio: z29.enum(["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"]).optional(),
8235
+ generate_audio: z29.boolean().optional(),
8236
+ seed: z29.number().int().nonnegative().optional(),
8183
8237
  // Veo-only passthroughs (routed via `provider.options.google-vertex.parameters`).
8184
- negative_prompt: z28.string().optional(),
8185
- person_generation: z28.string().optional(),
8186
- enhance_prompt: z28.boolean().optional(),
8187
- conditioning_scale: z28.number().optional(),
8238
+ negative_prompt: z29.string().optional(),
8239
+ person_generation: z29.string().optional(),
8240
+ enhance_prompt: z29.boolean().optional(),
8241
+ conditioning_scale: z29.number().optional(),
8188
8242
  // Kling-only passthrough (prompt-adherence dial, sent top-level).
8189
- cfg_scale: z28.number().optional()
8243
+ cfg_scale: z29.number().optional()
8190
8244
  }).strict();
8191
8245
  var videoGenerateNode = delegated({
8192
8246
  id: "video_generate",
@@ -8194,7 +8248,7 @@ var videoGenerateNode = delegated({
8194
8248
  category: "video",
8195
8249
  summary: "Generate video for ad creatives. Curated roster: `google/gemini-omni-flash` (DEFAULT \u2014 native audio + reference images in one call, 720p, 3-10s), `bytedance/seedance-2.0` (identity/product workhorse, NO photoreal human faces), `google/veo-3.1` (photoreal cine ceiling + the human-face model), `google/veo-3.1-fast` (cheap Veo iteration), `kwaivgi/kling-3.0` (motion-transfer/dynamic). Async with polling.",
8196
8250
  when_to_use: "`google/gemini-omni-flash` is the DEFAULT: it renders picture and synchronized audio in one call, and is the only model here that takes reference images alongside a prompt. SET `duration` (3-10s) to the length the beat actually needs \u2014 it bills ~$0.101 PER SECOND, so leaving it out renders the 10s default and charges ~$1.01 for a beat that may need 3s (~$0.30). Its resolution is fixed at 720p and it accepts only `16:9` or `9:16`; it takes NO `resolution`, `seed`, `negative_prompt` or `generate_audio` param \u2014 sending one fails validation. Keep the default for ordinary beats; move off it when a beat needs 1080p, a length outside 3-10s, or a 1:1 frame. Use `bytedance/seedance-2.0` for identity/product output WITHOUT a photoreal human face on camera. Any clip whose frame shows a photoreal human face \u2014 a UGC creator, presenter, or testimonial speaker, REAL OR AI-GENERATED \u2014 must use `google/veo-3.1`: ByteDance's filter rejects photoreal human faces on Seedance (content_policy_blocked/E005), and a synthetic AI face does NOT clear it. Use `google/veo-3.1-fast` while iterating to keep cost low; `kwaivgi/kling-3.0` for motion-transfer/hyper-dynamic beats. `baker canvas scaffold-video` auto-routes a photoreal cast to Veo; a Seedance clip blocked at runtime also auto-recovers on Veo. Each model gates its own durations/resolutions/aspect ratios in the registry \u2014 see the README per-model section.",
8197
- inputs: z28.object({
8251
+ inputs: z29.object({
8198
8252
  first_frame: ImageRef.optional(),
8199
8253
  last_frame: ImageRef.optional(),
8200
8254
  reference: ImageRef.optional(),
@@ -8204,10 +8258,10 @@ var videoGenerateNode = delegated({
8204
8258
  * exclusive with `first_frame` — OpenRouter treats a request carrying both
8205
8259
  * as image-to-video and ignores these, so wire one or the other.
8206
8260
  */
8207
- references: z28.array(ImageRef).optional()
8261
+ references: z29.array(ImageRef).optional()
8208
8262
  }).loose(),
8209
8263
  params: VideoGenerateParams,
8210
- outputs: z28.object({ video: VideoRef }).strict(),
8264
+ outputs: z29.object({ video: VideoRef }).strict(),
8211
8265
  outputKinds: { video: "video" },
8212
8266
  // Priced from the shared contract, so `validate`'s quote and the charge the
8213
8267
  // backend applies come from ONE table. A flat number here under-quoted a
@@ -8227,11 +8281,11 @@ var videoGenerateNode = delegated({
8227
8281
  });
8228
8282
 
8229
8283
  // src/engine/nodes/remote/videoBackgroundRemove.ts
8230
- import { z as z29 } from "zod";
8231
- var VideoBackgroundRemoveParams = z29.object({
8232
- model: z29.literal("fal/veed-video-background-removal").optional().default("fal/veed-video-background-removal"),
8233
- edge_refinement: z29.boolean().optional().default(true),
8234
- output_codec: z29.enum(["vp9", "h264"]).optional().default("vp9")
8284
+ import { z as z30 } from "zod";
8285
+ var VideoBackgroundRemoveParams = z30.object({
8286
+ model: z30.literal("fal/veed-video-background-removal").optional().default("fal/veed-video-background-removal"),
8287
+ edge_refinement: z30.boolean().optional().default(true),
8288
+ output_codec: z30.enum(["vp9", "h264"]).optional().default("vp9")
8235
8289
  }).strict();
8236
8290
  var videoBackgroundRemoveNode = delegated({
8237
8291
  id: "video_background_remove",
@@ -8239,18 +8293,18 @@ var videoBackgroundRemoveNode = delegated({
8239
8293
  category: "video",
8240
8294
  summary: "Remove the background from a video and return a transparent VP9-with-alpha WebM (or H264 RGB+alpha pair). Drops directly into a hyperframe composition as `<video src='...'>` for chroma-keyed picture-in-picture overlays. Powered by fal.ai `veed/video-background-removal/fast`.",
8241
8295
  when_to_use: "Use when you need a talking-head or subject to float over a custom background in a hyperframe composition. Pair with hyperframe_render(composition: screencast-with-talker) for screencast-with-narrator videos. Output is `video/webm` with alpha \u2014 feed straight into `<video src>` in a composition.",
8242
- inputs: z29.object({
8296
+ inputs: z30.object({
8243
8297
  video: VideoRef
8244
8298
  }).strict(),
8245
8299
  params: VideoBackgroundRemoveParams,
8246
- outputs: z29.object({ video: VideoRef }).strict(),
8300
+ outputs: z30.object({ video: VideoRef }).strict(),
8247
8301
  outputKinds: { video: "video" },
8248
8302
  // $0.012 per 30 frames (edge refinement on) — assume ~30fps; refine via fal dashboard.
8249
8303
  cost: () => ({ credits: 50, seconds_estimate: 60 })
8250
8304
  });
8251
8305
 
8252
8306
  // src/engine/nodes/remote/videoDeconstruct.ts
8253
- import { z as z30 } from "zod";
8307
+ import { z as z31 } from "zod";
8254
8308
  var VIDEO_DECONSTRUCT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
8255
8309
  var videoDeconstructNode = delegated({
8256
8310
  id: "video_deconstruct",
@@ -8258,34 +8312,34 @@ var videoDeconstructNode = delegated({
8258
8312
  category: "video",
8259
8313
  summary: 'Deconstruct a video into a replication-grade blueprint: scene boundaries, the real start/end frame of every scene (extracted from the video as images), and an exhaustive JSON analysis \u2014 per-scene action detail, camera motion, generation-ready frame/motion prompts, overlay text with full typographic style, floating elements, deeply detailed cast (perceived demographics, ethnicity/skin-tone, styling, market-recasting notes), brand-identified logos (named by brand and what they signal, not by appearance, with on-screen timestamps), dialogue with voice descriptions, music spec, SFX list, plus a word-level transcript. `mode:"index"` is the cheap structure-first pass: scene boundaries + global blueprint only (one LLM call, no frames).',
8260
8314
  when_to_use: 'Use to reverse-engineer a reference video (e.g. a competitor ad) so a new canvas can reproduce or remix it scene by scene. Agent loop: (1) optionally run `mode:"index"` to see the structure cheaply (scene count, boundaries, transcript) before planning; (2) run the full deconstruct; (3) read `analysis` and author the reproduction canvas. The blueprint maps 1:1 onto generation nodes: `analysis.scenes[i]` aligns positionally with `start_frames#i`/`end_frames#i`; per scene, `start_frame_prompt`/`end_frame_prompt` feed image_generate (overlay text is excluded from them by contract \u2014 recomposite it from `overlays`), `motion_prompt` + the two frames feed video_generate (first_frame/last_frame), `dialogue[].voice_description` casts tts/dialogue voices, `global.music.music_prompt` feeds music, `sfx[].sound_effect_prompt` feeds sound_effect, and `overlays`/`floating_elements` drive an ffmpeg/hyperframe overlay pass. Long videos (over ~8 min single-shot): run `mode:"index"` first, then several full nodes IN PARALLEL each with a `start_s`/`end_s` window (\u2264480s, snap edges to index scene boundaries), and merge by concatenating `analysis.scenes`; over-length errors include suggested windows. Inject fields into downstream prompts via `{{slot}}`. Pick `~google/gemini-pro-latest` for the densest extraction, `~google/gemini-flash-latest` for cheap/fast passes.',
8261
- inputs: z30.object({ video: VideoRef }).loose(),
8262
- params: z30.object({
8263
- model: z30.enum(VIDEO_DECONSTRUCT_MODELS),
8264
- mode: z30.enum(["full", "index"]).optional(),
8265
- language: z30.string().min(2).max(8).optional(),
8266
- max_scenes: z30.number().int().min(1).max(60).optional(),
8267
- focus: z30.string().optional(),
8268
- start_s: z30.number().min(0).optional(),
8269
- end_s: z30.number().positive().optional(),
8315
+ inputs: z31.object({ video: VideoRef }).loose(),
8316
+ params: z31.object({
8317
+ model: z31.enum(VIDEO_DECONSTRUCT_MODELS),
8318
+ mode: z31.enum(["full", "index"]).optional(),
8319
+ language: z31.string().min(2).max(8).optional(),
8320
+ max_scenes: z31.number().int().min(1).max(60).optional(),
8321
+ focus: z31.string().optional(),
8322
+ start_s: z31.number().min(0).optional(),
8323
+ end_s: z31.number().positive().optional(),
8270
8324
  // Real visual shot-cut timestamps (absolute seconds), detected locally with
8271
8325
  // ffmpeg before the deconstruct. The backend SNAPS its LLM scene boundaries
8272
8326
  // onto these and SPLITS any scene that spans one, so a scene's frames never
8273
8327
  // straddle a hard cut. `scaffold-video` populates this; omit for LLM-only cuts.
8274
- shot_cuts: z30.array(z30.number().min(0)).max(200).optional(),
8328
+ shot_cuts: z31.array(z31.number().min(0)).max(200).optional(),
8275
8329
  // The video model's per-clip ceiling (seconds). A shot longer than this is
8276
8330
  // split into seamless continuation sub-scenes (shared splice frame), so long
8277
8331
  // shots reproduce in full instead of being truncated. `scaffold-video` sets
8278
8332
  // the Seedance ceiling (15); omit to disable length splitting.
8279
- max_clip_s: z30.number().positive().max(60).optional(),
8333
+ max_clip_s: z31.number().positive().max(60).optional(),
8280
8334
  // Transcript provider for the blueprint's dialogue/transcript. Default
8281
8335
  // Groq Whisper; "deepgram" routes to Nova-3 so words carry punctuation.
8282
- transcriber: z30.enum(["groq", "deepgram"]).optional()
8336
+ transcriber: z31.enum(["groq", "deepgram"]).optional()
8283
8337
  }).strict(),
8284
- outputs: z30.object({
8338
+ outputs: z31.object({
8285
8339
  analysis: JsonRef,
8286
8340
  // Absent in mode:"index" (structure only, no Mux frame extraction).
8287
- start_frames: z30.array(ImageRef).min(1).optional(),
8288
- end_frames: z30.array(ImageRef).min(1).optional(),
8341
+ start_frames: z31.array(ImageRef).min(1).optional(),
8342
+ end_frames: z31.array(ImageRef).min(1).optional(),
8289
8343
  transcript: JsonRef
8290
8344
  }).strict(),
8291
8345
  outputKinds: { analysis: "json", start_frames: "image", end_frames: "image", transcript: "json" },
@@ -8293,22 +8347,22 @@ var videoDeconstructNode = delegated({
8293
8347
  });
8294
8348
 
8295
8349
  // src/engine/nodes/remote/videoLipsync.ts
8296
- import { z as z31 } from "zod";
8297
- var FalLipsyncParams = z31.object({
8298
- model: z31.literal("fal/veed-lipsync")
8350
+ import { z as z32 } from "zod";
8351
+ var FalLipsyncParams = z32.object({
8352
+ model: z32.literal("fal/veed-lipsync")
8299
8353
  }).strict();
8300
- var VideoLipsyncParams = z31.discriminatedUnion("model", [FalLipsyncParams]);
8354
+ var VideoLipsyncParams = z32.discriminatedUnion("model", [FalLipsyncParams]);
8301
8355
  var videoLipsyncNode = delegated({
8302
8356
  id: "video_lipsync",
8303
8357
  version: "1.0.0",
8304
8358
  category: "video",
8305
8359
  summary: "Lip-sync a video to an audio track. Currently backed by VEED via fal.ai (`fal/veed-lipsync`). $0.40/min of output.",
8306
- inputs: z31.object({
8360
+ inputs: z32.object({
8307
8361
  video: VideoRef,
8308
8362
  audio: AudioRef
8309
8363
  }).strict(),
8310
8364
  params: VideoLipsyncParams,
8311
- outputs: z31.object({ video: VideoRef }).strict(),
8365
+ outputs: z32.object({ video: VideoRef }).strict(),
8312
8366
  outputKinds: { video: "video" },
8313
8367
  cost: () => ({ credits: 20, seconds_estimate: 120 })
8314
8368
  });
@@ -8317,7 +8371,7 @@ var videoLipsyncNode = delegated({
8317
8371
  import { mkdtemp as mkdtemp6, readFile as readFile11, rm as rm6 } from "fs/promises";
8318
8372
  import { tmpdir as tmpdir6 } from "os";
8319
8373
  import path14 from "path";
8320
- import { z as z32 } from "zod";
8374
+ import { z as z33 } from "zod";
8321
8375
 
8322
8376
  // src/engine/nodes/local/lib/ffmpeg.ts
8323
8377
  import { execFile as execFile7 } from "child_process";
@@ -8396,24 +8450,24 @@ ${detail.slice(-4e3)}`);
8396
8450
  }
8397
8451
 
8398
8452
  // src/engine/nodes/remote/videoTranscribe.ts
8399
- var VideoTranscribeParams = z32.object({
8400
- language: z32.string().min(2).max(8).optional(),
8453
+ var VideoTranscribeParams = z33.object({
8454
+ language: z33.string().min(2).max(8).optional(),
8401
8455
  // Provider choice is explicit (no env-based silent branching). Default Groq
8402
8456
  // Whisper; "deepgram" routes to Deepgram Nova-3, which additionally emits a
8403
8457
  // `rich` JSON output with punctuated words + paragraph/sentence grouping.
8404
- transcriber: z32.enum(["groq", "deepgram"]).optional()
8458
+ transcriber: z33.enum(["groq", "deepgram"]).optional()
8405
8459
  }).strict();
8406
- var VideoTranscribeInputs = z32.object({
8460
+ var VideoTranscribeInputs = z33.object({
8407
8461
  // A video (audio auto-extracted locally) OR a bare audio track. The key stays
8408
8462
  // `video` for back-compat; the backend already accepts audio-kind refs on it —
8409
8463
  // the local extraction path has been shipping one for every video input.
8410
- video: z32.union([VideoRef, AudioRef])
8464
+ video: z33.union([VideoRef, AudioRef])
8411
8465
  }).strict();
8412
- var VideoTranscribeOutputs = z32.object({
8413
- transcript: z32.custom(),
8466
+ var VideoTranscribeOutputs = z33.object({
8467
+ transcript: z33.custom(),
8414
8468
  // Only emitted by the Deepgram path: full punctuated words + paragraph /
8415
8469
  // sentence grouping with speaker indices. Absent for the default Groq path.
8416
- rich: z32.custom().optional()
8470
+ rich: z33.custom().optional()
8417
8471
  }).strict();
8418
8472
  var AUDIO_EXTRACT_TIMEOUT_MS = 6e4;
8419
8473
  var videoTranscribeNode = defineNode({
@@ -8498,29 +8552,29 @@ async function tryExtractAudio(inputs, ctx) {
8498
8552
  }
8499
8553
 
8500
8554
  // src/engine/nodes/remote/voiceSelect.ts
8501
- import { z as z33 } from "zod";
8555
+ import { z as z34 } from "zod";
8502
8556
  var voiceSelectNode = delegated({
8503
8557
  id: "voice_select",
8504
8558
  version: "1.0.0",
8505
8559
  category: "audio",
8506
8560
  summary: 'Cast an ElevenLabs voice from a natural-language description (e.g. "warm, authoritative female narrator, American accent"). Lists the account\'s voices and ranks them against the brief, emitting the best `voice_id` as a bare-string text asset plus a ranked `candidates` JSON.',
8507
8561
  when_to_use: 'Use to turn a voice description (e.g. from a `video_deconstruct` blueprint\'s `voice_description`) into a usable ElevenLabs voice id, then feed it into a `tts` node by wiring `inputs.voice_ref: $ref:<this>.voice_id` and setting `params.voice: "{{voice_ref}}"` \u2014 the engine splices the id in at run time. Review `candidates` (json) to pick a different voice. Optional `gender`/`age`/`accent`/`language` hints sharpen the ranking.',
8508
- inputs: z33.object({}).loose(),
8509
- params: z33.object({
8510
- description: z33.string().min(1),
8511
- gender: z33.string().optional(),
8512
- age: z33.string().optional(),
8513
- accent: z33.string().optional(),
8514
- language: z33.string().optional(),
8515
- limit: z33.number().int().min(1).max(20).optional()
8562
+ inputs: z34.object({}).loose(),
8563
+ params: z34.object({
8564
+ description: z34.string().min(1),
8565
+ gender: z34.string().optional(),
8566
+ age: z34.string().optional(),
8567
+ accent: z34.string().optional(),
8568
+ language: z34.string().optional(),
8569
+ limit: z34.number().int().min(1).max(20).optional()
8516
8570
  }).strict(),
8517
- outputs: z33.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
8571
+ outputs: z34.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
8518
8572
  outputKinds: { voice_id: "text", candidates: "json" },
8519
8573
  cost: () => ({ credits: 0, seconds_estimate: 5 })
8520
8574
  });
8521
8575
 
8522
8576
  // src/engine/schema/catalog.ts
8523
- import { z as z34 } from "zod";
8577
+ import { z as z35 } from "zod";
8524
8578
  function generateCatalog(registry, opts = {}) {
8525
8579
  const entries = registry.all().map((def) => {
8526
8580
  const cost = def.cost ? safeCost(def) : void 0;
@@ -8531,9 +8585,9 @@ function generateCatalog(registry, opts = {}) {
8531
8585
  summary: def.summary,
8532
8586
  when_to_use: def.when_to_use,
8533
8587
  location: def.location,
8534
- inputs: z34.toJSONSchema(def.inputs, { unrepresentable: "any" }),
8535
- params: z34.toJSONSchema(def.params, { unrepresentable: "any" }),
8536
- outputs: z34.toJSONSchema(def.outputs, { unrepresentable: "any" }),
8588
+ inputs: z35.toJSONSchema(def.inputs, { unrepresentable: "any" }),
8589
+ params: z35.toJSONSchema(def.params, { unrepresentable: "any" }),
8590
+ outputs: z35.toJSONSchema(def.outputs, { unrepresentable: "any" }),
8537
8591
  cost_estimate_credits: cost?.credits,
8538
8592
  runtime_estimate_seconds: cost?.seconds_estimate
8539
8593
  };
@@ -8631,7 +8685,8 @@ var REMOTE_NODES = [
8631
8685
  videoBackgroundRemoveNode,
8632
8686
  videoDeconstructNode,
8633
8687
  voiceSelectNode,
8634
- imageBackgroundRemoveNode
8688
+ imageBackgroundRemoveNode,
8689
+ imageLayerizeNode
8635
8690
  ];
8636
8691
  function defaultRegistry() {
8637
8692
  const r = new NodeRegistry();
@@ -8698,6 +8753,7 @@ export {
8698
8753
  REF_PREFIX,
8699
8754
  parseRefExpr,
8700
8755
  sha256Hex,
8756
+ MAX_ASSET_BYTES,
8701
8757
  looksLikeHttpUrl,
8702
8758
  SEEDANCE_PROFILE,
8703
8759
  clipProfileFor,
@@ -8706,6 +8762,7 @@ export {
8706
8762
  spineInputFlags,
8707
8763
  spineInputOps,
8708
8764
  elementMentionKeywords,
8765
+ MAX_REMOTE_IMAGE_BYTES,
8709
8766
  fetchExternalBytes,
8710
8767
  toModelSafeImage,
8711
8768
  BackendClient2,
@@ -8718,4 +8775,4 @@ export {
8718
8775
  defaultRegistry,
8719
8776
  createEngineFromEnv
8720
8777
  };
8721
- //# sourceMappingURL=chunk-MMP7BRV7.js.map
8778
+ //# sourceMappingURL=chunk-5MPIOGRO.js.map