@koda-sl/baker-cli 0.300.0-dev.e0610b71d → 0.300.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 +68 -87
  2. package/canvas/tiktok-captions-composition/index.html +2 -23
  3. package/dist/{chunk-2IUN3S3E.js → chunk-2IB3IL4K.js} +2 -2
  4. package/dist/{chunk-4YF56EGK.js → chunk-D3HHUBIL.js} +4 -4
  5. package/dist/{chunk-WFWU3CHS.js → chunk-DZUVUGEP.js} +1 -9
  6. package/dist/{chunk-WFWU3CHS.js.map → chunk-DZUVUGEP.js.map} +1 -1
  7. package/dist/{chunk-A2VZOTCV.js → chunk-EX2OIOVL.js} +3 -3
  8. package/dist/{chunk-KDTHRRAC.js → chunk-GU7IWEBC.js} +3 -3
  9. package/dist/{chunk-FENRH7OF.js → chunk-YFQWCJX4.js} +123 -412
  10. package/dist/chunk-YFQWCJX4.js.map +1 -0
  11. package/dist/cli.js +1725 -2854
  12. package/dist/cli.js.map +1 -1
  13. package/dist/{client-6KQHCXS2.js → client-VDCCDEHE.js} +4 -4
  14. package/dist/engine/index.d.ts +0 -13
  15. package/dist/engine/index.js +3 -3
  16. package/dist/{env-C7YXL6C6.js → env-FWMZXMQK.js} +2 -6
  17. package/dist/{output-RBM32FKJ.js → output-4H2RNBXL.js} +5 -5
  18. package/dist/{shared-WJIJTWST.js → shared-UKL36AXT.js} +6 -6
  19. package/package.json +2 -5
  20. package/dist/chunk-FENRH7OF.js.map +0 -1
  21. /package/dist/{chunk-2IUN3S3E.js.map → chunk-2IB3IL4K.js.map} +0 -0
  22. /package/dist/{chunk-4YF56EGK.js.map → chunk-D3HHUBIL.js.map} +0 -0
  23. /package/dist/{chunk-A2VZOTCV.js.map → chunk-EX2OIOVL.js.map} +0 -0
  24. /package/dist/{chunk-KDTHRRAC.js.map → chunk-GU7IWEBC.js.map} +0 -0
  25. /package/dist/{client-6KQHCXS2.js.map → client-VDCCDEHE.js.map} +0 -0
  26. /package/dist/{env-C7YXL6C6.js.map → env-FWMZXMQK.js.map} +0 -0
  27. /package/dist/{output-RBM32FKJ.js.map → output-4H2RNBXL.js.map} +0 -0
  28. /package/dist/{shared-WJIJTWST.js.map → shared-UKL36AXT.js.map} +0 -0
@@ -1,25 +1,24 @@
1
1
  import {
2
2
  debugLogHttp,
3
3
  readBodyForLog
4
- } from "./chunk-2IUN3S3E.js";
4
+ } from "./chunk-2IB3IL4K.js";
5
5
  import {
6
6
  DIRECT_ROUTE,
7
7
  __commonJS,
8
8
  __toESM,
9
9
  captureProxyCredentials,
10
10
  childEnvWith,
11
- hasFrameVisionKey,
12
11
  isChallengeBody,
13
12
  isPrivateAddress,
14
13
  isProxyFailure,
15
14
  plannedRoutes,
16
15
  refuseNonPublicUrl,
17
16
  shouldEscalate
18
- } from "./chunk-WFWU3CHS.js";
17
+ } from "./chunk-DZUVUGEP.js";
19
18
 
