@koda-sl/baker-cli 0.198.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 +34 -2
  2. package/dist/{chunk-ZBS53HJF.js → chunk-2R2DEPG7.js} +3 -3
  3. package/dist/{chunk-CVJCXDWD.js → chunk-5MPIOGRO.js} +297 -176
  4. package/dist/chunk-5MPIOGRO.js.map +1 -0
  5. package/dist/{chunk-MVCCSWDE.js → chunk-6NZG2TCM.js} +82 -4
  6. package/dist/chunk-6NZG2TCM.js.map +1 -0
  7. package/dist/{chunk-UBTH7NPV.js → chunk-KHHD26LZ.js} +3 -3
  8. package/dist/{chunk-XVF27TBY.js → chunk-SVHFT7AN.js} +4 -4
  9. package/dist/{chunk-RN2XKQIJ.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-6PUX5HJY.js → env-FF7ZB5W5.js} +2 -2
  15. package/dist/{output-VRWY3V6Y.js → output-YGPWST77.js} +5 -5
  16. package/dist/{shared-T2HONXTH.js → shared-CRWB5B7P.js} +6 -6
  17. package/package.json +1 -1
  18. package/dist/chunk-CVJCXDWD.js.map +0 -1
  19. package/dist/chunk-MVCCSWDE.js.map +0 -1
  20. package/dist/client-YOLAMGIA.js +0 -15
  21. /package/dist/{chunk-ZBS53HJF.js.map → chunk-2R2DEPG7.js.map} +0 -0
  22. /package/dist/{chunk-UBTH7NPV.js.map → chunk-KHHD26LZ.js.map} +0 -0
  23. /package/dist/{chunk-XVF27TBY.js.map → chunk-SVHFT7AN.js.map} +0 -0
  24. /package/dist/{chunk-RN2XKQIJ.js.map → chunk-UZYVSPCA.js.map} +0 -0
  25. /package/dist/{client-YOLAMGIA.js.map → client-MP7BKFKM.js.map} +0 -0
  26. /package/dist/{env-6PUX5HJY.js.map → env-FF7ZB5W5.js.map} +0 -0
  27. /package/dist/{output-VRWY3V6Y.js.map → output-YGPWST77.js.map} +0 -0
  28. /package/dist/{shared-T2HONXTH.js.map → shared-CRWB5B7P.js.map} +0 -0
@@ -1,22 +1,23 @@
1
1
  import {
2
2
  debugLogHttp,
3
3
  readBodyForLog
4
- } from "./chunk-RN2XKQIJ.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-MVCCSWDE.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;
@@ -4917,7 +4918,8 @@ import { promisify } from "util";
4917
4918
  import { z as z5 } from "zod";
4918
4919
 
4919
4920
  // ../proxy/src/fetch.ts
4920
- import { ProxyAgent, Response, fetch as undiciFetch } from "undici";
4921
+ import { lookup as dnsLookup } from "dns";
4922
+ import { Agent, ProxyAgent, Response, fetch as undiciFetch } from "undici";
4921
4923
 
4922
4924
  // ../proxy/src/fetchDecision.ts
4923
4925
  function classifyFetchOutcome(signal) {
@@ -4981,10 +4983,65 @@ function tierOf(route) {
4981
4983
  return route.kind === "proxy" ? route.tier : "direct";
4982
4984
  }
4983
4985
  function agentFor(route) {
4984
- if (route.kind !== "proxy") return null;
4986
+ if (route.kind !== "proxy") return directAgent();
4985
4987
  const token = Buffer.from(`${route.username}:${route.password}`).toString("base64");
4986
4988
  return new ProxyAgent({ uri: route.server, token: `Basic ${token}` });
4987
4989
  }
4990
+ function directAgent() {
4991
+ return new Agent({
4992
+ connect: {
4993
+ lookup: (hostname, options, callback) => {
4994
+ dnsLookup(hostname, options, (error, address, family) => {
4995
+ if (error) {
4996
+ callback(error, address, family);
4997
+ return;
4998
+ }
4999
+ const found = Array.isArray(address) ? address.map((entry) => entry.address) : [address];
5000
+ const blocked = found.find((entry) => isPrivateAddress(entry));
5001
+ if (blocked !== void 0) {
5002
+ callback(new Error(`${PRIVATE_ADDRESS_CODE}: ${hostname} resolves to ${blocked}`), "", 4);
5003
+ return;
5004
+ }
5005
+ callback(null, address, family);
5006
+ });
5007
+ }
5008
+ }
5009
+ });
5010
+ }
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
+ }
4988
5045
  var NULL_BODY_STATUS = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
