@koda-sl/baker-cli 0.301.0-dev.50ab1c583 → 0.301.0-dev.c73cac21a
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.
- package/README.md +128 -88
- package/canvas/tiktok-captions-composition/index.html +2 -23
- package/dist/{chunk-2IUN3S3E.js → chunk-2IB3IL4K.js} +2 -2
- package/dist/{chunk-4YF56EGK.js → chunk-D3HHUBIL.js} +4 -4
- package/dist/{chunk-WFWU3CHS.js → chunk-DZUVUGEP.js} +1 -9
- package/dist/{chunk-WFWU3CHS.js.map → chunk-DZUVUGEP.js.map} +1 -1
- package/dist/{chunk-SSXER76B.js → chunk-EFETQHTX.js} +123 -438
- package/dist/chunk-EFETQHTX.js.map +1 -0
- package/dist/{chunk-A2VZOTCV.js → chunk-EX2OIOVL.js} +3 -3
- package/dist/{chunk-KDTHRRAC.js → chunk-GU7IWEBC.js} +3 -3
- package/dist/cli.js +7754 -6708
- package/dist/cli.js.map +1 -1
- package/dist/{client-6KQHCXS2.js → client-VDCCDEHE.js} +4 -4
- package/dist/engine/index.d.ts +0 -13
- package/dist/engine/index.js +3 -3
- package/dist/{env-C7YXL6C6.js → env-FWMZXMQK.js} +2 -6
- package/dist/{output-RBM32FKJ.js → output-4H2RNBXL.js} +5 -5
- package/dist/{shared-WJIJTWST.js → shared-UKL36AXT.js} +6 -6
- package/package.json +2 -5
- package/dist/chunk-SSXER76B.js.map +0 -1
- /package/dist/{chunk-2IUN3S3E.js.map → chunk-2IB3IL4K.js.map} +0 -0
- /package/dist/{chunk-4YF56EGK.js.map → chunk-D3HHUBIL.js.map} +0 -0
- /package/dist/{chunk-A2VZOTCV.js.map → chunk-EX2OIOVL.js.map} +0 -0
- /package/dist/{chunk-KDTHRRAC.js.map → chunk-GU7IWEBC.js.map} +0 -0
- /package/dist/{client-6KQHCXS2.js.map → client-VDCCDEHE.js.map} +0 -0
- /package/dist/{env-C7YXL6C6.js.map → env-FWMZXMQK.js.map} +0 -0
- /package/dist/{output-RBM32FKJ.js.map → output-4H2RNBXL.js.map} +0 -0
- /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-
|
|
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-
|
|
17
|
+
} from "./chunk-DZUVUGEP.js";
|
|
19
18
|
|
|
20
|
-
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/
|
|
19
|
+
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/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/
|
|
21
|
+
"../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/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})` : ""}
|
|
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/
|
|
1108
|
+
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/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;
|
|
@@ -2414,32 +2265,6 @@ var USD_PER_SECOND = {
|
|
|
2414
2265
|
},
|
|
2415
2266
|
// Flat $0.10/s across resolutions and modes, audio included.
|
|
2416
2267
|
"alibaba/wan-2.7": { silent: 0.1, audio: 0.1 },
|
|
2417
|
-
// 3.0 prices per resolution where 2.7 was flat, and its rate still includes audio —
|
|
2418
|
-
// there is no separate audio SKU, so a talking clip costs what a silent one costs.
|
|
2419
|
-
// `duration_seconds_480p|720p|1080p` on GET /api/v1/videos/models.
|
|
2420
|
-
"alibaba/wan-3.0": {
|
|
2421
|
-
silent: 0.1,
|
|
2422
|
-
audio: 0.1,
|
|
2423
|
-
byResolution: {
|
|
2424
|
-
"480p": { silent: 0.05, audio: 0.05 },
|
|
2425
|
-
"720p": { silent: 0.1, audio: 0.1 },
|
|
2426
|
-
"1080p": { silent: 0.2, audio: 0.2 }
|
|
2427
|
-
}
|
|
2428
|
-
},
|
|
2429
|
-
// `cents_per_video_output_second_*` — 8c, 14c and 25c. Dearer than the older Grok at
|
|
2430
|
-
// every tier, and it reaches 1080p, which that one does not. No audio, so both columns
|
|
2431
|
-
// are the same rate rather than one being wrong.
|
|
2432
|
-
"x-ai/grok-imagine-video-1.5": {
|
|
2433
|
-
silent: 0.14,
|
|
2434
|
-
audio: 0.14,
|
|
2435
|
-
byResolution: {
|
|
2436
|
-
"480p": { silent: 0.08, audio: 0.08 },
|
|
2437
|
-
"720p": { silent: 0.14, audio: 0.14 },
|
|
2438
|
-
"1080p": { silent: 0.25, audio: 0.25 }
|
|
2439
|
-
}
|
|
2440
|
-
},
|
|
2441
|
-
// One flat rate, one resolution: `cents_per_second_output: 12`. Silent.
|
|
2442
|
-
"runway/gen-4.5": { silent: 0.12, audio: 0.12 },
|
|
2443
2268
|
"alibaba/happyhorse-1.1": {
|
|
2444
2269
|
silent: 0.1278,
|
|
2445
2270
|
audio: 0.1278,
|
|
@@ -3305,16 +3130,6 @@ function dfsCycle(u, color, stack, reverseAdj) {
|
|
|
3305
3130
|
stack.pop();
|
|
3306
3131
|
return null;
|
|
3307
3132
|
}
|
|
3308
|
-
function nodesBeforeCheckpoint(graph, types, stopKinds) {
|
|
3309
|
-
const all = new Set(graph.keys());
|
|
3310
|
-
if (!stopKinds?.size) return all;
|
|
3311
|
-
const running = /* @__PURE__ */ new Set();
|
|
3312
|
-
for (const layer of topologicalLayers(graph)) {
|
|
3313
|
-
if (layer.some((id) => stopKinds.has(types.get(id) ?? ""))) return running;
|
|
3314
|
-
for (const id of layer) running.add(id);
|
|
3315
|
-
}
|
|
3316
|
-
return running;
|
|
3317
|
-
}
|
|
3318
3133
|
|
|
3319
3134
|
// src/engine/engine/validator.ts
|
|
3320
3135
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -3333,7 +3148,7 @@ var VEO_DURATIONS = [4, 6, 8];
|
|
|
3333
3148
|
var KLING_DURATIONS2 = [5, 10];
|
|
3334
3149
|
var KLING_NEGATIVE_PROMPT = "warped face, distorted hands, extra fingers, morphing, flicker, on-screen text, watermark, low quality";
|
|
3335
3150
|
var KLING_CFG_SCALE = 0.7;
|
|
3336
|
-
var SPEAKS_PROSE = (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}"`;
|
|
3337
3152
|
var SEEDANCE_PROFILE = {
|
|
3338
3153
|
id: "seedance",
|
|
3339
3154
|
dialogueDirective: SPEAKS_PROSE,
|
|
@@ -3434,14 +3249,9 @@ var GPT_IMAGE_PROFILE = {
|
|
|
3434
3249
|
id: "gpt-image",
|
|
3435
3250
|
constraintPlacement: "last",
|
|
3436
3251
|
photorealCue: true,
|
|
3437
|
-
// OpenRouter forwards `quality`; gpt-image already processes inputs at high
|
|
3438
|
-
// automatically, so we deliberately do NOT send `input_fidelity`.
|
|
3439
|
-
|
|
3440
|
-
// No `quality` here any more: it is the one default that depends on WHICH gpt-image is
|
|
3441
|
-
// pinned. 2.5 added `xhigh` and `max` above `high`, and the older entries would refuse
|
|
3442
|
-
// them at validate — so callers ask for the step they want through
|
|
3443
|
-
// `bestSupportedQuality` and get the best the chosen model actually has.
|
|
3444
|
-
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" }
|
|
3445
3255
|
};
|
|
3446
3256
|
var GEMINI_IMAGE_PROFILE = {
|
|
3447
3257
|
id: "gemini",
|
|
@@ -3952,19 +3762,6 @@ function checkSlotsForNode(ctx, n, _i) {
|
|
|
3952
3762
|
});
|
|
3953
3763
|
}
|
|
3954
3764
|
}
|
|
3955
|
-
function estimateCreditsFor(canvas, registry, ids) {
|
|
3956
|
-
let total = 0;
|
|
3957
|
-
for (const n of canvas.nodes) {
|
|
3958
|
-
if (!ids.has(n.id)) continue;
|
|
3959
|
-
const def = registry.get(n.type);
|
|
3960
|
-
if (!def?.cost) continue;
|
|
3961
|
-
try {
|
|
3962
|
-
total += def.cost({ params: def.params.parse(n.params ?? {}) }).credits;
|
|
3963
|
-
} catch {
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
return total;
|
|
3967
|
-
}
|
|
3968
3765
|
function estimateCredits(ctx) {
|
|
3969
3766
|
let total = 0;
|
|
3970
3767
|
for (const n of ctx.canvas.nodes) {
|
|
@@ -4271,7 +4068,7 @@ function checkAudioTimelineTotals(ctx, audioMixNode, expected) {
|
|
|
4271
4068
|
if (audioMixNode) mustBeFullLength.add(audioMixNode);
|
|
4272
4069
|
ctx.canvas.nodes.forEach((n) => {
|
|
4273
4070
|
if (n.type !== "audio_voice_convert") return;
|
|
4274
|
-
const track = refNodeOf(ctx, n.inputs
|
|
4071
|
+
const track = refNodeOf(ctx, (n.inputs ?? {}).audio);
|
|
4275
4072
|
if (track?.type === "audio_timeline") mustBeFullLength.add(track.id);
|
|
4276
4073
|
});
|
|
4277
4074
|
for (const id of mustBeFullLength) {
|
|
@@ -4292,11 +4089,11 @@ function checkAudioTimelineTotals(ctx, audioMixNode, expected) {
|
|
|
4292
4089
|
function checkNativeSegOverlap(ctx) {
|
|
4293
4090
|
for (const conv of ctx.canvas.nodes) {
|
|
4294
4091
|
if (conv.type !== "audio_voice_convert") continue;
|
|
4295
|
-
const track = refNodeOf(ctx, conv.inputs
|
|
4092
|
+
const track = refNodeOf(ctx, (conv.inputs ?? {}).audio);
|
|
4296
4093
|
if (track?.type !== "audio_timeline") continue;
|
|
4297
4094
|
const params = track.params;
|
|
4298
4095
|
const windows = (params.tracks ?? []).map((t) => {
|
|
4299
|
-
const extract = refNodeOf(ctx, track.inputs
|
|
4096
|
+
const extract = refNodeOf(ctx, (track.inputs ?? {})[t.slot]);
|
|
4300
4097
|
const len = t.duration_s ?? (extract ? ffmpegTrimSeconds(extract) : null);
|
|
4301
4098
|
return len === null ? null : { slot: t.slot, start: t.start_s, end: t.start_s + len };
|
|
4302
4099
|
}).filter((w) => w !== null).sort((a, b) => a.start - b.start);
|
|
@@ -4789,13 +4586,6 @@ function unwrap(schema) {
|
|
|
4789
4586
|
}
|
|
4790
4587
|
|
|
4791
4588
|
// src/engine/engine/executor.ts
|
|
4792
|
-
function heldByCheckpoint(canvas, layer, kinds) {
|
|
4793
|
-
if (!kinds?.size) return [];
|
|
4794
|
-
return layer.filter((id) => {
|
|
4795
|
-
const type = canvas.nodes.find((n) => n.id === id)?.type;
|
|
4796
|
-
return type !== void 0 && kinds.has(type);
|
|
4797
|
-
});
|
|
4798
|
-
}
|
|
4799
4589
|
var Engine = class {
|
|
4800
4590
|
registry;
|
|
4801
4591
|
client;
|
|
@@ -4823,7 +4613,7 @@ var Engine = class {
|
|
|
4823
4613
|
async run(input, opts = {}) {
|
|
4824
4614
|
const validation = await this.validateDeep(input);
|
|
4825
4615
|
if (!validation.ok) throw new ValidationError(validation.issues);
|
|
4826
|
-
if (
|
|
4616
|
+
if (opts.max_credits !== void 0 && validation.estimatedCredits > opts.max_credits) {
|
|
4827
4617
|
throw new RunAbortedError(
|
|
4828
4618
|
"cost_cap",
|
|
4829
4619
|
`estimated ${validation.estimatedCredits} credits exceeds the ${opts.max_credits}-credit cap \u2014 nothing was billed; raise --max-credits or shrink the canvas`
|
|
@@ -4842,20 +4632,6 @@ var Engine = class {
|
|
|
4842
4632
|
const counters = { cachedNodes: 0, totalCredits: 0 };
|
|
4843
4633
|
const nodeRuns = [];
|
|
4844
4634
|
const graph = this.pruneToOutput(canvas, buildGraph(canvas));
|
|
4845
|
-
if (opts.max_credits !== void 0 && opts.stop_before_kinds?.size) {
|
|
4846
|
-
const willRun = nodesBeforeCheckpoint(
|
|
4847
|
-
graph,
|
|
4848
|
-
new Map(canvas.nodes.map((n) => [n.id, n.type])),
|
|
4849
|
-
opts.stop_before_kinds
|
|
4850
|
-
);
|
|
4851
|
-
const estimate = estimateCreditsFor(canvas, this.registry, willRun);
|
|
4852
|
-
if (estimate > opts.max_credits) {
|
|
4853
|
-
throw new RunAbortedError(
|
|
4854
|
-
"cost_cap",
|
|
4855
|
-
`estimated ${estimate} credits exceeds the ${opts.max_credits}-credit cap \u2014 nothing was billed; raise --max-credits or shrink the canvas`
|
|
4856
|
-
);
|
|
4857
|
-
}
|
|
4858
|
-
}
|
|
4859
4635
|
const needsBytes = computeNeedsLocalBytes(canvas, graph, this.registry);
|
|
4860
4636
|
this.emitProgress(opts, {
|
|
4861
4637
|
kind: "plan",
|
|
@@ -4900,13 +4676,6 @@ var Engine = class {
|
|
|
4900
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`
|
|
4901
4677
|
);
|
|
4902
4678
|
}
|
|
4903
|
-
const held = heldByCheckpoint(canvas, layer, opts.stop_before_kinds);
|
|
4904
|
-
if (held.length > 0) {
|
|
4905
|
-
throw new RunAbortedError(
|
|
4906
|
-
"checkpoint",
|
|
4907
|
-
`stopped before ${held.join(", ")} \u2014 everything up to here is done and cached, so continuing this run id pays for none of it again`
|
|
4908
|
-
);
|
|
4909
|
-
}
|
|
4910
4679
|
const settled = await mapWithConcurrency(layer, limit, (nodeId) => {
|
|
4911
4680
|
if (opts.signal?.aborted) {
|
|
4912
4681
|
return Promise.reject(new RunAbortedError("signal", "run aborted before node dispatch"));
|
|
@@ -6180,7 +5949,7 @@ function resolveStrategy(params) {
|
|
|
6180
5949
|
case "text": {
|
|
6181
5950
|
const p = safePathname(params.url);
|
|
6182
5951
|
const isRawText = RAW_TEXT_EXTENSIONS.some((ext) => p.endsWith(ext));
|
|
6183
|
-
return isRawText ? "direct_fetch" : "
|
|
5952
|
+
return isRawText ? "direct_fetch" : "firecrawl";
|
|
6184
5953
|
}
|
|
6185
5954
|
default: {
|
|
6186
5955
|
params.expect;
|
|
@@ -6197,10 +5966,10 @@ function safePathname(rawUrl) {
|
|
|
6197
5966
|
}
|
|
6198
5967
|
var ingestNode = defineNode({
|
|
6199
5968
|
id: "ingest",
|
|
6200
|
-
version: "1.
|
|
5969
|
+
version: "1.3.0",
|
|
6201
5970
|
category: "io",
|
|
6202
5971
|
location: "local",
|
|
6203
|
-
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),
|
|
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.",
|
|
6204
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`).',
|
|
6205
5974
|
inputs: IngestInputs,
|
|
6206
5975
|
params: IngestParams,
|
|
@@ -6227,8 +5996,8 @@ function runStrategy(strategy, params, ctx) {
|
|
|
6227
5996
|
switch (strategy) {
|
|
6228
5997
|
case "direct_fetch":
|
|
6229
5998
|
return execDirectFetch(params, ctx);
|
|
6230
|
-
case "
|
|
6231
|
-
return
|
|
5999
|
+
case "firecrawl":
|
|
6000
|
+
return execFirecrawl(params, ctx);
|
|
6232
6001
|
case "yt_dlp":
|
|
6233
6002
|
return execYtDlp(params, ctx);
|
|
6234
6003
|
case "local_file":
|
|
@@ -6266,9 +6035,8 @@ async function ingestImageUrl(url, ctx) {
|
|
|
6266
6035
|
} catch (e) {
|
|
6267
6036
|
throw localExecError(ctx, `${url}: ${e.message}`);
|
|
6268
6037
|
}
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
}
|
|
6038
|
+
const note = normalizationNote(normalized);
|
|
6039
|
+
if (note) ctx.log(`ingest: ${url} ${note}`);
|
|
6272
6040
|
return uploadAndIngest({
|
|
6273
6041
|
bytes: normalized.bytes,
|
|
6274
6042
|
kind: "image",
|
|
@@ -6282,11 +6050,11 @@ async function ingestImageUrl(url, ctx) {
|
|
|
6282
6050
|
ctx
|
|
6283
6051
|
});
|
|
6284
6052
|
}
|
|
6285
|
-
async function
|
|
6053
|
+
async function execFirecrawl(params, ctx) {
|
|
6286
6054
|
const result = await callBackendExec({
|
|
6287
6055
|
nodeType: "ingest",
|
|
6288
6056
|
nodeVersion: ingestNode.version,
|
|
6289
|
-
params: { strategy: "
|
|
6057
|
+
params: { strategy: "firecrawl", url: params.url },
|
|
6290
6058
|
inputs: {},
|
|
6291
6059
|
outputKinds: { asset: "text" },
|
|
6292
6060
|
ctx
|
|
@@ -6378,23 +6146,69 @@ async function rasterizeSvgToPng(bytes) {
|
|
|
6378
6146
|
return await sharp(bytes, { density }).png({ force: true, palette: false }).toBuffer();
|
|
6379
6147
|
}
|
|
6380
6148
|
var MODEL_SAFE_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
|
|
6381
|
-
|
|
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
|
+
};
|
|
6382
6184
|
const safe = sniffImageMime(bytes);
|
|
6383
6185
|
if (safe && MODEL_SAFE_IMAGE_MIMES.has(safe)) {
|
|
6384
|
-
return
|
|
6186
|
+
return await fit(bytes, safe);
|
|
6385
6187
|
}
|
|
6386
6188
|
if (sniffSvg(bytes)) {
|
|
6387
|
-
return
|
|
6189
|
+
return await fit(await rasterizeSvgToPng(bytes), "image/png", "svg");
|
|
6388
6190
|
}
|
|
6389
6191
|
const { default: sharp } = await import("sharp");
|
|
6192
|
+
let png;
|
|
6193
|
+
let format;
|
|
6390
6194
|
try {
|
|
6391
6195
|
const img = sharp(bytes);
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
return { bytes: png, mime: "image/png", rasterizedFrom: format ?? "unknown" };
|
|
6196
|
+
format = (await img.metadata()).format ?? "unknown";
|
|
6197
|
+
png = await img.png({ force: true }).toBuffer();
|
|
6395
6198
|
} catch (e) {
|
|
6396
6199
|
throw new Error(`bytes are not a decodable image (${e.message})`);
|
|
6397
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;
|
|
6398
6212
|
}
|
|
6399
6213
|
function hasAscii(buf, offset, sig) {
|
|
6400
6214
|
return buf.length >= offset + sig.length && buf.toString("ascii", offset, offset + sig.length) === sig;
|
|
@@ -6524,7 +6338,8 @@ async function execLocalFile(params, ctx) {
|
|
|
6524
6338
|
outBytes = normalized.bytes;
|
|
6525
6339
|
outMime = normalized.mime;
|
|
6526
6340
|
rasterizedFrom = normalized.rasterizedFrom;
|
|
6527
|
-
|
|
6341
|
+
const note = normalizationNote(normalized);
|
|
6342
|
+
if (note) ctx.log(`ingest: ${note}`);
|
|
6528
6343
|
}
|
|
6529
6344
|
const durationMs = probeVideoDurationMs(params.expect, outBytes, ctx);
|
|
6530
6345
|
const ref = await uploadAndIngest({
|
|
@@ -7496,24 +7311,6 @@ import { execFile as execFile3 } from "child_process";
|
|
|
7496
7311
|
import { readFile as readFile9 } from "fs/promises";
|
|
7497
7312
|
import path12 from "path";
|
|
7498
7313
|
import { promisify as promisify3 } from "util";
|
|
7499
|
-
|
|
7500
|
-
// src/engine/nodes/local/lib/caption-style.ts
|
|
7501
|
-
var TERMINAL = /[.,;:!?…]$/;
|
|
7502
|
-
function captionTextsFrom(html) {
|
|
7503
|
-
const cards = html.match(/<div[^>]*class="[^"]*\bcaption\b[^"]*"[\s\S]*?<\/div>/g) ?? [];
|
|
7504
|
-
return cards.map(
|
|
7505
|
-
(card) => card.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
7506
|
-
).filter((text) => text.length > 0);
|
|
7507
|
-
}
|
|
7508
|
-
function fragmentedCaptions(texts) {
|
|
7509
|
-
if (texts.length < 2) return null;
|
|
7510
|
-
const fragments = texts.slice(0, -1).filter((t) => !TERMINAL.test(t));
|
|
7511
|
-
if (fragments.length === 0) return null;
|
|
7512
|
-
const example = fragments[0];
|
|
7513
|
-
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.`;
|
|
7514
|
-
}
|
|
7515
|
-
|
|
7516
|
-
// src/engine/nodes/local/lib/hyperframe-check.ts
|
|
7517
7314
|
var execFileAsync = promisify3(execFile3);
|
|
7518
7315
|
var NEVER_BLOCK = [
|
|
7519
7316
|
/contrast/i,
|
|
@@ -7582,74 +7379,6 @@ function classifyCheckOutput(lintRaw, inspectRaw) {
|
|
|
7582
7379
|
warnings: findings.filter((f) => f.severity === "warning")
|
|
7583
7380
|
};
|
|
7584
7381
|
}
|
|
7585
|
-
var GATE_SECTIONS = ["lint", "runtime", "layout", "motion", "contrast"];
|
|
7586
|
-
function buildCheckArgs(dir, samples) {
|
|
7587
|
-
return ["hyperframes", "check", dir, "--json", "--samples", String(samples), "--at-transitions"];
|
|
7588
|
-
}
|
|
7589
|
-
var PROMOTE_TO_BLOCKING = /GSAP target [^ ]+ not found|no deterministic font mapping/i;
|
|
7590
|
-
function toGateFinding(section, entry) {
|
|
7591
|
-
const f = entry;
|
|
7592
|
-
const code = typeof f.code === "string" ? f.code : "unknown";
|
|
7593
|
-
const message2 = typeof f.message === "string" ? f.message : "";
|
|
7594
|
-
const isError = f.severity === "error" || PROMOTE_TO_BLOCKING.test(message2);
|
|
7595
|
-
const fatal = PROMOTE_TO_BLOCKING.test(message2);
|
|
7596
|
-
return {
|
|
7597
|
-
source: section,
|
|
7598
|
-
code,
|
|
7599
|
-
message: message2,
|
|
7600
|
-
severity: fatal || isError && !isAdvisory(code, message2) ? "blocking" : "warning"
|
|
7601
|
-
};
|
|
7602
|
-
}
|
|
7603
|
-
function classifyGateOutput(raw) {
|
|
7604
|
-
const parsed = parseCheckJson(raw);
|
|
7605
|
-
const findings = GATE_SECTIONS.flatMap((section) => {
|
|
7606
|
-
const block = parsed?.[section];
|
|
7607
|
-
const entries = Array.isArray(block?.findings) ? block.findings : [];
|
|
7608
|
-
return entries.map((entry) => toGateFinding(section, entry));
|
|
7609
|
-
});
|
|
7610
|
-
return {
|
|
7611
|
-
blocking: findings.filter((f) => f.severity === "blocking"),
|
|
7612
|
-
warnings: findings.filter((f) => f.severity === "warning")
|
|
7613
|
-
};
|
|
7614
|
-
}
|
|
7615
|
-
function summariseWarnings(warnings) {
|
|
7616
|
-
const groups = /* @__PURE__ */ new Map();
|
|
7617
|
-
for (const w of warnings) {
|
|
7618
|
-
const key = `${w.source}/${w.code}`;
|
|
7619
|
-
const seen = groups.get(key);
|
|
7620
|
-
if (seen) seen.count += 1;
|
|
7621
|
-
else groups.set(key, { count: 1, message: w.message });
|
|
7622
|
-
}
|
|
7623
|
-
return [...groups.entries()].sort((a, b) => b[1].count - a[1].count).map(([key, g]) => `${g.count}\xD7 [${key}] ${g.message}`);
|
|
7624
|
-
}
|
|
7625
|
-
var FINDING_ADVICE = {
|
|
7626
|
-
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.",
|
|
7627
|
-
console_error: "the composition threw in the browser, so whatever that script was driving did not happen.",
|
|
7628
|
-
page_error: "a script failed to evaluate \u2014 animations and any JS-driven layout in this composition did not run.",
|
|
7629
|
-
http_error: "an asset 404ed at render time; it will be missing from the frame, not substituted.",
|
|
7630
|
-
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."
|
|
7631
|
-
};
|
|
7632
|
-
function explainGateFinding(finding) {
|
|
7633
|
-
const advice = FINDING_ADVICE[finding.code];
|
|
7634
|
-
return advice ? `${finding.message} \u2014 ${advice}` : finding.message;
|
|
7635
|
-
}
|
|
7636
|
-
function condenseFrameDescriptions(markdown) {
|
|
7637
|
-
const lines = [];
|
|
7638
|
-
let frame = null;
|
|
7639
|
-
for (const line of markdown.split("\n")) {
|
|
7640
|
-
const heading = line.match(/^## (.+)$/);
|
|
7641
|
-
if (heading) {
|
|
7642
|
-
frame = heading[1] ?? null;
|
|
7643
|
-
continue;
|
|
7644
|
-
}
|
|
7645
|
-
const text = line.trim();
|
|
7646
|
-
if (frame && text) {
|
|
7647
|
-
lines.push(`${frame}: ${text}`);
|
|
7648
|
-
frame = null;
|
|
7649
|
-
}
|
|
7650
|
-
}
|
|
7651
|
-
return lines;
|
|
7652
|
-
}
|
|
7653
7382
|
function buildLintArgs(dir) {
|
|
7654
7383
|
return ["hyperframes", "lint", dir, "--json"];
|
|
7655
7384
|
}
|
|
@@ -7681,13 +7410,8 @@ async function lintStructuralIssues(dir, timeoutMs) {
|
|
|
7681
7410
|
function buildInspectArgs(dir, samples) {
|
|
7682
7411
|
return ["hyperframes", "inspect", dir, "--json", "--samples", String(samples)];
|
|
7683
7412
|
}
|
|
7684
|
-
function buildSnapshotArgs(dir, frames
|
|
7685
|
-
|
|
7686
|
-
return vision ? [
|
|
7687
|
-
...base,
|
|
7688
|
-
"--describe",
|
|
7689
|
-
"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?"
|
|
7690
|
-
] : [...base, "--describe", "false"];
|
|
7413
|
+
function buildSnapshotArgs(dir, frames) {
|
|
7414
|
+
return ["hyperframes", "snapshot", dir, "--frames", String(frames), "--describe", "false"];
|
|
7691
7415
|
}
|
|
7692
7416
|
function usesNestedCompositions(indexHtml) {
|
|
7693
7417
|
const withoutComments = indexHtml.replace(/<!--[\s\S]*?-->/g, "");
|
|
@@ -7716,66 +7440,44 @@ async function runSnapshotSmoke(args, timeoutMs) {
|
|
|
7716
7440
|
return { ok: false, unavailable: false, message: (err.stderr || err.message || "snapshot failed").slice(0, 800) };
|
|
7717
7441
|
}
|
|
7718
7442
|
}
|
|
7719
|
-
async function
|
|
7720
|
-
const { dir, nodeId, ctx, timeoutMs, samples } = opts;
|
|
7721
|
-
const indexHtml = await readFile9(path12.join(dir, "index.html"), "utf-8").catch(() => "");
|
|
7722
|
-
if (!indexHtml || !usesNestedCompositions(indexHtml)) return;
|
|
7723
|
-
const snap = await runSnapshotSmoke(
|
|
7724
|
-
buildSnapshotArgs(dir, Math.min(samples, 3), hasFrameVisionKey()),
|
|
7725
|
-
Math.max(timeoutMs, 12e4)
|
|
7726
|
-
);
|
|
7727
|
-
if (snap.unavailable) {
|
|
7728
|
-
ctx.log(`${nodeId}: hyperframes snapshot unavailable \u2014 skipping nested-composition smoke test`);
|
|
7729
|
-
return;
|
|
7730
|
-
}
|
|
7731
|
-
if (!snap.ok) {
|
|
7732
|
-
throw new Error(
|
|
7733
|
-
`${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).
|
|
7734
|
-
${snap.message}`
|
|
7735
|
-
);
|
|
7736
|
-
}
|
|
7737
|
-
const described = await readFile9(path12.join(dir, "snapshots", "descriptions.md"), "utf-8").catch(() => "");
|
|
7738
|
-
for (const line of condenseFrameDescriptions(described)) {
|
|
7739
|
-
ctx.log(`${nodeId}: frame check \u2014 ${line}`);
|
|
7740
|
-
}
|
|
7741
|
-
ctx.log(`${nodeId}: nested-composition smoke test passed`);
|
|
7742
|
-
}
|
|
7743
|
-
async function runGate(dir, timeoutMs, samples) {
|
|
7744
|
-
const checkRaw = await runOne(buildCheckArgs(dir, samples), timeoutMs);
|
|
7745
|
-
if (checkRaw !== null) return classifyGateOutput(checkRaw);
|
|
7443
|
+
async function runHyperframesCheck(opts) {
|
|
7444
|
+
const { dir, nodeId, ctx, timeoutMs, samples = 5 } = opts;
|
|
7746
7445
|
const [lintRaw, inspectRaw] = await Promise.all([
|
|
7747
7446
|
runOne(buildLintArgs(dir), timeoutMs),
|
|
7748
7447
|
runOne(buildInspectArgs(dir, samples), timeoutMs)
|
|
7749
7448
|
]);
|
|
7750
|
-
if (lintRaw === null && inspectRaw === null)
|
|
7751
|
-
|
|
7752
|
-
}
|
|
7753
|
-
async function runHyperframesCheck(opts) {
|
|
7754
|
-
const { dir, nodeId, ctx, timeoutMs, samples = 5 } = opts;
|
|
7755
|
-
const gate = await runGate(dir, timeoutMs, samples);
|
|
7756
|
-
if (gate === null) {
|
|
7757
|
-
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`);
|
|
7758
7451
|
return;
|
|
7759
7452
|
}
|
|
7760
|
-
const { blocking, warnings } =
|
|
7761
|
-
for (const
|
|
7762
|
-
ctx.log(`${nodeId}: hyperframe check warning ${
|
|
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}`);
|
|
7763
7456
|
}
|
|
7764
7457
|
if (blocking.length > 0) {
|
|
7765
|
-
const
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
);
|
|
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}`);
|
|
7461
|
+
}
|
|
7462
|
+
let indexHtml = "";
|
|
7463
|
+
try {
|
|
7464
|
+
indexHtml = await readFile9(path12.join(dir, "index.html"), "utf-8");
|
|
7465
|
+
} catch {
|
|
7466
|
+
indexHtml = "";
|
|
7772
7467
|
}
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
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
|
+
}
|
|
7777
7480
|
}
|
|
7778
|
-
await runNestedCompositionSmoke({ dir, nodeId, ctx, timeoutMs, samples });
|
|
7779
7481
|
ctx.log(`${nodeId}: pre-render check passed (${warnings.length} warning${warnings.length === 1 ? "" : "s"})`);
|
|
7780
7482
|
}
|
|
7781
7483
|
|
|
@@ -9282,14 +8984,7 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
9282
8984
|
import { z as z34 } from "zod";
|
|
9283
8985
|
var voiceSelectNode = delegated({
|
|
9284
8986
|
id: "voice_select",
|
|
9285
|
-
|
|
9286
|
-
// and no mime, which every consumer taking the id as an INPUT rejected —
|
|
9287
|
-
// `audio_voice_convert` died on `voice_ref.sha256: expected string, received undefined`.
|
|
9288
|
-
// The backend now uploads it as a real asset like the ranked branch, and the version has
|
|
9289
|
-
// to move with it: the cache key is built from this string, so entries written under
|
|
9290
|
-
// 1.0.0 kept replaying the broken shape long after the fix was deployed. A node whose
|
|
9291
|
-
// OUTPUT shape changes has to bump, not only one whose behaviour does.
|
|
9292
|
-
version: "1.1.0",
|
|
8987
|
+
version: "1.0.0",
|
|
9293
8988
|
category: "audio",
|
|
9294
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.',
|
|
9295
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.',
|
|
@@ -9300,11 +8995,6 @@ var voiceSelectNode = delegated({
|
|
|
9300
8995
|
age: z34.string().optional(),
|
|
9301
8996
|
accent: z34.string().optional(),
|
|
9302
8997
|
language: z34.string().optional(),
|
|
9303
|
-
/**
|
|
9304
|
-
* A voice already chosen — an Avatar's pinned one. Given, nothing is cast: the
|
|
9305
|
-
* ranking is skipped and this id is emitted as-is.
|
|
9306
|
-
*/
|
|
9307
|
-
voice_id: z34.string().min(1).max(120).optional(),
|
|
9308
8998
|
limit: z34.number().int().min(1).max(20).optional()
|
|
9309
8999
|
}).strict(),
|
|
9310
9000
|
outputs: z34.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
|
|
@@ -9467,7 +9157,6 @@ export {
|
|
|
9467
9157
|
describeFailureReason,
|
|
9468
9158
|
AD_FORMAT_PLATFORMS,
|
|
9469
9159
|
platformFormats,
|
|
9470
|
-
frameRealismDirection,
|
|
9471
9160
|
SEEDANCE_DURATIONS,
|
|
9472
9161
|
ELEVENLABS_MAX_MUSIC_LENGTH_MS,
|
|
9473
9162
|
IMAGE_GENERATE_MODELS,
|
|
@@ -9479,14 +9168,10 @@ export {
|
|
|
9479
9168
|
MODEL_REGISTRY,
|
|
9480
9169
|
supportsParam,
|
|
9481
9170
|
DEFAULT_CLIP_DURATION_S,
|
|
9482
|
-
SPOKEN_WORDS_PER_SECOND,
|
|
9483
|
-
spokenWordsIn,
|
|
9484
9171
|
nearestClipDuration,
|
|
9485
|
-
splitScriptFor,
|
|
9486
9172
|
promptMaxLength,
|
|
9487
9173
|
maxInputSlot,
|
|
9488
9174
|
maxInputReferences,
|
|
9489
|
-
bestSupportedQuality,
|
|
9490
9175
|
nearestSupportedAspectRatio,
|
|
9491
9176
|
nearestSupportedImageSize,
|
|
9492
9177
|
estimateVideoCredits,
|
|
@@ -9527,4 +9212,4 @@ export {
|
|
|
9527
9212
|
defaultRegistry,
|
|
9528
9213
|
createEngineFromEnv
|
|
9529
9214
|
};
|
|
9530
|
-
//# sourceMappingURL=chunk-
|
|
9215
|
+
//# sourceMappingURL=chunk-EFETQHTX.js.map
|