20
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/88b6d0c76c4dff408de4044be85791896193f2435af7c280bc59312c3fb9649c/node_modules/safe-stable-stringify/index.js
19
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
21
20
  var require_safe_stable_stringify = __commonJS({
22
- "../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/88b6d0c76c4dff408de4044be85791896193f2435af7c280bc59312c3fb9649c/node_modules/safe-stable-stringify/index.js"(exports, module) {
21
+ "../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
23
22
  "use strict";
24
23
  var { hasOwnProperty } = Object.prototype;
25
24
  var stringify = configure2();
@@ -758,6 +757,19 @@ async function parseErrorBody(res) {
758
757
  }
759
758
  return classifyHttpError(res.status, errObj, message2);
760
759
  }
760
+ function providerVerdict(status, errObj, message2) {
761
+ if (errObj.provider === void 0 && errObj.code !== "provider_error") return null;
762
+ return {
763
+ kind: "provider",
764
+ status,
765
+ provider: errObj.provider,
766
+ code: errObj.code ?? "provider_error",
767
+ message: message2,
768
+ // A 4xx is terminal unless the backend says otherwise — the opposite
769
+ // default from the 502 branch, where the status itself means "try again".
770
+ retryable: errObj.retryable ?? false
771
+ };
772
+ }
761
773
  function classifyHttpError(status, errObj, message2) {
762
774
  if (errObj.code === CONTENT_POLICY_CODE) {
763
775
  return { kind: "content_policy", status, provider: errObj.provider, message: message2 };
@@ -766,7 +778,7 @@ function classifyHttpError(status, errObj, message2) {
766
778
  return { kind: "unauthorized", status, message: message2 };
767
779
  }
768
780
  if (status === 400 || status === 422) {
769
- return { kind: "validation", status, message: message2, details: errObj.details };
781
+ return providerVerdict(status, errObj, message2) ?? { kind: "validation", status, message: message2, details: errObj.details };
770
782
  }
771
783
  if (status === 502 || status === 504) {
772
784
  if (errObj.code === "provider_timeout" || status === 504) {
@@ -1034,17 +1046,12 @@ function failureDetail(reason) {
1034
1046
  }
1035
1047
  return { message: describeFailureReason(reason) };
1036
1048
  }
1037
- function retryVerdict(retryable) {
1038
- if (retryable === true) return "transient \u2014 retry the run";
1039
- if (retryable === false) return "not retryable \u2014 retrying will not help, fix the input";
1040
- return "retryability unknown";
1041
- }
1042
1049
  function describeCause(c) {
1043
1050
  switch (c.kind) {
1044
1051
  case "local":
1045
1052
  return c.cause instanceof Error ? c.cause.message : String(c.cause);
1046
1053
  case "remote":
1047
- return `[${c.code}] ${c.message}${c.provider ? ` (${c.provider})` : ""} \u2014 ${retryVerdict(c.retryable)}`;
1054
+ return `[${c.code}] ${c.message}${c.provider ? ` (${c.provider})` : ""}`;
1048
1055
  case "timeout":
1049
1056
  return `timeout${c.provider ? ` (${c.provider})` : ""}`;
1050
1057
  case "network":
@@ -1098,7 +1105,7 @@ function resolveAdaptFormats(params) {
1098
1105
  return params.formats ?? [];
1099
1106
  }
1100
1107
 
1101
- // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/88b6d0c76c4dff408de4044be85791896193f2435af7c280bc59312c3fb9649c/node_modules/safe-stable-stringify/esm/wrapper.js
1108
+ // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
1102
1109
  var import__ = __toESM(require_safe_stable_stringify(), 1);
1103
1110
  var configure = import__.default.configure;
1104
1111
  var wrapper_default = import__.default;
@@ -1139,14 +1146,6 @@ function normalizeForCanonical(value) {
1139
1146
  return void 0;
1140
1147
  }
1141
1148
 
1142
- // ../canvas-contract/src/frameRealism.ts
1143
- function frameRealismDirection(opts = {}) {
1144
- return " Everything obeys real-world physics: paper, card and screens are OPAQUE with nothing showing through from behind, every object is at believable real-world scale next to the people handling it, and every object has its real-world form and construction \u2014 a phone has ONE screen and it is on the front. NO readable text or numbers anywhere in frame \u2014 phone screens, documents and signage stay illegible or out of focus, because any figure the model invents will contradict the script. Hands are kept simple: no close-up of fingers manipulating small parts, no hand gripping the edge of an object, and each person has exactly TWO arms and TWO legs, all attached and all visible or all out of frame. Anyone working does so the way the trade actually does it: nobody stands or kneels on the equipment being installed, nothing is fitted overhanging an edge or floating unsupported, and every part rests on the structure that would really carry it." + // The newest clause, and the one no route had. A testimonial came back with the customer
1145
- // repairing the panel herself: nothing said she was not the installer, so the model cast
1146
- // her as one. Who someone IS in the picture has to be stated, or it is guessed.
1147
- (opts.role ? ` The person on camera is ${opts.role} \u2014 they are shown as that and never doing somebody else's job.` : "") + (opts.currency ? ` If a currency is unavoidably visible it is ${opts.currency}.` : "");
1148
- }
1149
-
1150
1149
  // ../canvas-contract/src/registry.ts
1151
1150
  var OPENROUTER_IMAGE_AR = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
1152
1151
  var OPENROUTER_IMAGE_AR_EXTREME = [...OPENROUTER_IMAGE_AR, "1:4", "4:1", "1:8", "8:1"];
@@ -1166,7 +1165,6 @@ var OPENROUTER_IMAGE_SIZES = ["0.5K", ...OPENROUTER_IMAGE_SIZE];
1166
1165
  var OPENROUTER_IMAGE_SIZE_EXTENDED = OPENROUTER_IMAGE_SIZES;
1167
1166
  var GEMINI_LITE_IMAGE_SIZE = ["1K"];
1168
1167
  var OPENROUTER_IMAGE_QUALITY = ["auto", "low", "medium", "high"];
1169
- var OPENROUTER_IMAGE_QUALITY_25 = ["auto", "low", "medium", "high", "xhigh", "max"];
1170
1168
  var RECRAFT_IMAGE_AR = ["1:1", "4:3", "3:4", "16:9", "9:16"];
1171
1169
  var SEEDANCE_DURATIONS = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
1172
1170
  var SEEDANCE_25_DURATIONS = [
@@ -1211,39 +1209,6 @@ var GEMINI_OMNI_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp"];
1211
1209
  var GROK_IMAGINE_DURATIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
1212
1210
  var WAN_27_DURATIONS = [2, 3, 4, 5, 6, 7, 8, 9, 10];
1213
1211
  var HAPPYHORSE_DURATIONS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
1214
- var WAN_30_DURATIONS = [
1215
- 2,
1216
- 3,
1217
- 4,
1218
- 5,
1219
- 6,
1220
- 7,
1221
- 8,
1222
- 9,
1223
- 10,
1224
- 11,
1225
- 12,
1226
- 13,
1227
- 14,
1228
- 15,
1229
- 16,
1230
- 17,
1231
- 18,
1232
- 19,
1233
- 20,
1234
- 21,
1235
- 22,
1236
- 23,
1237
- 24,
1238
- 25,
1239
- 26,
1240
- 27,
1241
- 28,
1242
- 29,
1243
- 30
1244
- ];
1245
- var GROK_IMAGINE_15_DURATIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
1246
- var RUNWAY_45_DURATIONS = [2, 3, 4, 5, 6, 7, 8, 9, 10];
1247
1212
  var ELEVENLABS_OUTPUT_FORMATS = [
1248
1213
  "mp3_22050_32",
1249
1214
  "mp3_44100_32",
@@ -1262,7 +1227,6 @@ var REPLICATE_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
1262
1227
  var DECONSTRUCT_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
1263
1228
  var REPLICATE_AUDIO_MIMES = ["audio/wav", "audio/mpeg", "audio/mp3"];
1264
1229
  var IMAGE_GENERATE_MODELS = [
1265
- "openai/gpt-image-2.5-sunburst",
1266
1230
  "openai/gpt-image-2",
1267
1231
  "openai/gpt-5.4-image-2",
1268
1232
  "google/gemini-3.1-flash-image-preview",
@@ -1281,10 +1245,7 @@ var VIDEO_GENERATE_MODELS = [
1281
1245
  "google/veo-3.1-lite",
1282
1246
  "kwaivgi/kling-v3.0-pro",
1283
1247
  "x-ai/grok-imagine-video",
1284
- "x-ai/grok-imagine-video-1.5",
1285
1248
  "alibaba/wan-2.7",
1286
- "alibaba/wan-3.0",
1287
- "runway/gen-4.5",
1288
1249
  "alibaba/happyhorse-1.1"
1289
1250
  ];
1290
1251
  var DEFAULT_VIDEO_GENERATE_MODEL = "google/gemini-omni-1.1-flash";
@@ -1411,20 +1372,6 @@ var MODEL_REGISTRY = {
1411
1372
  // images as a side effect. Notable capability gaps vs the Gemini entries:
1412
1373
  // no `image_size` (OpenAI picks the pixel dimensions from the ratio, and a
1413
1374
  // `resolution` is silently ignored) and the narrower `..._AR_GPT` ratio set.
1414
- "openai/gpt-image-2.5-sunburst": {
1415
- // Identical to the gpt-image-2 entry below except for the quality ladder — the
1416
- // ratio set, the 16-reference ceiling, the missing `image_size` and the reasons
1417
- // `background` / `output_compression` stay undeclared all carry over unchanged.
1418
- label: "OpenAI GPT Image 2.5",
1419
- inputs: [],
1420
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GPT_IMAGE_MAX_REFERENCES }],
1421
- required: ["prompt"],
1422
- params: {
1423
- prompt: { kind: "string", maxLength: GPT_IMAGE_PROMPT_MAX },
1424
- aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR_GPT },
1425
- quality: { kind: "string", enum: OPENROUTER_IMAGE_QUALITY_25 }
1426
- }
1427
- },
1428
1375
  "openai/gpt-image-2": {
1429
1376
  // Its live endpoint also advertises `background` (auto|opaque) and
1430
1377
  // `output_compression` (0-100), which we deliberately do not declare. Not an
@@ -1864,67 +1811,6 @@ var MODEL_REGISTRY = {
1864
1811
  generate_audio: { kind: "boolean" }
1865
1812
  }
1866
1813
  },
1867
- "x-ai/grok-imagine-video-1.5": {
1868
- // NOT a replacement for the entry above, and deliberately absent from
1869
- // `AVATAR_VIDEO_MODELS` and `REFERENCE_TO_VIDEO_MODELS`: OpenRouter describes the
1870
- // older Grok as "text-, image-, and reference-conditioned" and 1.5 as taking "an
1871
- // optional starting image". Reference conditioning is the whole reason Grok carries
1872
- // avatars — it is what holds a face from the identity sheet — so swapping them
1873
- // would quietly cost every avatar clip its likeness.
1874
- //
1875
- // What it adds over the older one: 1080p, and 15s rather than 10.
1876
- label: "Grok Imagine Video 1.5",
1877
- inputs: [],
1878
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1879
- required: ["prompt"],
1880
- params: {
1881
- prompt: { kind: "string" },
1882
- aspect_ratio: { kind: "string", enum: ["16:9", "9:16", "1:1", "4:3", "3:4", "3:2", "2:3"] },
1883
- resolution: { kind: "string", enum: ["480p", "720p", "1080p"] },
1884
- duration: { kind: "number", enum: GROK_IMAGINE_15_DURATIONS }
1885
- }
1886
- },
1887
- "alibaba/wan-3.0": {
1888
- // 2.7's successor on every axis that matters: 2–30s against 2–10, 1080p and 480p
1889
- // as well as 720p, and it keeps the native audio that made 2.7 the landscape
1890
- // talking-head option.
1891
- //
1892
- // The ratios are what it ADVERTISES. 2.7 advertised five and delivered 16:9 for
1893
- // all of them — measured, twice, which is why its enum here is a single value. That
1894
- // was a different model and this one is not gated on its predecessor's lie, but
1895
- // the first vertical clip anyone renders here is worth looking at before a campaign
1896
- // is built on it.
1897
- //
1898
- // No `last_frame`: 2.7 takes one and 3.0 does not, so a canvas that ends a shot on
1899
- // a chosen frame stays on 2.7.
1900
- label: "Alibaba Wan 3.0",
1901
- inputs: [],
1902
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1903
- required: ["prompt"],
1904
- params: {
1905
- prompt: { kind: "string" },
1906
- aspect_ratio: { kind: "string", enum: ["16:9", "9:16", "1:1", "4:3", "3:4"] },
1907
- resolution: { kind: "string", enum: ["480p", "720p", "1080p"] },
1908
- duration: { kind: "number", enum: WAN_30_DURATIONS },
1909
- seed: { kind: "number" },
1910
- generate_audio: { kind: "boolean" }
1911
- }
1912
- },
1913
- "runway/gen-4.5": {
1914
- // One resolution, two shapes, and silent — `generate_audio: false` in its listing,
1915
- // so a talking head belongs on Wan or Omni. First frame only, no reference.
1916
- label: "Runway Gen-4.5",
1917
- inputs: [],
1918
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1919
- required: ["prompt"],
1920
- params: {
1921
- prompt: { kind: "string" },
1922
- aspect_ratio: { kind: "string", enum: ["16:9", "9:16"] },
1923
- resolution: { kind: "string", enum: ["720p"] },
1924
- duration: { kind: "number", enum: RUNWAY_45_DURATIONS },
1925
- seed: { kind: "number" }
1926
- }
1927
- },
1928
1814
  "alibaba/happyhorse-1.1": {
1929
1815
  // Third reference-capable option: strong identity, correct ratios, no audio.
1930
1816
  // First-frame only (no `last_frame`), so it cannot close a beat on a
@@ -2140,34 +2026,12 @@ function supportsParam(kind, model, param) {
2140
2026
  return MODEL_REGISTRY[kind]?.[model]?.params[param] !== void 0;
2141
2027
  }
2142
2028
  var DEFAULT_CLIP_DURATION_S = 5;
2143
- var SPOKEN_WORDS_PER_SECOND = 2.5;
2144
- function spokenWordsIn(prompt) {
2145
- if (!prompt) return 0;
2146
- const quoted = [
2147
- ...prompt.matchAll(/['"\u201c\u201d\u2018\u2019]([^'"\u201c\u201d\u2018\u2019]{8,})['"\u201c\u201d\u2018\u2019]/g)
2148
- ].map((m) => m[1] ?? "");
2149
- const text = quoted.join(" ").trim();
2150
- return text ? text.split(/\s+/).filter(Boolean).length : 0;
2151
- }
2152
2029
  function nearestClipDuration(model, preferred) {
2153
2030
  const schema = MODEL_REGISTRY.video_generate[model]?.params.duration;
2154
2031
  const allowed = schema?.kind === "number" ? schema.enum : void 0;
2155
2032
  if (!allowed?.length || allowed.includes(preferred)) return preferred;
2156
2033
  return [...allowed].sort((a, b) => Math.abs(a - preferred) - Math.abs(b - preferred) || a - b)[0];
2157
2034
  }
2158
- function maxClipSeconds(model) {
2159
- const schema = MODEL_REGISTRY.video_generate[model]?.params.duration;
2160
- const allowed = schema?.kind === "number" ? schema.enum : void 0;
2161
- return allowed?.length ? Math.max(...allowed) : void 0;
2162
- }
2163
- function splitScriptFor(words, model) {
2164
- const maxSeconds = maxClipSeconds(model);
2165
- if (!maxSeconds) return null;
2166
- const perClip = Math.floor(maxSeconds * SPOKEN_WORDS_PER_SECOND);
2167
- if (words <= perClip) return null;
2168
- const clips = Math.ceil(words / perClip);
2169
- return { clips, wordsPerClip: Math.ceil(words / clips), maxSeconds };
2170
- }
2171
2035
  function promptMaxLength(kind, model) {
2172
2036
  const schema = MODEL_REGISTRY[kind]?.[model]?.params.prompt;
2173
2037
  return schema?.kind === "string" ? schema.maxLength : void 0;
@@ -2182,19 +2046,6 @@ function maxInputSlot(kind, model, inputKind) {
2182
2046
  function maxInputReferences(kind, model) {
2183
2047
  return maxInputSlot(kind, model, "image");
2184
2048
  }
2185
- function supportedQualities(kind, model) {
2186
- const schema = MODEL_REGISTRY[kind]?.[model]?.params.quality;
2187
- return schema?.kind === "string" ? schema.enum : void 0;
2188
- }
2189
- function bestSupportedQuality(kind, model, want) {
2190
- const supported = supportedQualities(kind, model);
2191
- if (!supported) return void 0;
2192
- if (supported.includes(want)) return want;
2193
- const ladder = supported.filter((step) => step !== "auto");
2194
- const ceiling = ladder.indexOf(want);
2195
- const reachable = ceiling === -1 ? ladder : ladder.slice(0, ceiling);
2196
- return reachable[reachable.length - 1];
2197
- }
2198
2049
  function supportedAspectRatios(kind, model) {
2199
2050
  const schema = MODEL_REGISTRY[kind]?.[model]?.params.aspect_ratio;
2200
2051
  return schema?.kind === "string" ? schema.enum : void 0;
@@ -3279,16 +3130,6 @@ function dfsCycle(u, color, stack, reverseAdj) {
3279
3130
  stack.pop();
3280
3131
  return null;
3281
3132
  }
3282
- function nodesBeforeCheckpoint(graph, types, stopKinds) {
3283
- const all = new Set(graph.keys());
3284
- if (!stopKinds?.size) return all;
3285
- const running = /* @__PURE__ */ new Set();
3286
- for (const layer of topologicalLayers(graph)) {
3287
- if (layer.some((id) => stopKinds.has(types.get(id) ?? ""))) return running;
3288
- for (const id of layer) running.add(id);
3289
- }
3290
- return running;
3291
- }
3292
3133
 
3293
3134
  // src/engine/engine/validator.ts
3294
3135
  import { readFile as readFile2 } from "fs/promises";
@@ -3307,7 +3148,7 @@ var VEO_DURATIONS = [4, 6, 8];
3307
3148
  var KLING_DURATIONS2 = [5, 10];
3308
3149
  var KLING_NEGATIVE_PROMPT = "warped face, distorted hands, extra fingers, morphing, flicker, on-screen text, watermark, low quality";
3309
3150
  var KLING_CFG_SCALE = 0.7;
3310
- var SPEAKS_PROSE = (line, accent) => `The person speaks to camera; lip-sync follows the dialogue verbatim, with delivery and emotion carried in the wording itself (no bracketed cues).${accent ? ` The speech is in ${accent}.` : ""} Dialogue: "${line}"`;
3151
+ var SPEAKS_PROSE = (line) => `The person speaks to camera; lip-sync follows the dialogue verbatim, with delivery and emotion carried in the wording itself (no bracketed cues). Dialogue: "${line}"`;
3311
3152
  var SEEDANCE_PROFILE = {
3312
3153
  id: "seedance",
3313
3154
  dialogueDirective: SPEAKS_PROSE,
@@ -3408,14 +3249,9 @@ var GPT_IMAGE_PROFILE = {
3408
3249
  id: "gpt-image",
3409
3250
  constraintPlacement: "last",
3410
3251
  photorealCue: true,
3411
- // OpenRouter forwards `quality`; gpt-image already processes inputs at high fidelity
3412
- // automatically, so we deliberately do NOT send `input_fidelity`.
3413
- //
3414
- // No `quality` here any more: it is the one default that depends on WHICH gpt-image is
3415
- // pinned. 2.5 added `xhigh` and `max` above `high`, and the older entries would refuse
3416
- // them at validate — so callers ask for the step they want through
3417
- // `bestSupportedQuality` and get the best the chosen model actually has.
3418
- paramDefaults: {}
3252
+ // OpenRouter forwards `quality`; gpt-image-2 already processes inputs at high
3253
+ // fidelity automatically, so we deliberately do NOT send `input_fidelity`.
3254
+ paramDefaults: { quality: "high" }
3419
3255
  };
3420
3256
  var GEMINI_IMAGE_PROFILE = {
3421
3257
  id: "gemini",
@@ -3926,19 +3762,6 @@ function checkSlotsForNode(ctx, n, _i) {
3926
3762
  });
3927
3763
  }
3928
3764
  }
3929
- function estimateCreditsFor(canvas, registry, ids) {
3930
- let total = 0;
3931
- for (const n of canvas.nodes) {
3932
- if (!ids.has(n.id)) continue;
3933
- const def = registry.get(n.type);
3934
- if (!def?.cost) continue;
3935
- try {
3936
- total += def.cost({ params: def.params.parse(n.params ?? {}) }).credits;
3937
- } catch {
3938
- }
3939
- }
3940
- return total;
3941
- }
3942
3765
  function estimateCredits(ctx) {
3943
3766
  let total = 0;
3944
3767
  for (const n of ctx.canvas.nodes) {
@@ -4245,7 +4068,7 @@ function checkAudioTimelineTotals(ctx, audioMixNode, expected) {
4245
4068
  if (audioMixNode) mustBeFullLength.add(audioMixNode);
4246
4069
  ctx.canvas.nodes.forEach((n) => {
4247
4070
  if (n.type !== "audio_voice_convert") return;
4248
- const track = refNodeOf(ctx, n.inputs?.audio);
4071
+ const track = refNodeOf(ctx, (n.inputs ?? {}).audio);
4249
4072
  if (track?.type === "audio_timeline") mustBeFullLength.add(track.id);
4250
4073
  });
4251
4074
  for (const id of mustBeFullLength) {
@@ -4266,11 +4089,11 @@ function checkAudioTimelineTotals(ctx, audioMixNode, expected) {
4266
4089
  function checkNativeSegOverlap(ctx) {
4267
4090
  for (const conv of ctx.canvas.nodes) {
4268
4091
  if (conv.type !== "audio_voice_convert") continue;
4269
- const track = refNodeOf(ctx, conv.inputs?.audio);
4092
+ const track = refNodeOf(ctx, (conv.inputs ?? {}).audio);
4270
4093
  if (track?.type !== "audio_timeline") continue;
4271
4094
  const params = track.params;
4272
4095
  const windows = (params.tracks ?? []).map((t) => {
4273
- const extract = refNodeOf(ctx, track.inputs?.[t.slot]);
4096
+ const extract = refNodeOf(ctx, (track.inputs ?? {})[t.slot]);
4274
4097
  const len = t.duration_s ?? (extract ? ffmpegTrimSeconds(extract) : null);
4275
4098
  return len === null ? null : { slot: t.slot, start: t.start_s, end: t.start_s + len };
4276
4099
  }).filter((w) => w !== null).sort((a, b) => a.start - b.start);
@@ -4763,13 +4586,6 @@ function unwrap(schema) {
4763
4586
  }
4764
4587
 
4765
4588
  // src/engine/engine/executor.ts
4766
- function heldByCheckpoint(canvas, layer, kinds) {
4767
- if (!kinds?.size) return [];
4768
- return layer.filter((id) => {
4769
- const type = canvas.nodes.find((n) => n.id === id)?.type;
4770
- return type !== void 0 && kinds.has(type);
4771
- });
4772
- }
4773
4589
  var Engine = class {
4774
4590
  registry;
4775
4591
  client;
@@ -4797,7 +4613,7 @@ var Engine = class {
4797
4613
  async run(input, opts = {}) {
4798
4614
  const validation = await this.validateDeep(input);
4799
4615
  if (!validation.ok) throw new ValidationError(validation.issues);
4800
- if (!opts.stop_before_kinds?.size && opts.max_credits !== void 0 && validation.estimatedCredits > opts.max_credits) {
4616
+ if (opts.max_credits !== void 0 && validation.estimatedCredits > opts.max_credits) {
4801
4617
  throw new RunAbortedError(
4802
4618
  "cost_cap",
4803
4619
  `estimated ${validation.estimatedCredits} credits exceeds the ${opts.max_credits}-credit cap \u2014 nothing was billed; raise --max-credits or shrink the canvas`
@@ -4816,20 +4632,6 @@ var Engine = class {
4816
4632
  const counters = { cachedNodes: 0, totalCredits: 0 };
4817
4633
  const nodeRuns = [];
4818
4634
  const graph = this.pruneToOutput(canvas, buildGraph(canvas));
4819
- if (opts.max_credits !== void 0 && opts.stop_before_kinds?.size) {
4820
- const willRun = nodesBeforeCheckpoint(
4821
- graph,
4822
- new Map(canvas.nodes.map((n) => [n.id, n.type])),
4823
- opts.stop_before_kinds
4824
- );
4825
- const estimate = estimateCreditsFor(canvas, this.registry, willRun);
4826
- if (estimate > opts.max_credits) {
4827
- throw new RunAbortedError(
4828
- "cost_cap",
4829
- `estimated ${estimate} credits exceeds the ${opts.max_credits}-credit cap \u2014 nothing was billed; raise --max-credits or shrink the canvas`
4830
- );
4831
- }
4832
- }
4833
4635
  const needsBytes = computeNeedsLocalBytes(canvas, graph, this.registry);
4834
4636
  this.emitProgress(opts, {
4835
4637
  kind: "plan",
@@ -4874,13 +4676,6 @@ var Engine = class {
4874
4676
  `spent ${counters.totalCredits} credits, over the ${opts.max_credits}-credit cap \u2014 completed nodes are cached; raise --max-credits to continue where this stopped`
4875
4677
  );
4876
4678
  }
4877
- const held = heldByCheckpoint(canvas, layer, opts.stop_before_kinds);
4878
- if (held.length > 0) {
4879
- throw new RunAbortedError(
4880
- "checkpoint",
4881
- `stopped before ${held.join(", ")} \u2014 everything up to here is done and cached, so continuing this run id pays for none of it again`
4882
- );
4883
- }
4884
4679
  const settled = await mapWithConcurrency(layer, limit, (nodeId) => {
4885
4680
  if (opts.signal?.aborted) {
4886
4681
  return Promise.reject(new RunAbortedError("signal", "run aborted before node dispatch"));
@@ -6154,7 +5949,7 @@ function resolveStrategy(params) {
6154
5949
  case "text": {
6155
5950
  const p = safePathname(params.url);
6156
5951
  const isRawText = RAW_TEXT_EXTENSIONS.some((ext) => p.endsWith(ext));
6157
- return isRawText ? "direct_fetch" : "handinger";
5952
+ return isRawText ? "direct_fetch" : "firecrawl";
6158
5953
  }
6159
5954
  default: {
6160
5955
  params.expect;
@@ -6171,10 +5966,10 @@ function safePathname(rawUrl) {
6171
5966
  }
6172
5967
  var ingestNode = defineNode({
6173
5968
  id: "ingest",
6174
- version: "1.2.0",
5969
+ version: "1.3.0",
6175
5970
  category: "io",
6176
5971
  location: "local",
6177
- summary: "Ingest an external URL or a local file into the asset store. Declare the kind you expect (image/video/audio/text/json/font); the node picks the strategy. For source=url: yt-dlp for video/audio (YouTube/TikTok/Vimeo/etc. and direct file URLs), Handinger for HTML/PDF pages \u2192 markdown, direct HTTP fetch for binary URLs (images, fonts) and raw .txt/.md. For source=path: read from the local filesystem and upload to R2.",
5972
+ summary: "Ingest an external URL or a local file into the asset store. Declare the kind you expect (image/video/audio/text/json/font); the node picks the strategy. For source=url: yt-dlp for video/audio (YouTube/TikTok/Vimeo/etc. and direct file URLs), Firecrawl for HTML/PDF pages \u2192 markdown, direct HTTP fetch for binary URLs (images, fonts) and raw .txt/.md. For source=path: read from the local filesystem and upload to R2.",
6178
5973
  when_to_use: 'Use as the on-ramp for any external asset a canvas needs. `expect` makes the output port statically typed so downstream wiring is checked before any byte is fetched. Pick `source: "url"` for portable canvas; pick `source: "path"` for local files (canvas is not portable across machines without the file). Compose with other nodes for richer pipelines (e.g. `ingest expect=video` \u2192 `video_transcribe`).',
6179
5974
  inputs: IngestInputs,
6180
5975
  params: IngestParams,
@@ -6201,8 +5996,8 @@ function runStrategy(strategy, params, ctx) {
6201
5996
  switch (strategy) {
6202
5997
  case "direct_fetch":
6203
5998
  return execDirectFetch(params, ctx);
6204
- case "handinger":
6205
- return execHandinger(params, ctx);
5999
+ case "firecrawl":
6000
+ return execFirecrawl(params, ctx);
6206
6001
  case "yt_dlp":
6207
6002
  return execYtDlp(params, ctx);
6208
6003
  case "local_file":
@@ -6240,9 +6035,8 @@ async function ingestImageUrl(url, ctx) {
6240
6035
  } catch (e) {
6241
6036
  throw localExecError(ctx, `${url}: ${e.message}`);
6242
6037
  }
6243
- if (normalized.rasterizedFrom) {
6244
- ctx.log(`ingest: normalized ${normalized.rasterizedFrom} URL -> PNG (${normalized.bytes.length}B)`);
6245
- }
6038
+ const note = normalizationNote(normalized);
6039
+ if (note) ctx.log(`ingest: ${url} ${note}`);
6246
6040
  return uploadAndIngest({
6247
6041
  bytes: normalized.bytes,
6248
6042
  kind: "image",
@@ -6256,11 +6050,11 @@ async function ingestImageUrl(url, ctx) {
6256
6050
  ctx
6257
6051
  });
6258
6052
  }
6259
- async function execHandinger(params, ctx) {
6053
+ async function execFirecrawl(params, ctx) {
6260
6054
  const result = await callBackendExec({
6261
6055
  nodeType: "ingest",
6262
6056
  nodeVersion: ingestNode.version,
6263
- params: { strategy: "handinger", url: params.url },
6057
+ params: { strategy: "firecrawl", url: params.url },
6264
6058
  inputs: {},
6265
6059
  outputKinds: { asset: "text" },
6266
6060
  ctx
@@ -6352,23 +6146,69 @@ async function rasterizeSvgToPng(bytes) {
6352
6146
  return await sharp(bytes, { density }).png({ force: true, palette: false }).toBuffer();
6353
6147
  }
6354
6148
  var MODEL_SAFE_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
6355
- async function toModelSafeImage(bytes) {
6149
+ var MAX_MODEL_REFERENCE_BYTES = 30 * 1024 * 1024;
6150
+ async function fitWithinReferenceLimit(bytes, mime, maxBytes) {
6151
+ if (bytes.byteLength <= maxBytes) return { bytes, mime };
6152
+ const { default: sharp } = await import("sharp");
6153
+ const read = () => sharp(bytes, { animated: true });
6154
+ const meta = await read().metadata();
6155
+ const animated = (meta.pages ?? 1) > 1;
6156
+ const keepAlpha = meta.hasAlpha === true;
6157
+ const width = meta.width ?? 0;
6158
+ const encode = async (scale2) => {
6159
+ let pipeline2 = read();
6160
+ if (scale2 < 1 && width > 0) pipeline2 = pipeline2.resize({ width: Math.max(256, Math.round(width * scale2)) });
6161
+ if (animated && mime === "image/gif") return { bytes: await pipeline2.gif().toBuffer(), mime: "image/gif" };
6162
+ if (animated || keepAlpha) return { bytes: await pipeline2.webp({ quality: 90 }).toBuffer(), mime: "image/webp" };
6163
+ return { bytes: await pipeline2.jpeg({ quality: 90, mozjpeg: true }).toBuffer(), mime: "image/jpeg" };
6164
+ };
6165
+ let out = await encode(1);
6166
+ let scale = 1;
6167
+ for (let pass = 0; pass < 4 && out.bytes.byteLength > maxBytes; pass++) {
6168
+ scale *= Math.min(0.9, Math.sqrt(maxBytes / out.bytes.byteLength));
6169
+ out = await encode(scale);
6170
+ }
6171
+ return out;
6172
+ }
6173
+ async function toModelSafeImage(bytes, opts) {
6174
+ const maxBytes = opts?.maxBytes ?? MAX_MODEL_REFERENCE_BYTES;
6175
+ const fit = async (candidate, mime, rasterizedFrom) => {
6176
+ const fitted = await fitWithinReferenceLimit(candidate, mime, maxBytes);
6177
+ return {
6178
+ bytes: fitted.bytes,
6179
+ mime: fitted.mime,
6180
+ ...rasterizedFrom ? { rasterizedFrom } : {},
6181
+ ...fitted.bytes === candidate ? {} : { shrunkFromBytes: candidate.byteLength }
6182
+ };
6183
+ };
6356
6184
  const safe = sniffImageMime(bytes);
6357
6185
  if (safe && MODEL_SAFE_IMAGE_MIMES.has(safe)) {
6358
- return { bytes, mime: safe };
6186
+ return await fit(bytes, safe);
6359
6187
  }
6360
6188
  if (sniffSvg(bytes)) {
6361
- return { bytes: await rasterizeSvgToPng(bytes), mime: "image/png", rasterizedFrom: "svg" };
6189
+ return await fit(await rasterizeSvgToPng(bytes), "image/png", "svg");
6362
6190
  }
6363
6191
  const { default: sharp } = await import("sharp");
6192
+ let png;
6193
+ let format;
6364
6194
  try {
6365
6195
  const img = sharp(bytes);
6366
- const format = (await img.metadata()).format;
6367
- const png = await img.png({ force: true }).toBuffer();
6368
- return { bytes: png, mime: "image/png", rasterizedFrom: format ?? "unknown" };
6196
+ format = (await img.metadata()).format ?? "unknown";
6197
+ png = await img.png({ force: true }).toBuffer();
6369
6198
  } catch (e) {
6370
6199
  throw new Error(`bytes are not a decodable image (${e.message})`);
6371
6200
  }
6201
+ return await fit(png, "image/png", format);
6202
+ }
6203
+ function normalizationNote(image) {
6204
+ const parts = [];
6205
+ if (image.rasterizedFrom) parts.push(`normalized ${image.rasterizedFrom} -> ${image.mime}`);
6206
+ if (image.shrunkFromBytes !== void 0) {
6207
+ parts.push(
6208
+ `fitted ${image.shrunkFromBytes}B -> ${image.bytes.length}B as ${image.mime} (providers refuse a reference over ${MAX_MODEL_REFERENCE_BYTES}B)`
6209
+ );
6210
+ }
6211
+ return parts.length > 0 ? parts.join("; ") : null;
6372
6212
  }
6373
6213
  function hasAscii(buf, offset, sig) {
6374
6214
  return buf.length >= offset + sig.length && buf.toString("ascii", offset, offset + sig.length) === sig;
@@ -6498,7 +6338,8 @@ async function execLocalFile(params, ctx) {
6498
6338
  outBytes = normalized.bytes;
6499
6339
  outMime = normalized.mime;
6500
6340
  rasterizedFrom = normalized.rasterizedFrom;
6501
- if (rasterizedFrom) ctx.log(`ingest: normalized ${rasterizedFrom} -> PNG (${outBytes.length}B)`);
6341
+ const note = normalizationNote(normalized);
6342
+ if (note) ctx.log(`ingest: ${note}`);
6502
6343
  }
6503
6344
  const durationMs = probeVideoDurationMs(params.expect, outBytes, ctx);
6504
6345
  const ref = await uploadAndIngest({
@@ -7470,24 +7311,6 @@ import { execFile as execFile3 } from "child_process";
7470
7311
  import { readFile as readFile9 } from "fs/promises";
7471
7312
  import path12 from "path";
7472
7313
  import { promisify as promisify3 } from "util";
7473
-
7474
- // src/engine/nodes/local/lib/caption-style.ts
7475
- var TERMINAL = /[.,;:!?…]$/;
7476
- function captionTextsFrom(html) {
7477
- const cards = html.match(/<div[^>]*class="[^"]*\bcaption\b[^"]*"[\s\S]*?<\/div>/g) ?? [];
7478
- return cards.map(
7479
- (card) => card.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
7480
- ).filter((text) => text.length > 0);
7481
- }
7482
- function fragmentedCaptions(texts) {
7483
- if (texts.length < 2) return null;
7484
- const fragments = texts.slice(0, -1).filter((t) => !TERMINAL.test(t));
7485
- if (fragments.length === 0) return null;
7486
- const example = fragments[0];
7487
- return `${fragments.length} of ${texts.length} caption cards end mid-clause, e.g. "${example}". Split captions where the script punctuates, not on a word count \u2014 a card that ends bare is a sentence cut in half by a timing decision, and it reads as one.`;
7488
- }
7489
-
7490
- // src/engine/nodes/local/lib/hyperframe-check.ts
7491
7314
  var execFileAsync = promisify3(execFile3);
7492
7315
  var NEVER_BLOCK = [
7493
7316
  /contrast/i,
@@ -7556,74 +7379,6 @@ function classifyCheckOutput(lintRaw, inspectRaw) {
7556
7379
  warnings: findings.filter((f) => f.severity === "warning")
7557
7380
  };
7558
7381
  }
7559
- var GATE_SECTIONS = ["lint", "runtime", "layout", "motion", "contrast"];
7560
- function buildCheckArgs(dir, samples) {
7561
- return ["hyperframes", "check", dir, "--json", "--samples", String(samples), "--at-transitions"];
7562
- }
7563
- var PROMOTE_TO_BLOCKING = /GSAP target [^ ]+ not found|no deterministic font mapping/i;
7564
- function toGateFinding(section, entry) {
7565
- const f = entry;
7566
- const code = typeof f.code === "string" ? f.code : "unknown";
7567
- const message2 = typeof f.message === "string" ? f.message : "";
7568
- const isError = f.severity === "error" || PROMOTE_TO_BLOCKING.test(message2);
7569
- const fatal = PROMOTE_TO_BLOCKING.test(message2);
7570
- return {
7571
- source: section,
7572
- code,
7573
- message: message2,
7574
- severity: fatal || isError && !isAdvisory(code, message2) ? "blocking" : "warning"
7575
- };
7576
- }
7577
- function classifyGateOutput(raw) {
7578
- const parsed = parseCheckJson(raw);
7579
- const findings = GATE_SECTIONS.flatMap((section) => {
7580
- const block = parsed?.[section];
7581
- const entries = Array.isArray(block?.findings) ? block.findings : [];
7582
- return entries.map((entry) => toGateFinding(section, entry));
7583
- });
7584
- return {
7585
- blocking: findings.filter((f) => f.severity === "blocking"),
7586
- warnings: findings.filter((f) => f.severity === "warning")
7587
- };
7588
- }
7589
- function summariseWarnings(warnings) {
7590
- const groups = /* @__PURE__ */ new Map();
7591
- for (const w of warnings) {
7592
- const key = `${w.source}/${w.code}`;
7593
- const seen = groups.get(key);
7594
- if (seen) seen.count += 1;
7595
- else groups.set(key, { count: 1, message: w.message });
7596
- }
7597
- return [...groups.entries()].sort((a, b) => b[1].count - a[1].count).map(([key, g]) => `${g.count}\xD7 [${key}] ${g.message}`);
7598
- }
7599
- var FINDING_ADVICE = {
7600
- sweep_static: "the timeline never advanced, so this renders as a STILL IMAGE while billing like a video \u2014 and every other green verdict in this run is unreliable. Check the composition registers a paused timeline on `window.__timelines[<composition-id>]` and that the tweens target elements that exist.",
7601
- console_error: "the composition threw in the browser, so whatever that script was driving did not happen.",
7602
- page_error: "a script failed to evaluate \u2014 animations and any JS-driven layout in this composition did not run.",
7603
- http_error: "an asset 404ed at render time; it will be missing from the frame, not substituted.",
7604
- console_warning: "if this names a GSAP target, the element it animates does not exist, so that tween is a no-op and whatever it was meant to reveal never appears. Fix the selector or add the element before rendering."
7605
- };
7606
- function explainGateFinding(finding) {
7607
- const advice = FINDING_ADVICE[finding.code];
7608
- return advice ? `${finding.message} \u2014 ${advice}` : finding.message;
7609
- }
7610
- function condenseFrameDescriptions(markdown) {
7611
- const lines = [];
7612
- let frame = null;
7613
- for (const line of markdown.split("\n")) {
7614
- const heading = line.match(/^## (.+)$/);
7615
- if (heading) {
7616
- frame = heading[1] ?? null;
7617
- continue;
7618
- }
7619
- const text = line.trim();
7620
- if (frame && text) {
7621
- lines.push(`${frame}: ${text}`);
7622
- frame = null;
7623
- }
7624
- }
7625
- return lines;
7626
- }
7627
7382
  function buildLintArgs(dir) {
7628
7383
  return ["hyperframes", "lint", dir, "--json"];
7629
7384
  }
@@ -7655,13 +7410,8 @@ async function lintStructuralIssues(dir, timeoutMs) {
7655
7410
  function buildInspectArgs(dir, samples) {
7656
7411
  return ["hyperframes", "inspect", dir, "--json", "--samples", String(samples)];
7657
7412
  }
7658
- function buildSnapshotArgs(dir, frames, vision) {
7659
- const base = ["hyperframes", "snapshot", dir, "--frames", String(frames)];
7660
- return vision ? [
7661
- ...base,
7662
- "--describe",
7663
- "Answer four things about this frame. (1) Is it empty or near-empty? Name every element actually visible. (2) Could what is shown physically happen \u2014 object sizes, human scale, and whether every object has its real-world form (a phone has ONE screen, on its front). Say so plainly if something is impossible. (3) Read out every number, currency symbol and word visible INSIDE the picture \u2014 phone screens, documents, signage \u2014 and say whether any of it contradicts the caption on this frame. (4) Is the caption legible against what is behind it?"
7664
- ] : [...base, "--describe", "false"];
7413
+ function buildSnapshotArgs(dir, frames) {
7414
+ return ["hyperframes", "snapshot", dir, "--frames", String(frames), "--describe", "false"];
7665
7415
  }
7666
7416
  function usesNestedCompositions(indexHtml) {
7667
7417
  const withoutComments = indexHtml.replace(/<!--[\s\S]*?-->/g, "");
@@ -7690,66 +7440,44 @@ async function runSnapshotSmoke(args, timeoutMs) {
7690
7440
  return { ok: false, unavailable: false, message: (err.stderr || err.message || "snapshot failed").slice(0, 800) };
7691
7441
  }
7692
7442
  }
7693
- async function runNestedCompositionSmoke(opts) {
7694
- const { dir, nodeId, ctx, timeoutMs, samples } = opts;
7695
- const indexHtml = await readFile9(path12.join(dir, "index.html"), "utf-8").catch(() => "");
7696
- if (!indexHtml || !usesNestedCompositions(indexHtml)) return;
7697
- const snap = await runSnapshotSmoke(
7698
- buildSnapshotArgs(dir, Math.min(samples, 3), hasFrameVisionKey()),
7699
- Math.max(timeoutMs, 12e4)
7700
- );
7701
- if (snap.unavailable) {
7702
- ctx.log(`${nodeId}: hyperframes snapshot unavailable \u2014 skipping nested-composition smoke test`);
7703
- return;
7704
- }
7705
- if (!snap.ok) {
7706
- throw new Error(
7707
- `${nodeId}: nested-composition smoke test failed \u2014 an embedded block did not render. Check the host\u2194block id match, that the block's <style>/<script> live inside its <template>, and that it styles #root (not a class).
7708
- ${snap.message}`
7709
- );
7710
- }
7711
- const described = await readFile9(path12.join(dir, "snapshots", "descriptions.md"), "utf-8").catch(() => "");
7712
- for (const line of condenseFrameDescriptions(described)) {
7713
- ctx.log(`${nodeId}: frame check \u2014 ${line}`);
7714
- }
7715
- ctx.log(`${nodeId}: nested-composition smoke test passed`);
7716
- }
7717
- async function runGate(dir, timeoutMs, samples) {
7718
- const checkRaw = await runOne(buildCheckArgs(dir, samples), timeoutMs);
7719
- if (checkRaw !== null) return classifyGateOutput(checkRaw);
7443
+ async function runHyperframesCheck(opts) {
7444
+ const { dir, nodeId, ctx, timeoutMs, samples = 5 } = opts;
7720
7445
  const [lintRaw, inspectRaw] = await Promise.all([
7721
7446
  runOne(buildLintArgs(dir), timeoutMs),
7722
7447
  runOne(buildInspectArgs(dir, samples), timeoutMs)
7723
7448
  ]);
7724
- if (lintRaw === null && inspectRaw === null) return null;
7725
- return classifyCheckOutput(lintRaw ?? "", inspectRaw ?? "");
7726
- }
7727
- async function runHyperframesCheck(opts) {
7728
- const { dir, nodeId, ctx, timeoutMs, samples = 5 } = opts;
7729
- const gate = await runGate(dir, timeoutMs, samples);
7730
- if (gate === null) {
7731
- ctx.log(`${nodeId}: hyperframes check/lint/inspect unavailable \u2014 skipping pre-render check`);
7449
+ if (lintRaw === null && inspectRaw === null) {
7450
+ ctx.log(`${nodeId}: hyperframes lint/inspect unavailable \u2014 skipping pre-render check`);
7732
7451
  return;
7733
7452
  }
7734
- const { blocking, warnings } = gate;
7735
- for (const line of summariseWarnings(warnings)) {
7736
- ctx.log(`${nodeId}: hyperframe check warning ${line}`);
7453
+ const { blocking, warnings } = classifyCheckOutput(lintRaw ?? "", inspectRaw ?? "");
7454
+ for (const w of warnings) {
7455
+ ctx.log(`${nodeId}: hyperframe check warning [${w.source}/${w.code}] ${w.message}`);
7737
7456
  }
7738
7457
  if (blocking.length > 0) {
7739
- const seen = /* @__PURE__ */ new Map();
7740
- for (const b of blocking) seen.set(`${b.source}/${b.code}/${b.message}`, b);
7741
- const detail = [...seen.values()].map((b) => `\u2022 [${b.source}/${b.code}] ${explainGateFinding(b)}`).join("\n");
7742
- throw new Error(
7743
- `${nodeId}: pre-render check failed (${blocking.length} blocking, ${seen.size} distinct)
7744
- ${detail}`
7745
- );
7458
+ const detail = blocking.map((b) => `\u2022 [${b.source}/${b.code}] ${b.message}`).join("\n");
7459
+ throw new Error(`${nodeId}: pre-render check failed (${blocking.length} blocking)
7460
+ ${detail}`);
7746
7461
  }
7747
- const composition = await readFile9(path12.join(dir, "index.html"), "utf-8").catch(() => "");
7748
- const captionComplaint = fragmentedCaptions(captionTextsFrom(composition));
7749
- if (captionComplaint) {
7750
- ctx.log(`${nodeId}: caption style \u2014 ${captionComplaint}`);
7462
+ let indexHtml = "";
7463
+ try {
7464
+ indexHtml = await readFile9(path12.join(dir, "index.html"), "utf-8");
7465
+ } catch {
7466
+ indexHtml = "";
7467
+ }
7468
+ if (indexHtml && usesNestedCompositions(indexHtml)) {
7469
+ const snap = await runSnapshotSmoke(buildSnapshotArgs(dir, Math.min(samples, 3)), Math.max(timeoutMs, 12e4));
7470
+ if (snap.unavailable) {
7471
+ ctx.log(`${nodeId}: hyperframes snapshot unavailable \u2014 skipping nested-composition smoke test`);
7472
+ } else if (!snap.ok) {
7473
+ throw new Error(
7474
+ `${nodeId}: nested-composition smoke test failed \u2014 an embedded block did not render. Check the host\u2194block id match, that the block's <style>/<script> live inside its <template>, and that it styles #root (not a class).
7475
+ ${snap.message}`
7476
+ );
7477
+ } else {
7478
+ ctx.log(`${nodeId}: nested-composition smoke test passed`);
7479
+ }
7751
7480
  }
7752
- await runNestedCompositionSmoke({ dir, nodeId, ctx, timeoutMs, samples });
7753
7481
  ctx.log(`${nodeId}: pre-render check passed (${warnings.length} warning${warnings.length === 1 ? "" : "s"})`);
7754
7482
  }
7755
7483
 
@@ -9256,14 +8984,7 @@ async function tryExtractAudio(inputs, ctx) {
9256
8984
  import { z as z34 } from "zod";
9257
8985
  var voiceSelectNode = delegated({
9258
8986
  id: "voice_select",
9259
- // 1.1.0: a pinned voice used to come back as an inline `{ kind, text }` with no sha256
9260
- // and no mime, which every consumer taking the id as an INPUT rejected —
9261
- // `audio_voice_convert` died on `voice_ref.sha256: expected string, received undefined`.
9262
- // The backend now uploads it as a real asset like the ranked branch, and the version has
9263
- // to move with it: the cache key is built from this string, so entries written under
9264
- // 1.0.0 kept replaying the broken shape long after the fix was deployed. A node whose
9265
- // OUTPUT shape changes has to bump, not only one whose behaviour does.
9266
- version: "1.1.0",
8987
+ version: "1.0.0",
9267
8988
  category: "audio",
9268
8989
  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.',
9269
8990
  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.',
@@ -9274,11 +8995,6 @@ var voiceSelectNode = delegated({
9274
8995
  age: z34.string().optional(),
9275
8996
  accent: z34.string().optional(),
9276
8997
  language: z34.string().optional(),
9277
- /**
9278
- * A voice already chosen — an Avatar's pinned one. Given, nothing is cast: the
9279
- * ranking is skipped and this id is emitted as-is.
9280
- */
9281
- voice_id: z34.string().min(1).max(120).optional(),
9282
8998
  limit: z34.number().int().min(1).max(20).optional()
9283
8999
  }).strict(),
9284
9000
  outputs: z34.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
@@ -9441,7 +9157,6 @@ export {
9441
9157
  describeFailureReason,
9442
9158
  AD_FORMAT_PLATFORMS,
9443
9159
  platformFormats,
9444
- frameRealismDirection,
9445
9160
  SEEDANCE_DURATIONS,
9446
9161
  ELEVENLABS_MAX_MUSIC_LENGTH_MS,
9447
9162
  IMAGE_GENERATE_MODELS,
@@ -9453,14 +9168,10 @@ export {
9453
9168
  MODEL_REGISTRY,
9454
9169
  supportsParam,
9455
9170
  DEFAULT_CLIP_DURATION_S,
9456
- SPOKEN_WORDS_PER_SECOND,
9457
- spokenWordsIn,
9458
9171
  nearestClipDuration,
9459
- splitScriptFor,
9460
9172
  promptMaxLength,
9461
9173
  maxInputSlot,
9462
9174
  maxInputReferences,
9463
- bestSupportedQuality,
9464
9175
  nearestSupportedAspectRatio,
9465
9176
  nearestSupportedImageSize,
9466
9177
  estimateVideoCredits,
@@ -9501,4 +9212,4 @@ export {
9501
9212
  defaultRegistry,
9502
9213
  createEngineFromEnv
9503
9214
  };
9504
- //# sourceMappingURL=chunk-FENRH7OF.js.map
9215
+ //# sourceMappingURL=chunk-YFQWCJX4.js.map