4989
5046
  function rebuild(response, body) {
4990
5047
  if (response.status < 200 || response.status > 599 || NULL_BODY_STATUS.has(response.status)) {
@@ -5020,7 +5077,7 @@ async function attemptRoute(url, init, route, options) {
5020
5077
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5021
5078
  const signal = init.signal ? AbortSignal.any([init.signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs);
5022
5079
  try {
5023
- const raw = await undiciFetch(url, { ...init, dispatcher: agent ?? void 0, signal });
5080
+ const raw = await followRedirects(url, { ...init, dispatcher: agent ?? void 0, signal });
5024
5081
  const read = await readOnce(raw, options.sniffChallenge ?? true, options.maxBytes);
5025
5082
  const outcome = classifyFetchOutcome({ status: raw.status, challenge: read.challenge });
5026
5083
  return {
@@ -5083,6 +5140,14 @@ function endedProxyUnavailable(result) {
5083
5140
  if (rescued(result) || endedBlocked(result)) return false;
5084
5141
  return result.attempts.some((attempt) => attempt.outcome === "proxy_unavailable");
5085
5142
  }
5143
+ function endedPrivateAddress(result) {
5144
+ if (rescued(result)) return false;
5145
+ for (let error = result.error, depth = 0; error instanceof Error && depth < 5; depth++) {
5146
+ if (error.message.includes(PRIVATE_ADDRESS_CODE)) return true;
5147
+ error = error.cause;
5148
+ }
5149
+ return false;
5150
+ }
5086
5151
 
5087
5152
  // src/lib/externalFetch.ts
5088
5153
  var BROWSER_HEADERS = {
@@ -5090,6 +5155,8 @@ var BROWSER_HEADERS = {
5090
5155
  Accept: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
5091
5156
  "Accept-Language": "en-US,en;q=0.9"
5092
5157
  };
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;
5093
5160
  var ExternalFetchError = class extends Error {
5094
5161
  /** True when the fault was ours — a route we could not open, not a dead host. */
5095
5162
  ourFault;
@@ -5101,7 +5168,7 @@ var ExternalFetchError = class extends Error {
5101
5168
  };
5102
5169
  async function fetchExternal(url, options = {}) {
5103
5170
  if (refuseNonPublicUrl(url) !== null) {
5104
- throw new ExternalFetchError("That address is not reachable from the public web, so there is nothing to read.", false);
5171
+ throw new ExternalFetchError(NOT_PUBLIC_MESSAGE, false);
5105
5172
  }
5106
5173
  const result = await fetchWithEscalation(
5107
5174
  url,
@@ -5118,6 +5185,9 @@ async function fetchExternal(url, options = {}) {
5118
5185
  `);
5119
5186
  }
5120
5187
  if (result.response === null) {
5188
+ if (endedPrivateAddress(result)) {
5189
+ throw new ExternalFetchError(NOT_PUBLIC_MESSAGE, false);
5190
+ }
5121
5191
  const ourFault = endedProxyUnavailable(result);
5122
5192
  throw new ExternalFetchError(
5123
5193
  ourFault ? "Could not open a route to that address." : "That address did not answer.",
@@ -5126,7 +5196,22 @@ async function fetchExternal(url, options = {}) {
5126
5196
  }
5127
5197
  return { response: result.response, blocked: endedBlocked(result) };
5128
5198
  }
5129
- 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) {
5130
5215
  const { response, blocked } = await fetchExternal(url, options);
5131
5216
  if (!response.ok) {
5132
5217
  throw new ExternalFetchError(`${response.status} ${response.statusText}`, false);
@@ -5134,10 +5219,16 @@ async function fetchExternalBytes(url, options = {}) {
5134
5219
  if (blocked) {
5135
5220
  throw new ExternalFetchError("That host served a security check instead of the file.", false);
5136
5221
  }
5137
- return {
5138
- buffer: Buffer.from(await response.arrayBuffer()),
5139
- contentType: response.headers.get("content-type")
5140
- };
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") };
5141
5232
  }
5142
5233
 
5143
5234
  // src/engine/schema/refs.ts
@@ -5477,9 +5568,6 @@ async function ingestImageUrl(url, ctx) {
5477
5568
  } catch (e) {
5478
5569
  throw localExecError(ctx, `fetch ${url} \u2192 ${e.message}`);
5479
5570
  }
5480
- if (bytes.byteLength > MAX_ASSET_BYTES) {
5481
- throw localExecError(ctx, `file_too_large: ${url} is ${bytes.byteLength} bytes (limit ${MAX_ASSET_BYTES})`);
5482
- }
5483
5571
  let normalized;
5484
5572
  try {
5485
5573
  normalized = await toModelSafeImage(bytes);
@@ -7829,20 +7917,50 @@ var imageDescribeNode = delegated({
7829
7917
  cost: () => ({ credits: 2, seconds_estimate: 10 })
7830
7918
  });
7831
7919
 
7832
- // src/engine/nodes/remote/imageReferenceSheet.ts
7920
+ // src/engine/nodes/remote/imageLayerize.ts
7833
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";
7834
7952
  var REFERENCE_SHEET_MODELS = ["google/gemini-3-pro-image-preview", "google/gemini-3.1-flash-image-preview"];
7835
- var ImageReferenceSheetParams = z21.object({
7836
- model: z21.enum(REFERENCE_SHEET_MODELS),
7837
- 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),
7838
7956
  // `location` = a set/room shown from several camera ANGLES (not a rotated subject),
7839
7957
  // so a multi-scene shoot keeps one consistent set.
7840
- subject_type: z21.enum(["character", "person", "product", "location"]),
7841
- views: z21.array(z21.string().min(1)).min(2).max(8).optional(),
7842
- style: z21.string().optional(),
7843
- prompt_override: z21.string().min(1).optional(),
7844
- 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(),
7845
- 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()
7846
7964
  }).strict();
7847
7965
  var imageReferenceSheetNode = delegated({
7848
7966
  id: "image_reference_sheet",
@@ -7850,9 +7968,9 @@ var imageReferenceSheetNode = delegated({
7850
7968
  category: "image",
7851
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).",
7852
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.",
7853
- 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(),
7854
7972
  params: ImageReferenceSheetParams,
7855
- outputs: z21.object({ sheet: ImageRef }).strict(),
7973
+ outputs: z22.object({ sheet: ImageRef }).strict(),
7856
7974
  outputKinds: { sheet: "image" },
7857
7975
  cost: ({ params }) => ({
7858
7976
  credits: params?.model === "google/gemini-3-pro-image-preview" ? 20 : 5,
@@ -7861,10 +7979,10 @@ var imageReferenceSheetNode = delegated({
7861
7979
  });
7862
7980
 
7863
7981
  // src/engine/nodes/remote/imageSearch.ts
7864
- import { z as z22 } from "zod";
7865
- var ImageSearchParams = z22.object({
7866
- prompt: z22.string().min(1),
7867
- 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)
7868
7986
  }).strict();
7869
7987
  var imageSearchNode = delegated({
7870
7988
  id: "image_search",
@@ -7872,15 +7990,15 @@ var imageSearchNode = delegated({
7872
7990
  category: "image",
7873
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.",
7874
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.",
7875
- inputs: z22.object({}).loose(),
7993
+ inputs: z23.object({}).loose(),
7876
7994
  params: ImageSearchParams,
7877
- outputs: z22.object({ images: z22.array(ImageRef).min(1) }).strict(),
7995
+ outputs: z23.object({ images: z23.array(ImageRef).min(1) }).strict(),
7878
7996
  outputKinds: { images: "image" },
7879
7997
  cost: ({ params }) => ({ credits: Math.ceil(2 + params.count / 2), seconds_estimate: 30 })
7880
7998
  });
7881
7999
 
7882
8000
  // src/engine/nodes/remote/imageSelect.ts
7883
- import { z as z23 } from "zod";
8001
+ import { z as z24 } from "zod";
7884
8002
  var IMAGE_SELECT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
7885
8003
  var imageSelectNode = delegated({
7886
8004
  id: "image_select",
@@ -7888,15 +8006,15 @@ var imageSelectNode = delegated({
7888
8006
  category: "vision",
7889
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.",
7890
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.",
7891
- inputs: z23.object({ images: z23.array(ImageRef).min(2) }).loose(),
7892
- params: z23.object({
7893
- model: z23.enum(IMAGE_SELECT_MODELS),
7894
- prompt: z23.string().min(1),
7895
- count: z23.number().int().min(1).default(1),
7896
- temperature: z23.number().min(0).max(2).optional(),
7897
- 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()
7898
8016
  }).strict(),
7899
- 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(),
7900
8018
  outputKinds: { images: "image", reasoning: "text" },
7901
8019
  cost: () => ({ credits: 1, seconds_estimate: 5 }),
7902
8020
  // Arity is only knowable at validate time when `images` is a literal array
@@ -7921,34 +8039,34 @@ var imageSelectNode = delegated({
7921
8039
  });
7922
8040
 
7923
8041
  // src/engine/nodes/remote/music.ts
7924
- import { z as z24 } from "zod";
8042
+ import { z as z25 } from "zod";
7925
8043
  var MUSIC_MODELS = ["elevenlabs/music-v1", "elevenlabs/video-background-music-v1"];
7926
- var MusicParams = z24.object({
7927
- model: z24.enum(MUSIC_MODELS),
8044
+ var MusicParams = z25.object({
8045
+ model: z25.enum(MUSIC_MODELS),
7928
8046
  /** Free-form prompt. Used by `elevenlabs/music-v1` (compose-detailed). */
7929
- prompt: z24.string().optional(),
8047
+ prompt: z25.string().optional(),
7930
8048
  /**
7931
8049
  * Structured composition plan (intro / hook / verse / outro sections with
7932
8050
  * per-section styles + durations). Mutually exclusive with `prompt`.
7933
8051
  */
7934
- composition_plan: z24.record(z24.string(), z24.unknown()).optional(),
8052
+ composition_plan: z25.record(z25.string(), z25.unknown()).optional(),
7935
8053
  /** Target length when using `prompt`. 3000–454545ms (capped by the $10 per-node cost limit). */
7936
- music_length_ms: z24.number().int().min(3e3).max(ELEVENLABS_MAX_MUSIC_LENGTH_MS).optional(),
7937
- 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(),
7938
8056
  /** Prompt mode only — forces an instrumental (no vocals) track. */
7939
- force_instrumental: z24.boolean().optional(),
8057
+ force_instrumental: z25.boolean().optional(),
7940
8058
  /** composition_plan only — honor exact section durations. */
7941
- respect_sections_durations: z24.boolean().optional(),
8059
+ respect_sections_durations: z25.boolean().optional(),
7942
8060
  /** Emit word-level timestamps alongside the audio. */
7943
- with_timestamps: z24.boolean().optional(),
8061
+ with_timestamps: z25.boolean().optional(),
7944
8062
  /**
7945
8063
  * video-to-music only — short description of the desired score
7946
8064
  * ("upbeat synth, fast cuts, 80s") used to bias the model.
7947
8065
  */
7948
- description: z24.string().max(1e3).optional(),
8066
+ description: z25.string().max(1e3).optional(),
7949
8067
  /** video-to-music only — up to 10 style tags. */
7950
- tags: z24.array(z24.string()).max(10).optional(),
7951
- 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()
7952
8070
  }).strict();
7953
8071
  var musicNode = delegated({
7954
8072
  id: "music",
@@ -7956,9 +8074,9 @@ var musicNode = delegated({
7956
8074
  category: "audio",
7957
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`.",
7958
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.",
7959
- inputs: z24.object({ video: VideoRef.optional() }).loose(),
8077
+ inputs: z25.object({ video: VideoRef.optional() }).loose(),
7960
8078
  params: MusicParams,
7961
- outputs: z24.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
8079
+ outputs: z25.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
7962
8080
  outputKinds: { audio: "audio", timestamps: "json" },
7963
8081
  cost: ({ params }) => {
7964
8082
  const seconds = params.music_length_ms ? Math.ceil(params.music_length_ms / 1e3) : 30;
@@ -7989,25 +8107,25 @@ var musicNode = delegated({
7989
8107
  });
7990
8108
 
7991
8109
  // src/engine/nodes/remote/soundEffect.ts
7992
- import { z as z25 } from "zod";
8110
+ import { z as z26 } from "zod";
7993
8111
  var SOUND_EFFECT_MODELS = ["elevenlabs/eleven_text_to_sound_v2"];
7994
- var SoundEffectParams = z25.object({
7995
- model: z25.enum(SOUND_EFFECT_MODELS),
8112
+ var SoundEffectParams = z26.object({
8113
+ model: z26.enum(SOUND_EFFECT_MODELS),
7996
8114
  /** Prompt describing the SFX ("metal door slam", "soft UI tap", "ocean waves"). */
7997
- text: z25.string().min(1),
8115
+ text: z26.string().min(1),
7998
8116
  /**
7999
8117
  * Target length in seconds. 0.5–30. Leave unset to let the model pick the
8000
8118
  * natural length for the described effect.
8001
8119
  */
8002
- duration_seconds: z25.number().min(0.5).max(30).optional(),
8120
+ duration_seconds: z26.number().min(0.5).max(30).optional(),
8003
8121
  /**
8004
8122
  * 0–1. Higher = stick closer to the prompt at the cost of variety; lower
8005
8123
  * = let the model interpret more freely. Defaults to 0.3 on the provider.
8006
8124
  */
8007
- prompt_influence: z25.number().min(0).max(1).optional(),
8125
+ prompt_influence: z26.number().min(0).max(1).optional(),
8008
8126
  /** Only valid on `eleven_text_to_sound_v2` — produce a seamless loop. */
8009
- loop: z25.boolean().optional(),
8010
- output_format: z25.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
8127
+ loop: z26.boolean().optional(),
8128
+ output_format: z26.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
8011
8129
  }).strict();
8012
8130
  var soundEffectNode = delegated({
8013
8131
  id: "sound_effect",
@@ -8015,9 +8133,9 @@ var soundEffectNode = delegated({
8015
8133
  category: "audio",
8016
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.",
8017
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.",
8018
- inputs: z25.object({}).loose(),
8136
+ inputs: z26.object({}).loose(),
8019
8137
  params: SoundEffectParams,
8020
- outputs: z25.object({ audio: AudioRef }).strict(),
8138
+ outputs: z26.object({ audio: AudioRef }).strict(),
8021
8139
  outputKinds: { audio: "audio" },
8022
8140
  cost: ({ params }) => {
8023
8141
  const seconds = params.duration_seconds ?? 5;
@@ -8026,7 +8144,7 @@ var soundEffectNode = delegated({
8026
8144
  });
8027
8145
 
8028
8146
  // src/engine/nodes/remote/textGenerate.ts
8029
- import { z as z26 } from "zod";
8147
+ import { z as z27 } from "zod";
8030
8148
  var TEXT_GENERATE_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
8031
8149
  var textGenerateNode = delegated({
8032
8150
  id: "text_generate",
@@ -8034,58 +8152,58 @@ var textGenerateNode = delegated({
8034
8152
  category: "language",
8035
8153
  summary: "Single-turn LLM text generation via OpenRouter. Returns a text response.",
8036
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.',
8037
- inputs: z26.object({}).loose(),
8038
- params: z26.object({
8039
- model: z26.enum(TEXT_GENERATE_MODELS),
8040
- prompt: z26.string().min(1),
8041
- system: z26.string().optional(),
8042
- response_format: z26.enum(["text", "json_object"]).optional(),
8043
- web_search: z26.boolean().optional(),
8044
- temperature: z26.number().min(0).max(2).optional(),
8045
- 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()
8046
8164
  }).strict(),
8047
- outputs: z26.object({ text: TextRef }).strict(),
8165
+ outputs: z27.object({ text: TextRef }).strict(),
8048
8166
  outputKinds: { text: "text" },
8049
8167
  cost: () => ({ credits: 1, seconds_estimate: 3 })
8050
8168
  });
8051
8169
 
8052
8170
  // src/engine/nodes/remote/tts.ts
8053
- import { z as z27 } from "zod";
8171
+ import { z as z28 } from "zod";
8054
8172
  var TTS_MODELS = ["elevenlabs/eleven_v3"];
8055
- var TtsVoiceSettings = z27.object({
8056
- stability: z27.number().min(0).max(1).optional(),
8057
- similarity_boost: z27.number().min(0).max(1).optional(),
8058
- style: z27.number().min(0).max(1).optional(),
8059
- use_speaker_boost: z27.boolean().optional(),
8060
- 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()
8061
8179
  }).strict();
8062
- var TtsPronunciationLocator = z27.object({
8063
- pronunciation_dictionary_id: z27.string().min(1),
8064
- 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()
8065
8183
  }).strict();
8066
- var TtsParams = z27.object({
8067
- model: z27.enum(TTS_MODELS),
8068
- text: z27.string().min(1).max(ELEVENLABS_MAX_TEXT_CHARS),
8069
- 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),
8070
8188
  /** Provider output_format (mp3 family only — assets are stored as audio/mpeg). */
8071
- output_format: z27.enum(ELEVENLABS_OUTPUT_FORMATS).optional(),
8072
- 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(),
8073
8191
  // Top-level shortcuts; structured form is `voice_settings`.
8074
- stability: z27.number().min(0).max(1).optional(),
8075
- 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(),
8076
8194
  voice_settings: TtsVoiceSettings.optional(),
8077
8195
  /** ISO 639-1 language code. eleven_v3 supports language hints. */
8078
- language_code: z27.string().optional(),
8079
- pronunciation_dictionary_locators: z27.array(TtsPronunciationLocator).max(3).optional(),
8080
- 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(),
8081
8199
  /** Currently Japanese-only. Adds latency. */
8082
- apply_language_text_normalization: z27.boolean().optional(),
8200
+ apply_language_text_normalization: z28.boolean().optional(),
8083
8201
  /**
8084
8202
  * When true, hits `/v1/text-to-speech/{voice_id}/with-timestamps` and
8085
8203
  * adds a `timestamps` output (character-level alignment) for caption
8086
8204
  * rendering, lipsync, and beat-matched cuts.
8087
8205
  */
8088
- with_timestamps: z27.boolean().optional()
8206
+ with_timestamps: z28.boolean().optional()
8089
8207
  }).strict();
8090
8208
  var ttsNode = delegated({
8091
8209
  id: "tts",
@@ -8093,9 +8211,9 @@ var ttsNode = delegated({
8093
8211
  category: "audio",
8094
8212
  summary: "Single-voice text-to-speech via ElevenLabs Eleven v3. Optional character-level timestamps for caption rendering and beat-matched cuts.",
8095
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).",
8096
- inputs: z27.object({}).loose(),
8214
+ inputs: z28.object({}).loose(),
8097
8215
  params: TtsParams,
8098
- outputs: z27.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
8216
+ outputs: z28.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
8099
8217
  outputKinds: { audio: "audio", timestamps: "json" },
8100
8218
  cost: ({ params }) => ({
8101
8219
  credits: Math.max(1, Math.ceil(params.text.length * 15e-4)),
@@ -8104,25 +8222,25 @@ var ttsNode = delegated({
8104
8222
  });
8105
8223
 
8106
8224
  // src/engine/nodes/remote/video.ts
8107
- import { z as z28 } from "zod";
8108
- var videoModelEnum = z28.enum(VIDEO_GENERATE_MODELS);
8109
- var VideoGenerateParams = z28.object({
8225
+ import { z as z29 } from "zod";
8226
+ var videoModelEnum = z29.enum(VIDEO_GENERATE_MODELS);
8227
+ var VideoGenerateParams = z29.object({
8110
8228
  model: videoModelEnum,
8111
- prompt: z28.string().min(1),
8112
- duration: z28.number().int().positive().optional(),
8113
- resolution: z28.string().optional(),
8229
+ prompt: z29.string().min(1),
8230
+ duration: z29.number().int().positive().optional(),
8231
+ resolution: z29.string().optional(),
8114
8232
  // Union of ratios accepted by at least one curated model (registry gates
8115
8233
  // per-model). 3:2/2:3 are deliberately absent: no registered model takes them.
8116
- aspect_ratio: z28.enum(["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"]).optional(),
8117
- generate_audio: z28.boolean().optional(),
8118
- 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(),
8119
8237
  // Veo-only passthroughs (routed via `provider.options.google-vertex.parameters`).
8120
- negative_prompt: z28.string().optional(),
8121
- person_generation: z28.string().optional(),
8122
- enhance_prompt: z28.boolean().optional(),
8123
- 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(),
8124
8242
  // Kling-only passthrough (prompt-adherence dial, sent top-level).
8125
- cfg_scale: z28.number().optional()
8243
+ cfg_scale: z29.number().optional()
8126
8244
  }).strict();
8127
8245
  var videoGenerateNode = delegated({
8128
8246
  id: "video_generate",
@@ -8130,7 +8248,7 @@ var videoGenerateNode = delegated({
8130
8248
  category: "video",
8131
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.",
8132
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.",
8133
- inputs: z28.object({
8251
+ inputs: z29.object({
8134
8252
  first_frame: ImageRef.optional(),
8135
8253
  last_frame: ImageRef.optional(),
8136
8254
  reference: ImageRef.optional(),
@@ -8140,10 +8258,10 @@ var videoGenerateNode = delegated({
8140
8258
  * exclusive with `first_frame` — OpenRouter treats a request carrying both
8141
8259
  * as image-to-video and ignores these, so wire one or the other.
8142
8260
  */
8143
- references: z28.array(ImageRef).optional()
8261
+ references: z29.array(ImageRef).optional()
8144
8262
  }).loose(),
8145
8263
  params: VideoGenerateParams,
8146
- outputs: z28.object({ video: VideoRef }).strict(),
8264
+ outputs: z29.object({ video: VideoRef }).strict(),
8147
8265
  outputKinds: { video: "video" },
8148
8266
  // Priced from the shared contract, so `validate`'s quote and the charge the
8149
8267
  // backend applies come from ONE table. A flat number here under-quoted a
@@ -8163,11 +8281,11 @@ var videoGenerateNode = delegated({
8163
8281
  });
8164
8282
 
8165
8283
  // src/engine/nodes/remote/videoBackgroundRemove.ts
8166
- import { z as z29 } from "zod";
8167
- var VideoBackgroundRemoveParams = z29.object({
8168
- model: z29.literal("fal/veed-video-background-removal").optional().default("fal/veed-video-background-removal"),
8169
- edge_refinement: z29.boolean().optional().default(true),
8170
- 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")
8171
8289
  }).strict();
8172
8290
  var videoBackgroundRemoveNode = delegated({
8173
8291
  id: "video_background_remove",
@@ -8175,18 +8293,18 @@ var videoBackgroundRemoveNode = delegated({
8175
8293
  category: "video",
8176
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`.",
8177
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.",
8178
- inputs: z29.object({
8296
+ inputs: z30.object({
8179
8297
  video: VideoRef
8180
8298
  }).strict(),
8181
8299
  params: VideoBackgroundRemoveParams,
8182
- outputs: z29.object({ video: VideoRef }).strict(),
8300
+ outputs: z30.object({ video: VideoRef }).strict(),
8183
8301
  outputKinds: { video: "video" },
8184
8302
  // $0.012 per 30 frames (edge refinement on) — assume ~30fps; refine via fal dashboard.
8185
8303
  cost: () => ({ credits: 50, seconds_estimate: 60 })
8186
8304
  });
8187
8305
 
8188
8306
  // src/engine/nodes/remote/videoDeconstruct.ts
8189
- import { z as z30 } from "zod";
8307
+ import { z as z31 } from "zod";
8190
8308
  var VIDEO_DECONSTRUCT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
8191
8309
  var videoDeconstructNode = delegated({
8192
8310
  id: "video_deconstruct",
@@ -8194,34 +8312,34 @@ var videoDeconstructNode = delegated({
8194
8312
  category: "video",
8195
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).',
8196
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.',
8197
- inputs: z30.object({ video: VideoRef }).loose(),
8198
- params: z30.object({
8199
- model: z30.enum(VIDEO_DECONSTRUCT_MODELS),
8200
- mode: z30.enum(["full", "index"]).optional(),
8201
- language: z30.string().min(2).max(8).optional(),
8202
- max_scenes: z30.number().int().min(1).max(60).optional(),
8203
- focus: z30.string().optional(),
8204
- start_s: z30.number().min(0).optional(),
8205
- 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(),
8206
8324
  // Real visual shot-cut timestamps (absolute seconds), detected locally with
8207
8325
  // ffmpeg before the deconstruct. The backend SNAPS its LLM scene boundaries
8208
8326
  // onto these and SPLITS any scene that spans one, so a scene's frames never
8209
8327
  // straddle a hard cut. `scaffold-video` populates this; omit for LLM-only cuts.
8210
- shot_cuts: z30.array(z30.number().min(0)).max(200).optional(),
8328
+ shot_cuts: z31.array(z31.number().min(0)).max(200).optional(),
8211
8329
  // The video model's per-clip ceiling (seconds). A shot longer than this is
8212
8330
  // split into seamless continuation sub-scenes (shared splice frame), so long
8213
8331
  // shots reproduce in full instead of being truncated. `scaffold-video` sets
8214
8332
  // the Seedance ceiling (15); omit to disable length splitting.
8215
- max_clip_s: z30.number().positive().max(60).optional(),
8333
+ max_clip_s: z31.number().positive().max(60).optional(),
8216
8334
  // Transcript provider for the blueprint's dialogue/transcript. Default
8217
8335
  // Groq Whisper; "deepgram" routes to Nova-3 so words carry punctuation.
8218
- transcriber: z30.enum(["groq", "deepgram"]).optional()
8336
+ transcriber: z31.enum(["groq", "deepgram"]).optional()
8219
8337
  }).strict(),
8220
- outputs: z30.object({
8338
+ outputs: z31.object({
8221
8339
  analysis: JsonRef,
8222
8340
  // Absent in mode:"index" (structure only, no Mux frame extraction).
8223
- start_frames: z30.array(ImageRef).min(1).optional(),
8224
- 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(),
8225
8343
  transcript: JsonRef
8226
8344
  }).strict(),
8227
8345
  outputKinds: { analysis: "json", start_frames: "image", end_frames: "image", transcript: "json" },
@@ -8229,22 +8347,22 @@ var videoDeconstructNode = delegated({
8229
8347
  });
8230
8348
 
8231
8349
  // src/engine/nodes/remote/videoLipsync.ts
8232
- import { z as z31 } from "zod";
8233
- var FalLipsyncParams = z31.object({
8234
- 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")
8235
8353
  }).strict();
8236
- var VideoLipsyncParams = z31.discriminatedUnion("model", [FalLipsyncParams]);
8354
+ var VideoLipsyncParams = z32.discriminatedUnion("model", [FalLipsyncParams]);
8237
8355
  var videoLipsyncNode = delegated({
8238
8356
  id: "video_lipsync",
8239
8357
  version: "1.0.0",
8240
8358
  category: "video",
8241
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.",
8242
- inputs: z31.object({
8360
+ inputs: z32.object({
8243
8361
  video: VideoRef,
8244
8362
  audio: AudioRef
8245
8363
  }).strict(),
8246
8364
  params: VideoLipsyncParams,
8247
- outputs: z31.object({ video: VideoRef }).strict(),
8365
+ outputs: z32.object({ video: VideoRef }).strict(),
8248
8366
  outputKinds: { video: "video" },
8249
8367
  cost: () => ({ credits: 20, seconds_estimate: 120 })
8250
8368
  });
@@ -8253,7 +8371,7 @@ var videoLipsyncNode = delegated({
8253
8371
  import { mkdtemp as mkdtemp6, readFile as readFile11, rm as rm6 } from "fs/promises";
8254
8372
  import { tmpdir as tmpdir6 } from "os";
8255
8373
  import path14 from "path";
8256
- import { z as z32 } from "zod";
8374
+ import { z as z33 } from "zod";
8257
8375
 
8258
8376
  // src/engine/nodes/local/lib/ffmpeg.ts
8259
8377
  import { execFile as execFile7 } from "child_process";
@@ -8332,24 +8450,24 @@ ${detail.slice(-4e3)}`);
8332
8450
  }
8333
8451
 
8334
8452
  // src/engine/nodes/remote/videoTranscribe.ts
8335
- var VideoTranscribeParams = z32.object({
8336
- language: z32.string().min(2).max(8).optional(),
8453
+ var VideoTranscribeParams = z33.object({
8454
+ language: z33.string().min(2).max(8).optional(),
8337
8455
  // Provider choice is explicit (no env-based silent branching). Default Groq
8338
8456
  // Whisper; "deepgram" routes to Deepgram Nova-3, which additionally emits a
8339
8457
  // `rich` JSON output with punctuated words + paragraph/sentence grouping.
8340
- transcriber: z32.enum(["groq", "deepgram"]).optional()
8458
+ transcriber: z33.enum(["groq", "deepgram"]).optional()
8341
8459
  }).strict();
8342
- var VideoTranscribeInputs = z32.object({
8460
+ var VideoTranscribeInputs = z33.object({
8343
8461
  // A video (audio auto-extracted locally) OR a bare audio track. The key stays
8344
8462
  // `video` for back-compat; the backend already accepts audio-kind refs on it —
8345
8463
  // the local extraction path has been shipping one for every video input.
8346
- video: z32.union([VideoRef, AudioRef])
8464
+ video: z33.union([VideoRef, AudioRef])
8347
8465
  }).strict();
8348
- var VideoTranscribeOutputs = z32.object({
8349
- transcript: z32.custom(),
8466
+ var VideoTranscribeOutputs = z33.object({
8467
+ transcript: z33.custom(),
8350
8468
  // Only emitted by the Deepgram path: full punctuated words + paragraph /
8351
8469
  // sentence grouping with speaker indices. Absent for the default Groq path.
8352
- rich: z32.custom().optional()
8470
+ rich: z33.custom().optional()
8353
8471
  }).strict();
8354
8472
  var AUDIO_EXTRACT_TIMEOUT_MS = 6e4;
8355
8473
  var videoTranscribeNode = defineNode({
@@ -8434,29 +8552,29 @@ async function tryExtractAudio(inputs, ctx) {
8434
8552
  }
8435
8553
 
8436
8554
  // src/engine/nodes/remote/voiceSelect.ts
8437
- import { z as z33 } from "zod";
8555
+ import { z as z34 } from "zod";
8438
8556
  var voiceSelectNode = delegated({
8439
8557
  id: "voice_select",
8440
8558
  version: "1.0.0",
8441
8559
  category: "audio",
8442
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.',
8443
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.',
8444
- inputs: z33.object({}).loose(),
8445
- params: z33.object({
8446
- description: z33.string().min(1),
8447
- gender: z33.string().optional(),
8448
- age: z33.string().optional(),
8449
- accent: z33.string().optional(),
8450
- language: z33.string().optional(),
8451
- 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()
8452
8570
  }).strict(),
8453
- outputs: z33.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
8571
+ outputs: z34.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
8454
8572
  outputKinds: { voice_id: "text", candidates: "json" },
8455
8573
  cost: () => ({ credits: 0, seconds_estimate: 5 })
8456
8574
  });
8457
8575
 
8458
8576
  // src/engine/schema/catalog.ts
8459
- import { z as z34 } from "zod";
8577
+ import { z as z35 } from "zod";
8460
8578
  function generateCatalog(registry, opts = {}) {
8461
8579
  const entries = registry.all().map((def) => {
8462
8580
  const cost = def.cost ? safeCost(def) : void 0;
@@ -8467,9 +8585,9 @@ function generateCatalog(registry, opts = {}) {
8467
8585
  summary: def.summary,
8468
8586
  when_to_use: def.when_to_use,
8469
8587
  location: def.location,
8470
- inputs: z34.toJSONSchema(def.inputs, { unrepresentable: "any" }),
8471
- params: z34.toJSONSchema(def.params, { unrepresentable: "any" }),
8472
- 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" }),
8473
8591
  cost_estimate_credits: cost?.credits,
8474
8592
  runtime_estimate_seconds: cost?.seconds_estimate
8475
8593
  };
@@ -8567,7 +8685,8 @@ var REMOTE_NODES = [
8567
8685
  videoBackgroundRemoveNode,
8568
8686
  videoDeconstructNode,
8569
8687
  voiceSelectNode,
8570
- imageBackgroundRemoveNode
8688
+ imageBackgroundRemoveNode,
8689
+ imageLayerizeNode
8571
8690
  ];
8572
8691
  function defaultRegistry() {
8573
8692
  const r = new NodeRegistry();
@@ -8634,6 +8753,7 @@ export {
8634
8753
  REF_PREFIX,
8635
8754
  parseRefExpr,
8636
8755
  sha256Hex,
8756
+ MAX_ASSET_BYTES,
8637
8757
  looksLikeHttpUrl,
8638
8758
  SEEDANCE_PROFILE,
8639
8759
  clipProfileFor,
@@ -8642,6 +8762,7 @@ export {
8642
8762
  spineInputFlags,
8643
8763
  spineInputOps,
8644
8764
  elementMentionKeywords,
8765
+ MAX_REMOTE_IMAGE_BYTES,
8645
8766
  fetchExternalBytes,
8646
8767
  toModelSafeImage,
8647
8768
  BackendClient2,
@@ -8654,4 +8775,4 @@ export {
8654
8775
  defaultRegistry,
8655
8776
  createEngineFromEnv
8656
8777
  };
8657
- //# sourceMappingURL=chunk-CVJCXDWD.js.map
8778
+ //# sourceMappingURL=chunk-5MPIOGRO.js.map