@koda-sl/baker-cli 0.122.1-dev.57a9836c5 → 0.123.0-dev.31b784126
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 +89 -16
- package/dist/{chunk-T6HBTZOO.js → chunk-Q3K5TXC6.js} +506 -356
- package/dist/chunk-Q3K5TXC6.js.map +1 -0
- package/dist/cli.js +2519 -1433
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +39 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-T6HBTZOO.js.map +0 -1
|
@@ -780,7 +780,9 @@ function failedJobError(error) {
|
|
|
780
780
|
retryable: error.retryable ?? false
|
|
781
781
|
});
|
|
782
782
|
}
|
|
783
|
-
|
|
783
|
+
function pollInterval(attempt) {
|
|
784
|
+
return attempt < 15 ? 1e3 : 3e3;
|
|
785
|
+
}
|
|
784
786
|
var JOB_POLL_MAX_MS = 20 * 60 * 1e3;
|
|
785
787
|
var BackendClient = class {
|
|
786
788
|
http;
|
|
@@ -797,7 +799,7 @@ var BackendClient = class {
|
|
|
797
799
|
async pollJob(jobId, signal) {
|
|
798
800
|
const deadline = Date.now() + JOB_POLL_MAX_MS;
|
|
799
801
|
const path16 = `/api/canvas/jobs/${encodeURIComponent(jobId)}`;
|
|
800
|
-
|
|
802
|
+
for (let attempt = 0; ; attempt++) {
|
|
801
803
|
if (signal?.aborted) {
|
|
802
804
|
throw new BackendHttpError({ kind: "network", cause: signal.reason ?? new Error("aborted") });
|
|
803
805
|
}
|
|
@@ -807,7 +809,7 @@ var BackendClient = class {
|
|
|
807
809
|
if (Date.now() > deadline) {
|
|
808
810
|
throw new BackendHttpError({ kind: "timeout", message: `job ${jobId} did not finish in time` });
|
|
809
811
|
}
|
|
810
|
-
await sleep(
|
|
812
|
+
await sleep(pollInterval(attempt));
|
|
811
813
|
}
|
|
812
814
|
}
|
|
813
815
|
presignAssetUpload(sha256, mime, signal) {
|
|
@@ -838,6 +840,14 @@ var BackendClient = class {
|
|
|
838
840
|
async recordRun(payload, signal) {
|
|
839
841
|
await this.http.postJson("/api/canvas/runs", payload, signal);
|
|
840
842
|
}
|
|
843
|
+
/**
|
|
844
|
+
* Chat-scoped blueprint sync — POST /api/creatives/definition. Lets the
|
|
845
|
+
* dashboard draw a scaffolded creative's workflow graph BEFORE the first run.
|
|
846
|
+
* Additive on the backend (never archives siblings, never sets definitionPath).
|
|
847
|
+
*/
|
|
848
|
+
async syncCreativeDefinition(payload, signal) {
|
|
849
|
+
await this.http.postJson("/api/creatives/definition", payload, signal);
|
|
850
|
+
}
|
|
841
851
|
getArtifact(kind, name, version, signal) {
|
|
842
852
|
const path16 = version ? `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}` : `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`;
|
|
843
853
|
return this.http.getJson(path16, signal);
|
|
@@ -986,10 +996,10 @@ var ELEVENLABS_OUTPUT_FORMATS = [
|
|
|
986
996
|
var ELEVENLABS_MAX_TEXT_CHARS = 45454;
|
|
987
997
|
var ELEVENLABS_MAX_MUSIC_LENGTH_MS = 454545;
|
|
988
998
|
var OPENROUTER_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
989
|
-
var
|
|
990
|
-
var
|
|
999
|
+
var REPLICATE_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp"];
|
|
1000
|
+
var REPLICATE_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
991
1001
|
var DECONSTRUCT_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
992
|
-
var
|
|
1002
|
+
var REPLICATE_AUDIO_MIMES = ["audio/wav", "audio/mpeg", "audio/mp3"];
|
|
993
1003
|
var IMAGE_GENERATE_MODELS = [
|
|
994
1004
|
"openai/gpt-5.4-image-2",
|
|
995
1005
|
"google/gemini-3.5-flash",
|
|
@@ -1207,20 +1217,23 @@ var MODEL_REGISTRY = {
|
|
|
1207
1217
|
},
|
|
1208
1218
|
video_generate: {
|
|
1209
1219
|
"bytedance/seedance-2.0": {
|
|
1210
|
-
// Routed via
|
|
1211
|
-
//
|
|
1212
|
-
//
|
|
1220
|
+
// Routed via Replicate's official `bytedance/seedance-2.0` model. NOTE:
|
|
1221
|
+
// ByteDance's upstream "real person" likeness filter still blocks photoreal
|
|
1222
|
+
// human reference frames on ANY reseller — the escape is a synthetic/AI
|
|
1223
|
+
// presenter face or routing real faces to Veo, not the provider choice.
|
|
1213
1224
|
label: "ByteDance Seedance 2.0",
|
|
1214
1225
|
inputs: [],
|
|
1215
|
-
optional_inputs: [{ kind: "image", mimes:
|
|
1226
|
+
optional_inputs: [{ kind: "image", mimes: REPLICATE_IMAGE_MIMES }],
|
|
1216
1227
|
required: ["prompt"],
|
|
1217
1228
|
params: {
|
|
1218
|
-
prompt
|
|
1229
|
+
// Replicate's Seedance wrapper hard-caps the prompt at 4000 chars; gate
|
|
1230
|
+
// it here so an over-length prompt fails validate (free) not the billed call.
|
|
1231
|
+
prompt: { kind: "string", maxLength: 4e3 },
|
|
1219
1232
|
aspect_ratio: {
|
|
1220
1233
|
kind: "string",
|
|
1221
1234
|
enum: ["1:1", "3:4", "9:16", "4:3", "16:9", "21:9", "9:21"]
|
|
1222
1235
|
},
|
|
1223
|
-
resolution: { kind: "string", enum: ["480p", "720p", "1080p"] },
|
|
1236
|
+
resolution: { kind: "string", enum: ["480p", "720p", "1080p", "4k"] },
|
|
1224
1237
|
duration: { kind: "number", enum: SEEDANCE_DURATIONS },
|
|
1225
1238
|
seed: { kind: "number" },
|
|
1226
1239
|
generate_audio: { kind: "boolean" }
|
|
@@ -1241,7 +1254,10 @@ var MODEL_REGISTRY = {
|
|
|
1241
1254
|
duration: { kind: "number", enum: [4, 6, 8] },
|
|
1242
1255
|
seed: { kind: "number" },
|
|
1243
1256
|
generate_audio: { kind: "boolean" },
|
|
1244
|
-
|
|
1257
|
+
// Image-to-video and EU/UK/CH/MENA regions cap this at `allow_adult`;
|
|
1258
|
+
// `allow_all` is text-to-video only. Allow both so an image-conditioned
|
|
1259
|
+
// Veo clip (the real-face fallback) validates.
|
|
1260
|
+
person_generation: { kind: "string", enum: ["allow_all", "allow_adult"] },
|
|
1245
1261
|
enhance_prompt: { kind: "boolean" },
|
|
1246
1262
|
conditioning_scale: { kind: "number" }
|
|
1247
1263
|
}
|
|
@@ -1292,8 +1308,8 @@ var MODEL_REGISTRY = {
|
|
|
1292
1308
|
"fal/veed-lipsync": {
|
|
1293
1309
|
label: "VEED Lipsync (fal.ai)",
|
|
1294
1310
|
inputs: [
|
|
1295
|
-
{ kind: "video", mimes:
|
|
1296
|
-
{ kind: "audio", mimes:
|
|
1311
|
+
{ kind: "video", mimes: REPLICATE_VIDEO_MIMES },
|
|
1312
|
+
{ kind: "audio", mimes: REPLICATE_AUDIO_MIMES }
|
|
1297
1313
|
],
|
|
1298
1314
|
required: [],
|
|
1299
1315
|
params: {}
|
|
@@ -1329,7 +1345,7 @@ var MODEL_REGISTRY = {
|
|
|
1329
1345
|
// TARGET voice, preserving timing/prosody. Used to normalize a talking-head
|
|
1330
1346
|
// clip's native (generator-chosen) voice into ONE consistent brand voice.
|
|
1331
1347
|
label: "ElevenLabs Voice Changer (multilingual STS v2)",
|
|
1332
|
-
inputs: [{ kind: "audio", mimes:
|
|
1348
|
+
inputs: [{ kind: "audio", mimes: REPLICATE_AUDIO_MIMES }],
|
|
1333
1349
|
required: ["voice"],
|
|
1334
1350
|
params: {
|
|
1335
1351
|
voice: { kind: "string" },
|
|
@@ -1356,7 +1372,7 @@ var MODEL_REGISTRY = {
|
|
|
1356
1372
|
},
|
|
1357
1373
|
"elevenlabs/video-background-music-v1": {
|
|
1358
1374
|
label: "ElevenLabs Video Background Music v1",
|
|
1359
|
-
inputs: [{ kind: "video", mimes:
|
|
1375
|
+
inputs: [{ kind: "video", mimes: REPLICATE_VIDEO_MIMES }],
|
|
1360
1376
|
required: [],
|
|
1361
1377
|
params: {
|
|
1362
1378
|
description: { kind: "string" },
|
|
@@ -1527,7 +1543,7 @@ function validateValue(key, value, schema, model) {
|
|
|
1527
1543
|
}
|
|
1528
1544
|
|
|
1529
1545
|
// src/engine/lib/concurrency.ts
|
|
1530
|
-
var DEFAULT_CONCURRENCY =
|
|
1546
|
+
var DEFAULT_CONCURRENCY = 8;
|
|
1531
1547
|
function resolveConcurrency(...candidates) {
|
|
1532
1548
|
for (const candidate of candidates) {
|
|
1533
1549
|
if (candidate === void 0 || candidate === "") continue;
|
|
@@ -1772,7 +1788,14 @@ var NodeDecl = z.object({
|
|
|
1772
1788
|
version: z.string().min(1).optional(),
|
|
1773
1789
|
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
1774
1790
|
params: z.record(z.string(), z.unknown()).optional(),
|
|
1775
|
-
when: z.unknown().optional()
|
|
1791
|
+
when: z.unknown().optional(),
|
|
1792
|
+
// Regenerate knob. The engine is content-addressed: identical params + inputs
|
|
1793
|
+
// return the cached render, so re-running an unchanged node NEVER re-bills or
|
|
1794
|
+
// produces a new result. Bump this token (any string/number — a `2`, a `"v3"`,
|
|
1795
|
+
// a note) and re-run to force THIS node to render fresh; because its new output
|
|
1796
|
+
// changes downstream input hashes, everything depending on it regenerates too.
|
|
1797
|
+
// This is the declarative "change a value, re-run, get a new render" affordance.
|
|
1798
|
+
regenerate: z.union([z.string(), z.number()]).optional()
|
|
1776
1799
|
}).strict();
|
|
1777
1800
|
var OutputRef = z.object({
|
|
1778
1801
|
node: z.string(),
|
|
@@ -3055,6 +3078,7 @@ var Engine = class {
|
|
|
3055
3078
|
const counters = { cachedNodes: 0, totalCredits: 0 };
|
|
3056
3079
|
const nodeRuns = [];
|
|
3057
3080
|
const graph = this.pruneToOutput(canvas, buildGraph(canvas));
|
|
3081
|
+
const needsBytes = computeNeedsLocalBytes(canvas, graph, this.registry);
|
|
3058
3082
|
this.emitProgress(opts, {
|
|
3059
3083
|
kind: "plan",
|
|
3060
3084
|
nodes: [...graph.entries()].map(([id, deps]) => {
|
|
@@ -3062,7 +3086,7 @@ var Engine = class {
|
|
|
3062
3086
|
return { node_id: id, node_type: node?.type ?? "unknown", deps: [...deps], params: node?.params };
|
|
3063
3087
|
})
|
|
3064
3088
|
});
|
|
3065
|
-
await this.runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns);
|
|
3089
|
+
await this.runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns, needsBytes);
|
|
3066
3090
|
const output = pickFinalOutput(canvas, outputs);
|
|
3067
3091
|
const stats = {
|
|
3068
3092
|
total_nodes: canvas.nodes.length,
|
|
@@ -3087,13 +3111,13 @@ var Engine = class {
|
|
|
3087
3111
|
this.log(`outputs in: ${writer.runDir}`);
|
|
3088
3112
|
return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir, node_runs: nodeRuns };
|
|
3089
3113
|
}
|
|
3090
|
-
async runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns) {
|
|
3114
|
+
async runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns, needsBytes) {
|
|
3091
3115
|
const layers = topologicalLayers(graph);
|
|
3092
3116
|
const limit = resolveConcurrency(opts.concurrency);
|
|
3093
3117
|
for (const layer of layers) {
|
|
3094
3118
|
const settled = await mapWithConcurrency(layer, limit, (nodeId) => {
|
|
3095
3119
|
this.emitProgress(opts, { kind: "node_start", node_id: nodeId });
|
|
3096
|
-
return this.executeOne(canvas, nodeId, outputs, runId, writer, opts).then((r) => {
|
|
3120
|
+
return this.executeOne(canvas, nodeId, outputs, runId, writer, opts, needsBytes.has(nodeId)).then((r) => {
|
|
3097
3121
|
if (r.cached) counters.cachedNodes++;
|
|
3098
3122
|
counters.totalCredits += r.credits;
|
|
3099
3123
|
const node = canvas.nodes.find((n) => n.id === nodeId);
|
|
@@ -3160,12 +3184,13 @@ var Engine = class {
|
|
|
3160
3184
|
}
|
|
3161
3185
|
await writer.writeManifest("_final", output);
|
|
3162
3186
|
}
|
|
3163
|
-
async executeOne(canvas, nodeId, outputs, runId, writer, opts) {
|
|
3187
|
+
async executeOne(canvas, nodeId, outputs, runId, writer, opts, downloadOutputs) {
|
|
3164
3188
|
const node = canvas.nodes.find((n) => n.id === nodeId);
|
|
3165
3189
|
if (!node) throw new Error(`executor: missing node ${nodeId}`);
|
|
3166
3190
|
const def = this.registry.get(node.type);
|
|
3167
3191
|
if (!def) throw new Error(`executor: missing registry entry for type ${node.type}`);
|
|
3168
|
-
const
|
|
3192
|
+
const regenerateToken = resolveRegenerateToken(node, opts.regenerate, runId);
|
|
3193
|
+
const prepared = await prepareForExecution(node, outputs, def, canvas.cache_salt, regenerateToken, this.assets);
|
|
3169
3194
|
const policy = opts.cache_policy ?? "read_write";
|
|
3170
3195
|
if (policy !== "bypass") {
|
|
3171
3196
|
const cacheT0 = Date.now();
|
|
@@ -3183,12 +3208,14 @@ var Engine = class {
|
|
|
3183
3208
|
nodeId: node.id,
|
|
3184
3209
|
nodeType: node.type,
|
|
3185
3210
|
cacheKey: prepared.cacheKey,
|
|
3211
|
+
downloadOutputs,
|
|
3186
3212
|
client: this.client,
|
|
3187
3213
|
assets: this.assets,
|
|
3188
3214
|
log: this.log,
|
|
3189
3215
|
signal: opts.signal
|
|
3190
3216
|
};
|
|
3191
|
-
const
|
|
3217
|
+
const preparedForExec = needsLocalMaterialization(def) ? { ...prepared, resolvedInputs: await this.materializeLocalInputs(prepared.resolvedInputs) } : prepared;
|
|
3218
|
+
const { parsedInputs, parsedParams } = parseNodeArgs(def, preparedForExec, node.id, node.type);
|
|
3192
3219
|
const result = await invokeExecute(def, parsedInputs, parsedParams, ctx, node.id, node.type);
|
|
3193
3220
|
const elapsed = Date.now() - t0;
|
|
3194
3221
|
const credits = def.cost ? def.cost({ params: parsedParams }).credits : 0;
|
|
@@ -3230,8 +3257,42 @@ var Engine = class {
|
|
|
3230
3257
|
}
|
|
3231
3258
|
}
|
|
3232
3259
|
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Download any URL-only asset ref reachable in a local node's inputs so the
|
|
3262
|
+
* bytes are on disk before the local runner stages them. Returns a copy —
|
|
3263
|
+
* refs are replaced, never mutated in place, so the producer's cached output
|
|
3264
|
+
* (shared object) keeps its URL-only shape.
|
|
3265
|
+
*/
|
|
3266
|
+
async materializeLocalInputs(inputs) {
|
|
3267
|
+
const fix = async (value) => {
|
|
3268
|
+
if (Array.isArray(value)) return Promise.all(value.map(fix));
|
|
3269
|
+
if (value && typeof value === "object") {
|
|
3270
|
+
const v = value;
|
|
3271
|
+
if (typeof v.kind === "string" && typeof v.url === "string" && typeof v.sha256 === "string" && typeof v.mime === "string" && typeof v.path !== "string") {
|
|
3272
|
+
this.log(`[warn ] materializing URL-only input on demand (${v.kind}/${v.mime}) \u2014 missed graph edge`);
|
|
3273
|
+
return this.assets.ingestRemote({
|
|
3274
|
+
kind: v.kind,
|
|
3275
|
+
url: v.url,
|
|
3276
|
+
sha256: v.sha256,
|
|
3277
|
+
mime: v.mime,
|
|
3278
|
+
metadata: v.metadata
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
const out = {};
|
|
3282
|
+
for (const [k, val] of Object.entries(v)) out[k] = await fix(val);
|
|
3283
|
+
return out;
|
|
3284
|
+
}
|
|
3285
|
+
return value;
|
|
3286
|
+
};
|
|
3287
|
+
return await fix(inputs);
|
|
3288
|
+
}
|
|
3233
3289
|
};
|
|
3234
|
-
|
|
3290
|
+
function resolveRegenerateToken(node, forced, runId) {
|
|
3291
|
+
if (forced?.has(node.id)) return `run:${runId}`;
|
|
3292
|
+
if (node.regenerate !== void 0) return `node:${String(node.regenerate)}`;
|
|
3293
|
+
return void 0;
|
|
3294
|
+
}
|
|
3295
|
+
async function prepareForExecution(node, outputs, def, cacheSalt, regenerateToken, assets) {
|
|
3235
3296
|
const resolvedInputs = resolveRefs(node.inputs ?? {}, { outputs }) ?? {};
|
|
3236
3297
|
const resolvedParams = resolveRefs(node.params ?? {}, { outputs }) ?? {};
|
|
3237
3298
|
const slotValues = await hydrateTextSlots(resolvedInputs, assets, node.id, node.type);
|
|
@@ -3244,6 +3305,9 @@ async function prepareForExecution(node, outputs, def, cacheSalt, assets) {
|
|
|
3244
3305
|
throw new NodeExecutionError(node.id, node.type, { kind: "local", cause: e });
|
|
3245
3306
|
}
|
|
3246
3307
|
}
|
|
3308
|
+
if (regenerateToken !== void 0) {
|
|
3309
|
+
extras = { ...extras ?? {}, __regenerate__: regenerateToken };
|
|
3310
|
+
}
|
|
3247
3311
|
const cacheKey = computeCacheKey({
|
|
3248
3312
|
node_id: node.type,
|
|
3249
3313
|
node_version: def.version,
|
|
@@ -3272,6 +3336,9 @@ async function invokeExecute(def, parsedInputs, parsedParams, ctx, nodeId, nodeT
|
|
|
3272
3336
|
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3273
3337
|
}
|
|
3274
3338
|
}
|
|
3339
|
+
function needsLocalMaterialization(def) {
|
|
3340
|
+
return def.location === "local" && !def.passthroughRefs;
|
|
3341
|
+
}
|
|
3275
3342
|
function pickFinalOutput(canvas, outputs) {
|
|
3276
3343
|
if (canvas.output) {
|
|
3277
3344
|
const node = outputs[canvas.output.node];
|
|
@@ -3282,6 +3349,16 @@ function pickFinalOutput(canvas, outputs) {
|
|
|
3282
3349
|
const lastOut = outputs[last.id];
|
|
3283
3350
|
return lastOut ? Object.values(lastOut)[0] : void 0;
|
|
3284
3351
|
}
|
|
3352
|
+
function computeNeedsLocalBytes(canvas, graph, registry) {
|
|
3353
|
+
const typeById = new Map(canvas.nodes.map((n) => [n.id, n.type]));
|
|
3354
|
+
const needs = /* @__PURE__ */ new Set();
|
|
3355
|
+
for (const [consumerId, deps] of graph) {
|
|
3356
|
+
const def = registry.get(typeById.get(consumerId) ?? "");
|
|
3357
|
+
if (def?.location !== "local" || def.passthroughRefs) continue;
|
|
3358
|
+
for (const dep of deps) needs.add(dep);
|
|
3359
|
+
}
|
|
3360
|
+
return needs;
|
|
3361
|
+
}
|
|
3285
3362
|
function buildGraph(canvas) {
|
|
3286
3363
|
const graph = /* @__PURE__ */ new Map();
|
|
3287
3364
|
for (const n of canvas.nodes) graph.set(n.id, /* @__PURE__ */ new Set());
|
|
@@ -3412,7 +3489,16 @@ async function hydrateSlotValue(value, assets, nodeId, nodeType) {
|
|
|
3412
3489
|
try {
|
|
3413
3490
|
bytes = await assets.readBytes(value.sha256, value.mime);
|
|
3414
3491
|
} catch (e) {
|
|
3415
|
-
|
|
3492
|
+
if (value.url) {
|
|
3493
|
+
try {
|
|
3494
|
+
await assets.ingestRemote({ kind: value.kind, url: value.url, sha256: value.sha256, mime: value.mime });
|
|
3495
|
+
bytes = await assets.readBytes(value.sha256, value.mime);
|
|
3496
|
+
} catch (e2) {
|
|
3497
|
+
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e2 });
|
|
3498
|
+
}
|
|
3499
|
+
} else {
|
|
3500
|
+
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3501
|
+
}
|
|
3416
3502
|
}
|
|
3417
3503
|
if (bytes.length > MAX_INLINE_TEXT_BYTES) {
|
|
3418
3504
|
throw new NodeExecutionError(nodeId, nodeType, {
|
|
@@ -3547,7 +3633,9 @@ async function callBackendExec(args) {
|
|
|
3547
3633
|
nodeVersion: args.nodeVersion,
|
|
3548
3634
|
params: args.params,
|
|
3549
3635
|
inputs: serialized,
|
|
3550
|
-
idempotency_key: idempotencyKey
|
|
3636
|
+
idempotency_key: idempotencyKey,
|
|
3637
|
+
canvas_run_id: args.ctx.canvasRunId,
|
|
3638
|
+
node_id: args.ctx.nodeId
|
|
3551
3639
|
},
|
|
3552
3640
|
args.ctx.signal
|
|
3553
3641
|
);
|
|
@@ -3599,6 +3687,9 @@ async function ingestValue(value, ctx, declaredKind) {
|
|
|
3599
3687
|
}
|
|
3600
3688
|
if (isRawAsset(value)) {
|
|
3601
3689
|
const kind = value.kind ?? declaredKind ?? "json";
|
|
3690
|
+
if (ctx.downloadOutputs === false) {
|
|
3691
|
+
return buildRef({ kind, sha: value.sha256, mime: value.mime, url: value.url, metadata: value.metadata });
|
|
3692
|
+
}
|
|
3602
3693
|
return ctx.assets.ingestRemote({
|
|
3603
3694
|
kind,
|
|
3604
3695
|
url: value.url,
|
|
@@ -3836,7 +3927,15 @@ var EXT_TO_MIME = {
|
|
|
3836
3927
|
jpeg: "image/jpeg",
|
|
3837
3928
|
webp: "image/webp",
|
|
3838
3929
|
gif: "image/gif",
|
|
3930
|
+
// Non-model-safe rasters `toModelSafeImage` transcodes to PNG at ingest — they
|
|
3931
|
+
// must resolve to an image mime here or the kind-check rejects the local file
|
|
3932
|
+
// before normalization ever runs.
|
|
3839
3933
|
avif: "image/avif",
|
|
3934
|
+
heic: "image/heic",
|
|
3935
|
+
heif: "image/heif",
|
|
3936
|
+
tif: "image/tiff",
|
|
3937
|
+
tiff: "image/tiff",
|
|
3938
|
+
bmp: "image/bmp",
|
|
3840
3939
|
mp4: "video/mp4",
|
|
3841
3940
|
webm: "video/webm",
|
|
3842
3941
|
mov: "video/quicktime",
|
|
@@ -3906,15 +4005,26 @@ async function toModelSafeImage(bytes) {
|
|
|
3906
4005
|
throw new Error(`bytes are not a decodable image (${e.message})`);
|
|
3907
4006
|
}
|
|
3908
4007
|
}
|
|
4008
|
+
function hasAscii(buf, offset, sig) {
|
|
4009
|
+
return buf.length >= offset + sig.length && buf.toString("ascii", offset, offset + sig.length) === sig;
|
|
4010
|
+
}
|
|
4011
|
+
var HEIC_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "heim", "heis", "hevc", "hevx", "mif1", "msf1", "heif"]);
|
|
4012
|
+
function sniffIsoBmff(buf) {
|
|
4013
|
+
if (!hasAscii(buf, 4, "ftyp")) return null;
|
|
4014
|
+
const brand = buf.subarray(8, 12).toString("ascii");
|
|
4015
|
+
if (brand === "avif" || brand === "avis") return "image/avif";
|
|
4016
|
+
if (HEIC_BRANDS.has(brand)) return "image/heic";
|
|
4017
|
+
return null;
|
|
4018
|
+
}
|
|
3909
4019
|
function sniffImageMime(buf) {
|
|
3910
4020
|
if (buf.length < 4) return null;
|
|
3911
|
-
if (buf[0] === 137 && buf
|
|
4021
|
+
if (buf[0] === 137 && hasAscii(buf, 1, "PNG")) return "image/png";
|
|
3912
4022
|
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
3913
|
-
if (buf
|
|
3914
|
-
if (buf
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
return
|
|
4023
|
+
if (hasAscii(buf, 0, "GIF")) return "image/gif";
|
|
4024
|
+
if (hasAscii(buf, 0, "RIFF") && hasAscii(buf, 8, "WEBP")) return "image/webp";
|
|
4025
|
+
if (hasAscii(buf, 0, "II*\0") || hasAscii(buf, 0, "MM\0*")) return "image/tiff";
|
|
4026
|
+
if (hasAscii(buf, 0, "BM")) return "image/bmp";
|
|
4027
|
+
return sniffIsoBmff(buf);
|
|
3918
4028
|
}
|
|
3919
4029
|
function findBoxPayload(buf, start, end, type) {
|
|
3920
4030
|
let offset = start;
|
|
@@ -4575,19 +4685,56 @@ var audioTimelineNode = defineNode({
|
|
|
4575
4685
|
}
|
|
4576
4686
|
});
|
|
4577
4687
|
|
|
4578
|
-
// src/engine/nodes/local/
|
|
4688
|
+
// src/engine/nodes/local/collect.ts
|
|
4579
4689
|
import { z as z7 } from "zod";
|
|
4690
|
+
var collectNode = defineNode({
|
|
4691
|
+
id: "collect",
|
|
4692
|
+
version: "1.0.0",
|
|
4693
|
+
category: "data",
|
|
4694
|
+
location: "local",
|
|
4695
|
+
passthroughRefs: true,
|
|
4696
|
+
summary: "Gather images from multiple upstream nodes into one ordered array \u2014 the standard terminal for multi-variant canvases whose final output is several images.",
|
|
4697
|
+
when_to_use: "Point the canvas `output` at this node when several independent branches (e.g. one image_generate per scene/variant) must ALL be finals. Wire `inputs.images` as an array of refs like `$ref:gen_billboard_03.images#0` \u2014 each final inherits its producer node id as its label (or set `params.labels` to override), so variants stay identifiable in the dashboard and selection.",
|
|
4698
|
+
inputs: z7.object({ images: z7.array(ImageRef).min(1) }).strict(),
|
|
4699
|
+
params: z7.object({ labels: z7.array(z7.string().min(1)).min(1).optional() }).strict(),
|
|
4700
|
+
outputs: z7.object({ images: z7.array(ImageRef).min(1) }).strict(),
|
|
4701
|
+
outputKinds: { images: "image" },
|
|
4702
|
+
cost: () => ({ credits: 0, seconds_estimate: 0 }),
|
|
4703
|
+
// Arity is only knowable at validate time when `images` is a literal array;
|
|
4704
|
+
// a single `$ref:` string to an upstream array output defers to runtime.
|
|
4705
|
+
validateExtra: ({ rawParams, rawInputs }) => {
|
|
4706
|
+
const labels = rawParams?.labels;
|
|
4707
|
+
if (!Array.isArray(labels)) return [];
|
|
4708
|
+
if (new Set(labels).size !== labels.length) {
|
|
4709
|
+
return [{ path: "params.labels", message: "labels must be unique \u2014 each names one output variant" }];
|
|
4710
|
+
}
|
|
4711
|
+
const images = rawInputs?.images;
|
|
4712
|
+
if (Array.isArray(images) && labels.length !== images.length) {
|
|
4713
|
+
return [
|
|
4714
|
+
{
|
|
4715
|
+
path: "params.labels",
|
|
4716
|
+
message: `labels has ${labels.length} entries but ${images.length} images are wired \u2014 provide one label per image`
|
|
4717
|
+
}
|
|
4718
|
+
];
|
|
4719
|
+
}
|
|
4720
|
+
return [];
|
|
4721
|
+
},
|
|
4722
|
+
execute: ({ inputs }) => Promise.resolve({ images: inputs.images })
|
|
4723
|
+
});
|
|
4724
|
+
|
|
4725
|
+
// src/engine/nodes/local/ffmpeg.ts
|
|
4726
|
+
import { z as z8 } from "zod";
|
|
4580
4727
|
var FFMPEG_BIN2 = "ffmpeg";
|
|
4581
|
-
var OutputDecl =
|
|
4582
|
-
kind:
|
|
4583
|
-
ext:
|
|
4728
|
+
var OutputDecl = z8.object({
|
|
4729
|
+
kind: z8.enum(["image", "video", "audio"]),
|
|
4730
|
+
ext: z8.string().min(1).max(8)
|
|
4584
4731
|
}).strict();
|
|
4585
|
-
var FfmpegParams =
|
|
4586
|
-
args:
|
|
4587
|
-
outputs:
|
|
4732
|
+
var FfmpegParams = z8.object({
|
|
4733
|
+
args: z8.array(z8.string()).min(1),
|
|
4734
|
+
outputs: z8.record(z8.string(), OutputDecl).default({})
|
|
4588
4735
|
}).strict();
|
|
4589
|
-
var FfmpegInputs =
|
|
4590
|
-
var FfmpegOutputs =
|
|
4736
|
+
var FfmpegInputs = z8.record(z8.string(), z8.unknown());
|
|
4737
|
+
var FfmpegOutputs = z8.record(z8.string(), z8.custom());
|
|
4591
4738
|
var ffmpegNode = defineNode({
|
|
4592
4739
|
id: "ffmpeg",
|
|
4593
4740
|
version: "2.0.0",
|
|
@@ -4618,7 +4765,7 @@ import { mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/prom
|
|
|
4618
4765
|
import { createRequire } from "module";
|
|
4619
4766
|
import { tmpdir as tmpdir3 } from "os";
|
|
4620
4767
|
import path6 from "path";
|
|
4621
|
-
import { z as
|
|
4768
|
+
import { z as z9 } from "zod";
|
|
4622
4769
|
|
|
4623
4770
|
// src/engine/nodes/local/lib/assets.ts
|
|
4624
4771
|
import { copyFile as copyFile3, readFile as readFile4 } from "fs/promises";
|
|
@@ -4654,15 +4801,15 @@ var DEFAULT_SPECIMEN = [
|
|
|
4654
4801
|
"abcdefghijklmnopqrstuvwxyz",
|
|
4655
4802
|
`0123456789 !?&@#$%().,:;'"-`
|
|
4656
4803
|
].join("\n");
|
|
4657
|
-
var FontSpecimenParams =
|
|
4658
|
-
text:
|
|
4659
|
-
font_size:
|
|
4660
|
-
padding:
|
|
4661
|
-
line_height:
|
|
4662
|
-
max_width:
|
|
4804
|
+
var FontSpecimenParams = z9.object({
|
|
4805
|
+
text: z9.string().min(1).max(2e3).optional().default(DEFAULT_SPECIMEN),
|
|
4806
|
+
font_size: z9.number().int().min(8).max(512).optional().default(72),
|
|
4807
|
+
padding: z9.number().int().min(0).max(512).optional().default(64),
|
|
4808
|
+
line_height: z9.number().min(0.8).max(3).optional().default(1.35),
|
|
4809
|
+
max_width: z9.number().int().min(256).max(4096).optional()
|
|
4663
4810
|
}).strict();
|
|
4664
|
-
var FontSpecimenInputs =
|
|
4665
|
-
var FontSpecimenOutputs =
|
|
4811
|
+
var FontSpecimenInputs = z9.object({ font: FontRef }).loose();
|
|
4812
|
+
var FontSpecimenOutputs = z9.object({ image: ImageRef }).strict();
|
|
4666
4813
|
var DEVICE_SCALE_FACTOR = 2;
|
|
4667
4814
|
var PAGE_TIMEOUT_MS = 3e4;
|
|
4668
4815
|
function escapeHtml(text) {
|
|
@@ -4803,7 +4950,7 @@ import { createRequire as createRequire2 } from "module";
|
|
|
4803
4950
|
import { cpus, tmpdir as tmpdir4 } from "os";
|
|
4804
4951
|
import path11 from "path";
|
|
4805
4952
|
import { promisify as promisify4 } from "util";
|
|
4806
|
-
import { z as
|
|
4953
|
+
import { z as z11 } from "zod";
|
|
4807
4954
|
|
|
4808
4955
|
// src/engine/engine/composition-hash.ts
|
|
4809
4956
|
import { readdir as readdir2, readFile as readFile5, stat as stat4 } from "fs/promises";
|
|
@@ -4841,62 +4988,62 @@ async function collectFiles(root, current) {
|
|
|
4841
4988
|
// src/engine/engine/composition-meta.ts
|
|
4842
4989
|
import { readFile as readFile6 } from "fs/promises";
|
|
4843
4990
|
import path8 from "path";
|
|
4844
|
-
import { z as
|
|
4845
|
-
var InputKind =
|
|
4846
|
-
var InputSpec =
|
|
4991
|
+
import { z as z10 } from "zod";
|
|
4992
|
+
var InputKind = z10.enum(["video", "image", "audio", "json"]);
|
|
4993
|
+
var InputSpec = z10.object({
|
|
4847
4994
|
kind: InputKind,
|
|
4848
|
-
required:
|
|
4995
|
+
required: z10.boolean().optional().default(false),
|
|
4849
4996
|
// Filename the composition's HTML references (e.g. `input.mp4`, `logo.png`).
|
|
4850
4997
|
// Defaults to `<key><ext>` derived from the kind.
|
|
4851
|
-
staged_as:
|
|
4852
|
-
description:
|
|
4998
|
+
staged_as: z10.string().min(1).optional(),
|
|
4999
|
+
description: z10.string().optional()
|
|
4853
5000
|
}).strict();
|
|
4854
5001
|
var ParamSpecBase = {
|
|
4855
|
-
required:
|
|
4856
|
-
description:
|
|
5002
|
+
required: z10.boolean().optional().default(false),
|
|
5003
|
+
description: z10.string().optional()
|
|
4857
5004
|
};
|
|
4858
|
-
var StringParam =
|
|
5005
|
+
var StringParam = z10.object({
|
|
4859
5006
|
...ParamSpecBase,
|
|
4860
|
-
kind:
|
|
4861
|
-
default:
|
|
4862
|
-
enum:
|
|
5007
|
+
kind: z10.literal("string"),
|
|
5008
|
+
default: z10.string().optional(),
|
|
5009
|
+
enum: z10.array(z10.string()).optional()
|
|
4863
5010
|
}).strict();
|
|
4864
|
-
var IntegerParam =
|
|
5011
|
+
var IntegerParam = z10.object({
|
|
4865
5012
|
...ParamSpecBase,
|
|
4866
|
-
kind:
|
|
4867
|
-
default:
|
|
4868
|
-
min:
|
|
4869
|
-
max:
|
|
5013
|
+
kind: z10.literal("integer"),
|
|
5014
|
+
default: z10.number().int().optional(),
|
|
5015
|
+
min: z10.number().int().optional(),
|
|
5016
|
+
max: z10.number().int().optional()
|
|
4870
5017
|
}).strict();
|
|
4871
|
-
var NumberParam =
|
|
5018
|
+
var NumberParam = z10.object({
|
|
4872
5019
|
...ParamSpecBase,
|
|
4873
|
-
kind:
|
|
4874
|
-
default:
|
|
4875
|
-
min:
|
|
4876
|
-
max:
|
|
5020
|
+
kind: z10.literal("number"),
|
|
5021
|
+
default: z10.number().optional(),
|
|
5022
|
+
min: z10.number().optional(),
|
|
5023
|
+
max: z10.number().optional()
|
|
4877
5024
|
}).strict();
|
|
4878
|
-
var BooleanParam =
|
|
5025
|
+
var BooleanParam = z10.object({
|
|
4879
5026
|
...ParamSpecBase,
|
|
4880
|
-
kind:
|
|
4881
|
-
default:
|
|
5027
|
+
kind: z10.literal("boolean"),
|
|
5028
|
+
default: z10.boolean().optional()
|
|
4882
5029
|
}).strict();
|
|
4883
|
-
var ColorParam =
|
|
5030
|
+
var ColorParam = z10.object({
|
|
4884
5031
|
...ParamSpecBase,
|
|
4885
|
-
kind:
|
|
4886
|
-
default:
|
|
5032
|
+
kind: z10.literal("color"),
|
|
5033
|
+
default: z10.string().optional()
|
|
4887
5034
|
}).strict();
|
|
4888
|
-
var ImageParam =
|
|
5035
|
+
var ImageParam = z10.object({
|
|
4889
5036
|
...ParamSpecBase,
|
|
4890
|
-
kind:
|
|
4891
|
-
default:
|
|
5037
|
+
kind: z10.literal("image"),
|
|
5038
|
+
default: z10.string().optional()
|
|
4892
5039
|
}).strict();
|
|
4893
|
-
var JsonParam =
|
|
5040
|
+
var JsonParam = z10.object({
|
|
4894
5041
|
...ParamSpecBase,
|
|
4895
|
-
kind:
|
|
4896
|
-
schema:
|
|
4897
|
-
default:
|
|
5042
|
+
kind: z10.literal("json"),
|
|
5043
|
+
schema: z10.unknown().optional(),
|
|
5044
|
+
default: z10.unknown().optional()
|
|
4898
5045
|
}).strict();
|
|
4899
|
-
var ParamSpec =
|
|
5046
|
+
var ParamSpec = z10.discriminatedUnion("kind", [
|
|
4900
5047
|
StringParam,
|
|
4901
5048
|
IntegerParam,
|
|
4902
5049
|
NumberParam,
|
|
@@ -4905,16 +5052,16 @@ var ParamSpec = z9.discriminatedUnion("kind", [
|
|
|
4905
5052
|
ImageParam,
|
|
4906
5053
|
JsonParam
|
|
4907
5054
|
]);
|
|
4908
|
-
var CompositionMetaSchema =
|
|
4909
|
-
id:
|
|
4910
|
-
title:
|
|
4911
|
-
description:
|
|
4912
|
-
width:
|
|
4913
|
-
height:
|
|
4914
|
-
fps:
|
|
4915
|
-
default_duration:
|
|
4916
|
-
inputs:
|
|
4917
|
-
params:
|
|
5055
|
+
var CompositionMetaSchema = z10.object({
|
|
5056
|
+
id: z10.string().min(1),
|
|
5057
|
+
title: z10.string().min(1),
|
|
5058
|
+
description: z10.string().optional(),
|
|
5059
|
+
width: z10.number().int().positive(),
|
|
5060
|
+
height: z10.number().int().positive(),
|
|
5061
|
+
fps: z10.number().int().positive().default(30),
|
|
5062
|
+
default_duration: z10.number().positive().default(10),
|
|
5063
|
+
inputs: z10.record(z10.string(), InputSpec).default({}),
|
|
5064
|
+
params: z10.record(z10.string(), ParamSpec).default({})
|
|
4918
5065
|
}).strict();
|
|
4919
5066
|
async function loadCompositionMeta(compositionDir) {
|
|
4920
5067
|
const metaPath = path8.join(compositionDir, "meta.json");
|
|
@@ -4942,39 +5089,39 @@ function buildParamsSchema(meta) {
|
|
|
4942
5089
|
for (const [name, spec] of Object.entries(meta.params)) {
|
|
4943
5090
|
shape[name] = buildParamFieldSchema(name, spec);
|
|
4944
5091
|
}
|
|
4945
|
-
return
|
|
5092
|
+
return z10.object(shape).strict();
|
|
4946
5093
|
}
|
|
4947
5094
|
function buildParamFieldSchema(name, spec) {
|
|
4948
5095
|
switch (spec.kind) {
|
|
4949
5096
|
case "string": {
|
|
4950
|
-
const s = spec.enum && spec.enum.length > 0 ?
|
|
5097
|
+
const s = spec.enum && spec.enum.length > 0 ? z10.enum(spec.enum) : z10.string();
|
|
4951
5098
|
return finalize(s, spec.default, spec.required);
|
|
4952
5099
|
}
|
|
4953
5100
|
case "integer": {
|
|
4954
|
-
let s =
|
|
5101
|
+
let s = z10.number().int();
|
|
4955
5102
|
if (spec.min !== void 0) s = s.min(spec.min);
|
|
4956
5103
|
if (spec.max !== void 0) s = s.max(spec.max);
|
|
4957
5104
|
return finalize(s, spec.default, spec.required);
|
|
4958
5105
|
}
|
|
4959
5106
|
case "number": {
|
|
4960
|
-
let s =
|
|
5107
|
+
let s = z10.number();
|
|
4961
5108
|
if (spec.min !== void 0) s = s.min(spec.min);
|
|
4962
5109
|
if (spec.max !== void 0) s = s.max(spec.max);
|
|
4963
5110
|
return finalize(s, spec.default, spec.required);
|
|
4964
5111
|
}
|
|
4965
5112
|
case "boolean":
|
|
4966
|
-
return finalize(
|
|
5113
|
+
return finalize(z10.boolean(), spec.default, spec.required);
|
|
4967
5114
|
case "color": {
|
|
4968
|
-
const s =
|
|
5115
|
+
const s = z10.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
|
|
4969
5116
|
message: `param "${name}": must be a 3/6/8-digit hex color (e.g. "#ff0066")`
|
|
4970
5117
|
});
|
|
4971
5118
|
return finalize(s, spec.default, spec.required);
|
|
4972
5119
|
}
|
|
4973
5120
|
case "image":
|
|
4974
|
-
return finalize(
|
|
5121
|
+
return finalize(z10.union([z10.string().min(1), z10.record(z10.string(), z10.unknown())]), spec.default, spec.required);
|
|
4975
5122
|
case "json":
|
|
4976
5123
|
return finalize(
|
|
4977
|
-
|
|
5124
|
+
z10.unknown().refine((v) => v !== void 0, { message: `param "${name}" is required` }),
|
|
4978
5125
|
spec.default,
|
|
4979
5126
|
spec.required
|
|
4980
5127
|
);
|
|
@@ -5274,17 +5421,17 @@ function literalize(value) {
|
|
|
5274
5421
|
// src/engine/nodes/local/hyperframe.ts
|
|
5275
5422
|
var execFileAsync2 = promisify4(execFile4);
|
|
5276
5423
|
var require_2 = createRequire2(import.meta.url);
|
|
5277
|
-
var HyperframeParams =
|
|
5278
|
-
composition:
|
|
5424
|
+
var HyperframeParams = z11.object({
|
|
5425
|
+
composition: z11.string().min(1),
|
|
5279
5426
|
// Output container. mp4 (default) for delivery; webm/mov render WITH
|
|
5280
5427
|
// transparency (alpha) when the composition background is transparent —
|
|
5281
5428
|
// use for motion-graphic overlays dropped into Premiere/AE/Nuke.
|
|
5282
|
-
format:
|
|
5283
|
-
timeout_ms:
|
|
5284
|
-
}).catchall(
|
|
5285
|
-
var HyperframeInputs =
|
|
5286
|
-
var HyperframeOutputs =
|
|
5287
|
-
video:
|
|
5429
|
+
format: z11.enum(["mp4", "webm", "mov"]).optional().default("mp4"),
|
|
5430
|
+
timeout_ms: z11.number().int().positive().optional().default(10 * 60 * 1e3)
|
|
5431
|
+
}).catchall(z11.unknown());
|
|
5432
|
+
var HyperframeInputs = z11.record(z11.string(), z11.custom()).optional().default({});
|
|
5433
|
+
var HyperframeOutputs = z11.object({
|
|
5434
|
+
video: z11.custom()
|
|
5288
5435
|
}).strict();
|
|
5289
5436
|
var NODE_OWNED_PARAM_KEYS = /* @__PURE__ */ new Set(["composition", "format", "timeout_ms"]);
|
|
5290
5437
|
var MIME_BY_FORMAT = {
|
|
@@ -5573,23 +5720,23 @@ import { createRequire as createRequire3 } from "module";
|
|
|
5573
5720
|
import { tmpdir as tmpdir5 } from "os";
|
|
5574
5721
|
import path12 from "path";
|
|
5575
5722
|
import { promisify as promisify5 } from "util";
|
|
5576
|
-
import { z as
|
|
5723
|
+
import { z as z12 } from "zod";
|
|
5577
5724
|
var _execFileAsync = promisify5(execFile5);
|
|
5578
5725
|
var require_3 = createRequire3(import.meta.url);
|
|
5579
|
-
var WaitForSpec =
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5726
|
+
var WaitForSpec = z12.discriminatedUnion("kind", [
|
|
5727
|
+
z12.object({ kind: z12.literal("auto") }),
|
|
5728
|
+
z12.object({ kind: z12.literal("selector"), value: z12.string().min(1) }),
|
|
5729
|
+
z12.object({ kind: z12.literal("function"), value: z12.string().min(1) }),
|
|
5730
|
+
z12.object({ kind: z12.literal("timeout"), ms: z12.number().int().min(0).max(6e4) })
|
|
5584
5731
|
]);
|
|
5585
|
-
var HyperframeSnapshotParams =
|
|
5586
|
-
composition:
|
|
5732
|
+
var HyperframeSnapshotParams = z12.object({
|
|
5733
|
+
composition: z12.string().min(1),
|
|
5587
5734
|
wait_for: WaitForSpec.optional().default({ kind: "auto" }),
|
|
5588
|
-
timeout_ms:
|
|
5589
|
-
}).catchall(
|
|
5590
|
-
var HyperframeSnapshotInputs =
|
|
5591
|
-
var HyperframeSnapshotOutputs =
|
|
5592
|
-
image:
|
|
5735
|
+
timeout_ms: z12.number().int().positive().optional().default(6e4)
|
|
5736
|
+
}).catchall(z12.unknown());
|
|
5737
|
+
var HyperframeSnapshotInputs = z12.record(z12.string(), z12.custom()).optional().default({});
|
|
5738
|
+
var HyperframeSnapshotOutputs = z12.object({
|
|
5739
|
+
image: z12.custom()
|
|
5593
5740
|
}).strict();
|
|
5594
5741
|
var NODE_OWNED_PARAM_KEYS2 = /* @__PURE__ */ new Set(["composition", "wait_for", "timeout_ms"]);
|
|
5595
5742
|
var DEVICE_SCALE_FACTOR2 = 2;
|
|
@@ -5781,18 +5928,18 @@ async function waitForReady(page, waitFor, timeoutMs) {
|
|
|
5781
5928
|
// src/engine/nodes/local/imagemagick.ts
|
|
5782
5929
|
import { execFile as execFile6 } from "child_process";
|
|
5783
5930
|
import { promisify as promisify6 } from "util";
|
|
5784
|
-
import { z as
|
|
5931
|
+
import { z as z13 } from "zod";
|
|
5785
5932
|
var execFileAsync3 = promisify6(execFile6);
|
|
5786
|
-
var OutputDecl2 =
|
|
5787
|
-
kind:
|
|
5788
|
-
ext:
|
|
5933
|
+
var OutputDecl2 = z13.object({
|
|
5934
|
+
kind: z13.enum(["image", "video", "audio"]),
|
|
5935
|
+
ext: z13.string().min(1).max(8)
|
|
5789
5936
|
}).strict();
|
|
5790
|
-
var ImageMagickParams =
|
|
5791
|
-
args:
|
|
5792
|
-
outputs:
|
|
5937
|
+
var ImageMagickParams = z13.object({
|
|
5938
|
+
args: z13.array(z13.string()).min(1),
|
|
5939
|
+
outputs: z13.record(z13.string(), OutputDecl2).default({})
|
|
5793
5940
|
}).strict();
|
|
5794
|
-
var ImageMagickInputs =
|
|
5795
|
-
var ImageMagickOutputs =
|
|
5941
|
+
var ImageMagickInputs = z13.record(z13.string(), z13.unknown());
|
|
5942
|
+
var ImageMagickOutputs = z13.record(z13.string(), z13.custom());
|
|
5796
5943
|
var resolvedBin;
|
|
5797
5944
|
async function resolveBin() {
|
|
5798
5945
|
if (resolvedBin) return resolvedBin;
|
|
@@ -5834,29 +5981,29 @@ var imagemagickNode = defineNode({
|
|
|
5834
5981
|
});
|
|
5835
5982
|
|
|
5836
5983
|
// src/engine/nodes/local/text.ts
|
|
5837
|
-
import { z as
|
|
5984
|
+
import { z as z14 } from "zod";
|
|
5838
5985
|
var textNode = defineNode({
|
|
5839
5986
|
id: "text",
|
|
5840
5987
|
version: "1.0.0",
|
|
5841
5988
|
category: "data",
|
|
5842
5989
|
location: "local",
|
|
5843
5990
|
summary: "A literal text value. Use for prompts, descriptions, copy.",
|
|
5844
|
-
inputs:
|
|
5845
|
-
params:
|
|
5846
|
-
outputs:
|
|
5991
|
+
inputs: z14.object({}).strict(),
|
|
5992
|
+
params: z14.object({ value: z14.string() }).strict(),
|
|
5993
|
+
outputs: z14.object({ text: z14.string() }).strict(),
|
|
5847
5994
|
cost: () => ({ credits: 0, seconds_estimate: 0 }),
|
|
5848
5995
|
execute: ({ params }) => Promise.resolve({ text: params.value })
|
|
5849
5996
|
});
|
|
5850
5997
|
|
|
5851
5998
|
// src/engine/nodes/remote/audioVoiceConvert.ts
|
|
5852
|
-
import { z as
|
|
5853
|
-
var AudioVoiceConvertParams =
|
|
5854
|
-
model:
|
|
5999
|
+
import { z as z15 } from "zod";
|
|
6000
|
+
var AudioVoiceConvertParams = z15.object({
|
|
6001
|
+
model: z15.literal("elevenlabs/eleven_multilingual_sts_v2"),
|
|
5855
6002
|
/** Target voice id. Splice an upstream `voice_select` via `"{{voice_ref}}"`. */
|
|
5856
|
-
voice:
|
|
5857
|
-
output_format:
|
|
6003
|
+
voice: z15.string().min(1),
|
|
6004
|
+
output_format: z15.string().optional(),
|
|
5858
6005
|
/** Strip the source clip's background noise before re-voicing. */
|
|
5859
|
-
remove_background_noise:
|
|
6006
|
+
remove_background_noise: z15.boolean().optional()
|
|
5860
6007
|
}).strict();
|
|
5861
6008
|
var audioVoiceConvertNode = delegated({
|
|
5862
6009
|
id: "audio_voice_convert",
|
|
@@ -5864,44 +6011,44 @@ var audioVoiceConvertNode = delegated({
|
|
|
5864
6011
|
category: "audio",
|
|
5865
6012
|
summary: "Voice Changer / speech-to-speech via ElevenLabs (eleven_multilingual_sts_v2). Re-voices an existing audio clip in a TARGET voice while preserving timing/prosody.",
|
|
5866
6013
|
when_to_use: 'Use to normalize a generator-chosen voice (e.g. a Seedance talking-head clip\'s native audio) into ONE consistent brand voice across every scene \u2014 the cadence is preserved so any lip-sync stays valid. Wire `inputs.voice_ref: $ref:<voice_select>.voice_id` and set `params.voice: "{{voice_ref}}"`.',
|
|
5867
|
-
inputs:
|
|
6014
|
+
inputs: z15.object({
|
|
5868
6015
|
audio: AudioRef,
|
|
5869
6016
|
voice_ref: TextRef.optional()
|
|
5870
6017
|
}).strict(),
|
|
5871
6018
|
params: AudioVoiceConvertParams,
|
|
5872
|
-
outputs:
|
|
6019
|
+
outputs: z15.object({ audio: AudioRef }).strict(),
|
|
5873
6020
|
outputKinds: { audio: "audio" },
|
|
5874
6021
|
cost: () => ({ credits: 1, seconds_estimate: 20 })
|
|
5875
6022
|
});
|
|
5876
6023
|
|
|
5877
6024
|
// src/engine/nodes/remote/dialogue.ts
|
|
5878
|
-
import { z as
|
|
5879
|
-
var DialogueInput =
|
|
5880
|
-
text:
|
|
5881
|
-
voice_id:
|
|
6025
|
+
import { z as z16 } from "zod";
|
|
6026
|
+
var DialogueInput = z16.object({
|
|
6027
|
+
text: z16.string().min(1),
|
|
6028
|
+
voice_id: z16.string().min(1)
|
|
5882
6029
|
});
|
|
5883
6030
|
var DIALOGUE_MODELS = ["elevenlabs/eleven_v3"];
|
|
5884
|
-
var DialogueParams =
|
|
5885
|
-
model:
|
|
6031
|
+
var DialogueParams = z16.object({
|
|
6032
|
+
model: z16.enum(DIALOGUE_MODELS),
|
|
5886
6033
|
/**
|
|
5887
6034
|
* Ordered list of lines, each tagged with the voice that should speak it.
|
|
5888
6035
|
* Up to 10 unique voice_ids; total text across all lines should stay under
|
|
5889
6036
|
* ~2000 characters for best quality (ElevenLabs guidance).
|
|
5890
6037
|
*/
|
|
5891
|
-
inputs:
|
|
5892
|
-
language_code:
|
|
6038
|
+
inputs: z16.array(DialogueInput).min(1).max(50),
|
|
6039
|
+
language_code: z16.string().optional(),
|
|
5893
6040
|
/** ElevenLabs voice/model settings passthrough (e.g. `{ stability: 0.5 }`). */
|
|
5894
|
-
settings:
|
|
5895
|
-
seed:
|
|
5896
|
-
apply_text_normalization:
|
|
6041
|
+
settings: z16.record(z16.string(), z16.unknown()).optional(),
|
|
6042
|
+
seed: z16.number().int().min(0).max(4294967295).optional(),
|
|
6043
|
+
apply_text_normalization: z16.enum(["auto", "on", "off"]).optional(),
|
|
5897
6044
|
/**
|
|
5898
6045
|
* When true, hits `/v1/text-to-dialogue/with-timestamps` and emits a
|
|
5899
6046
|
* separate `timestamps` output — character-level alignment plus
|
|
5900
6047
|
* per-voice segment markers usable for captions, lipsync, or
|
|
5901
6048
|
* beat-matched cuts in ad creatives.
|
|
5902
6049
|
*/
|
|
5903
|
-
with_timestamps:
|
|
5904
|
-
output_format:
|
|
6050
|
+
with_timestamps: z16.boolean().optional(),
|
|
6051
|
+
output_format: z16.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
5905
6052
|
}).strict().refine((p) => p.inputs.reduce((sum, line) => sum + line.text.length, 0) <= ELEVENLABS_MAX_TEXT_CHARS, {
|
|
5906
6053
|
message: `total dialogue text exceeds ${ELEVENLABS_MAX_TEXT_CHARS} characters`,
|
|
5907
6054
|
path: ["inputs"]
|
|
@@ -5912,9 +6059,9 @@ var dialogueNode = delegated({
|
|
|
5912
6059
|
category: "audio",
|
|
5913
6060
|
summary: "Multi-voice dialogue / VO with ElevenLabs Eleven v3. Each line is tagged with a `voice_id`, so you can render two-character scripts (e.g. ad VO + customer testimonial reaction) in a single call. Setting `with_timestamps: true` adds character-level alignment for caption rendering and lipsync-friendly cuts.",
|
|
5914
6061
|
when_to_use: "Use for any ad creative or website video VO that needs more than narration \u2014 interviews, two-actor scripts, character ads, testimonial reads. For single-voice flat reads the existing `tts` node is cheaper and simpler; reach for `dialogue` when you need multiple speakers in one stitched track or word-level timing for downstream lipsync / captions.",
|
|
5915
|
-
inputs:
|
|
6062
|
+
inputs: z16.object({}).loose(),
|
|
5916
6063
|
params: DialogueParams,
|
|
5917
|
-
outputs:
|
|
6064
|
+
outputs: z16.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
5918
6065
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
5919
6066
|
cost: ({ params }) => {
|
|
5920
6067
|
const chars = params.inputs.reduce((sum, line) => sum + line.text.length, 0);
|
|
@@ -5923,7 +6070,7 @@ var dialogueNode = delegated({
|
|
|
5923
6070
|
});
|
|
5924
6071
|
|
|
5925
6072
|
// src/engine/nodes/remote/image.ts
|
|
5926
|
-
import { z as
|
|
6073
|
+
import { z as z17 } from "zod";
|
|
5927
6074
|
var IMAGE_GENERATE_MODELS2 = [
|
|
5928
6075
|
"openai/gpt-5.4-image-2",
|
|
5929
6076
|
"google/gemini-3.5-flash",
|
|
@@ -5931,16 +6078,16 @@ var IMAGE_GENERATE_MODELS2 = [
|
|
|
5931
6078
|
"google/gemini-3-pro-image-preview",
|
|
5932
6079
|
"recraft/recraft-v4.1-pro-vector"
|
|
5933
6080
|
];
|
|
5934
|
-
var ImageGenerateParams =
|
|
5935
|
-
model:
|
|
5936
|
-
prompt:
|
|
5937
|
-
aspect_ratio:
|
|
5938
|
-
image_size:
|
|
6081
|
+
var ImageGenerateParams = z17.object({
|
|
6082
|
+
model: z17.enum(IMAGE_GENERATE_MODELS2),
|
|
6083
|
+
prompt: z17.string().min(1),
|
|
6084
|
+
aspect_ratio: z17.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9", "1:4", "4:1", "1:8", "8:1"]).optional(),
|
|
6085
|
+
image_size: z17.enum(["0.5K", "1K", "2K", "4K"]).optional(),
|
|
5939
6086
|
// Recraft v4 vector controls — forwarded into `image_config`. Registry
|
|
5940
6087
|
// rejects them on non-Recraft models.
|
|
5941
|
-
strength:
|
|
5942
|
-
rgb_colors:
|
|
5943
|
-
background_rgb_color:
|
|
6088
|
+
strength: z17.number().min(0).max(1).optional(),
|
|
6089
|
+
rgb_colors: z17.array(z17.array(z17.number().int().min(0).max(255))).optional(),
|
|
6090
|
+
background_rgb_color: z17.array(z17.number().int().min(0).max(255)).optional()
|
|
5944
6091
|
}).strict();
|
|
5945
6092
|
var imageGenerateNode = delegated({
|
|
5946
6093
|
id: "image_generate",
|
|
@@ -5950,22 +6097,22 @@ var imageGenerateNode = delegated({
|
|
|
5950
6097
|
when_to_use: "Use for hero shots, product photography, illustrations, and vector logos. `recraft/recraft-v4.1-pro-vector` for crisp vector / logo work; `openai/gpt-5.4-image-2` for photorealistic; Gemini variants for fast iteration and editing via the `reference` input. `reference` accepts ONE image or an ARRAY of images \u2014 wire several to combine references in a single generation (e.g. a subject sheet + a font specimen + the original ad). Every reference is forwarded to the model in array order.",
|
|
5951
6098
|
// `reference` is one image or an ordered array of images. The backend forwards
|
|
5952
6099
|
// each as a separate `image_url` to the provider (OpenRouter accepts many).
|
|
5953
|
-
inputs:
|
|
6100
|
+
inputs: z17.object({ reference: z17.union([ImageRef, z17.array(ImageRef).min(1)]).optional() }).loose(),
|
|
5954
6101
|
params: ImageGenerateParams,
|
|
5955
|
-
outputs:
|
|
6102
|
+
outputs: z17.object({ images: z17.array(ImageRef).min(1) }).strict(),
|
|
5956
6103
|
outputKinds: { images: "image" },
|
|
5957
6104
|
cost: () => ({ credits: 5, seconds_estimate: 10 })
|
|
5958
6105
|
});
|
|
5959
6106
|
|
|
5960
6107
|
// src/engine/nodes/remote/imageAspectAdapt.ts
|
|
5961
|
-
import { z as
|
|
6108
|
+
import { z as z18 } from "zod";
|
|
5962
6109
|
var ASPECT_ADAPT_MODELS = ["google/gemini-3-pro-image-preview", "google/gemini-3.1-flash-image-preview"];
|
|
5963
6110
|
var ASPECT_ADAPT_FORMATS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
|
|
5964
|
-
var ImageAspectAdaptParams =
|
|
5965
|
-
model:
|
|
5966
|
-
formats:
|
|
5967
|
-
guidance:
|
|
5968
|
-
image_size:
|
|
6111
|
+
var ImageAspectAdaptParams = z18.object({
|
|
6112
|
+
model: z18.enum(ASPECT_ADAPT_MODELS),
|
|
6113
|
+
formats: z18.array(z18.enum(ASPECT_ADAPT_FORMATS)).min(1).max(6).refine((formats) => new Set(formats).size === formats.length, { message: "formats must be unique" }),
|
|
6114
|
+
guidance: z18.string().min(1).optional(),
|
|
6115
|
+
image_size: z18.enum(["0.5K", "1K", "2K", "4K"]).optional()
|
|
5969
6116
|
}).strict();
|
|
5970
6117
|
var imageAspectAdaptNode = delegated({
|
|
5971
6118
|
id: "image_aspect_adapt",
|
|
@@ -5973,9 +6120,9 @@ var imageAspectAdaptNode = delegated({
|
|
|
5973
6120
|
category: "image",
|
|
5974
6121
|
summary: "Adapt ONE creative into multiple aspect ratios (Meta: 9:16 stories, 1:1 feed, 4:5, 16:9\u2026) in a single step. AI recomposes the layout per format \u2014 identical subject, text, logos, colors, and style; the scene is extended/restructured, never stretched or cropped. Formats that already match the source ratio pass through unchanged at zero cost. Outputs are ordered exactly as `formats`.",
|
|
5975
6122
|
when_to_use: "Use after a hero creative exists (image_generate, ingest, image_search) to fan it out to every placement format \u2014 wire the creative into `source` and list the target ratios in `formats`. Cost is estimated per format; formats matching the source ratio are free pass-throughs. Pick `google/gemini-3.1-flash-image-preview` (Nano Banana flash) while iterating, `google/gemini-3-pro-image-preview` (Nano Banana Pro) for final-quality adaptation.",
|
|
5976
|
-
inputs:
|
|
6123
|
+
inputs: z18.object({ source: ImageRef }).loose(),
|
|
5977
6124
|
params: ImageAspectAdaptParams,
|
|
5978
|
-
outputs:
|
|
6125
|
+
outputs: z18.object({ images: z18.array(ImageRef).min(1) }).strict(),
|
|
5979
6126
|
outputKinds: { images: "image" },
|
|
5980
6127
|
cost: ({ params }) => {
|
|
5981
6128
|
const p = params;
|
|
@@ -5988,12 +6135,12 @@ var imageAspectAdaptNode = delegated({
|
|
|
5988
6135
|
});
|
|
5989
6136
|
|
|
5990
6137
|
// src/engine/nodes/remote/imageBackgroundRemove.ts
|
|
5991
|
-
import { z as
|
|
5992
|
-
var ImageBackgroundRemoveParams =
|
|
5993
|
-
model:
|
|
5994
|
-
model_variant:
|
|
5995
|
-
operating_resolution:
|
|
5996
|
-
mask_only:
|
|
6138
|
+
import { z as z19 } from "zod";
|
|
6139
|
+
var ImageBackgroundRemoveParams = z19.object({
|
|
6140
|
+
model: z19.literal("fal/birefnet-v2").optional().default("fal/birefnet-v2"),
|
|
6141
|
+
model_variant: z19.enum(["General Use (Light)", "General Use (Heavy)", "Matting", "Portrait", "DIS", "HRSOD", "COD"]).optional().default("General Use (Light)"),
|
|
6142
|
+
operating_resolution: z19.enum(["1024x1024", "2048x2048", "2304x2304"]).optional(),
|
|
6143
|
+
mask_only: z19.boolean().optional().default(false)
|
|
5997
6144
|
}).strict();
|
|
5998
6145
|
var imageBackgroundRemoveNode = delegated({
|
|
5999
6146
|
id: "image_background_remove",
|
|
@@ -6001,11 +6148,11 @@ var imageBackgroundRemoveNode = delegated({
|
|
|
6001
6148
|
category: "image",
|
|
6002
6149
|
summary: "Remove the background from an image and return a transparent PNG (or the segmentation mask). Powered by fal.ai `fal-ai/birefnet/v2`.",
|
|
6003
6150
|
when_to_use: "Use to extract subjects from photos for use as overlays in hyperframe compositions, product shots, or compositing pipelines. Set `mask_only:true` to return the binary mask instead of the alpha-cut image.",
|
|
6004
|
-
inputs:
|
|
6151
|
+
inputs: z19.object({
|
|
6005
6152
|
image: ImageRef
|
|
6006
6153
|
}).strict(),
|
|
6007
6154
|
params: ImageBackgroundRemoveParams,
|
|
6008
|
-
outputs:
|
|
6155
|
+
outputs: z19.object({
|
|
6009
6156
|
image: ImageRef,
|
|
6010
6157
|
mask: ImageRef.optional()
|
|
6011
6158
|
}).strict(),
|
|
@@ -6014,7 +6161,7 @@ var imageBackgroundRemoveNode = delegated({
|
|
|
6014
6161
|
});
|
|
6015
6162
|
|
|
6016
6163
|
// src/engine/nodes/remote/imageDescribe.ts
|
|
6017
|
-
import { z as
|
|
6164
|
+
import { z as z20 } from "zod";
|
|
6018
6165
|
var IMAGE_DESCRIBE_MODELS = ["~google/gemini-pro-latest", "~google/gemini-flash-latest"];
|
|
6019
6166
|
var imageDescribeNode = delegated({
|
|
6020
6167
|
id: "image_describe",
|
|
@@ -6022,33 +6169,33 @@ var imageDescribeNode = delegated({
|
|
|
6022
6169
|
category: "vision",
|
|
6023
6170
|
summary: "Reverse-engineer an image into an exhaustive, replication-grade JSON description: who the advertiser is and what they sell (source_context), composition, non-person subjects with expression/treatment, deeply detailed people, brand-identified logos (named by brand, not appearance), camera optics, lighting, color palette WITH per-color brand-ownership (brand vs borrowed-functional) and purpose, materials, visible text, ad signals (proof badges/CTA/price), the persuasion engine (ad_intent), style, post-processing.",
|
|
6024
6171
|
when_to_use: 'Use to turn a reference image into a structured blueprint you can inject into downstream prompts via `{{slot}}` \u2014 e.g. restyle a competitor ad onto your own product, lock a look across a series, or feed exact palette/lighting into image_generate. Purpose-built for market adaptation: logos are identified by brand ("Trustpilot", never "green star"), people and animals carry expression/emotion/intent detail, and each color is tagged brand vs borrowed-functional so a recolor can keep the reds/yellows that do a job. The extraction prompt is baked in; use `focus` to emphasise aspects and `context` to pass known provenance (advertiser, category, market) so source_context and color ownership are grounded. Pick `~google/gemini-pro-latest` for the densest extraction (recommended for ad / market-adaptation passes), `~google/gemini-flash-latest` for cheap/fast passes. The output is rich \u2014 raise `max_tokens` (e.g. 8000+) for dense ads so the JSON isn\'t truncated.',
|
|
6025
|
-
inputs:
|
|
6026
|
-
params:
|
|
6027
|
-
model:
|
|
6028
|
-
focus:
|
|
6029
|
-
context:
|
|
6030
|
-
temperature:
|
|
6031
|
-
max_tokens:
|
|
6172
|
+
inputs: z20.object({ image: ImageRef }).loose(),
|
|
6173
|
+
params: z20.object({
|
|
6174
|
+
model: z20.enum(IMAGE_DESCRIBE_MODELS),
|
|
6175
|
+
focus: z20.string().optional(),
|
|
6176
|
+
context: z20.string().optional(),
|
|
6177
|
+
temperature: z20.number().min(0).max(2).optional(),
|
|
6178
|
+
max_tokens: z20.number().int().positive().optional()
|
|
6032
6179
|
}).strict(),
|
|
6033
|
-
outputs:
|
|
6180
|
+
outputs: z20.object({ description: JsonRef }).strict(),
|
|
6034
6181
|
outputKinds: { description: "json" },
|
|
6035
6182
|
cost: () => ({ credits: 2, seconds_estimate: 10 })
|
|
6036
6183
|
});
|
|
6037
6184
|
|
|
6038
6185
|
// src/engine/nodes/remote/imageReferenceSheet.ts
|
|
6039
|
-
import { z as
|
|
6186
|
+
import { z as z21 } from "zod";
|
|
6040
6187
|
var REFERENCE_SHEET_MODELS = ["google/gemini-3-pro-image-preview", "google/gemini-3.1-flash-image-preview"];
|
|
6041
|
-
var ImageReferenceSheetParams =
|
|
6042
|
-
model:
|
|
6043
|
-
subject_description:
|
|
6188
|
+
var ImageReferenceSheetParams = z21.object({
|
|
6189
|
+
model: z21.enum(REFERENCE_SHEET_MODELS),
|
|
6190
|
+
subject_description: z21.string().min(1),
|
|
6044
6191
|
// `location` = a set/room shown from several camera ANGLES (not a rotated subject),
|
|
6045
6192
|
// so a multi-scene shoot keeps one consistent set.
|
|
6046
|
-
subject_type:
|
|
6047
|
-
views:
|
|
6048
|
-
style:
|
|
6049
|
-
prompt_override:
|
|
6050
|
-
aspect_ratio:
|
|
6051
|
-
image_size:
|
|
6193
|
+
subject_type: z21.enum(["character", "person", "product", "location"]),
|
|
6194
|
+
views: z21.array(z21.string().min(1)).min(2).max(8).optional(),
|
|
6195
|
+
style: z21.string().optional(),
|
|
6196
|
+
prompt_override: z21.string().min(1).optional(),
|
|
6197
|
+
aspect_ratio: z21.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9", "1:4", "4:1", "1:8", "8:1"]).optional(),
|
|
6198
|
+
image_size: z21.enum(["0.5K", "1K", "2K", "4K"]).optional()
|
|
6052
6199
|
}).strict();
|
|
6053
6200
|
var imageReferenceSheetNode = delegated({
|
|
6054
6201
|
id: "image_reference_sheet",
|
|
@@ -6056,9 +6203,9 @@ var imageReferenceSheetNode = delegated({
|
|
|
6056
6203
|
category: "image",
|
|
6057
6204
|
summary: "Fuse 1\u20136 images of a single subject (person, character, product, or location/set) into ONE multi-view reference sheet \u2014 a labeled grid in consistent style and lighting: a turnaround (FRONT / SIDE / BACK\u2026) for a person/character/product, or several camera angles of the same room (WIDE / REVERSE / DETAIL\u2026) for a location. Curated models: Gemini 3 Pro Image (best fusion + labels), Gemini 3.1 Flash Image (cheap iteration).",
|
|
6058
6205
|
when_to_use: "Use before image_generate / video_generate when a subject must stay consistent across many creatives \u2014 wire the `sheet` output into their `reference` input instead of re-describing the subject per prompt. `subject_description` should be the exact wording you reuse downstream. Pick `google/gemini-3-pro-image-preview` for final 6-view sheets at 2K+, `google/gemini-3.1-flash-image-preview` while iterating.",
|
|
6059
|
-
inputs:
|
|
6206
|
+
inputs: z21.object({ references: z21.array(ImageRef).min(1).max(6) }).loose(),
|
|
6060
6207
|
params: ImageReferenceSheetParams,
|
|
6061
|
-
outputs:
|
|
6208
|
+
outputs: z21.object({ sheet: ImageRef }).strict(),
|
|
6062
6209
|
outputKinds: { sheet: "image" },
|
|
6063
6210
|
cost: ({ params }) => ({
|
|
6064
6211
|
credits: params?.model === "google/gemini-3-pro-image-preview" ? 20 : 5,
|
|
@@ -6067,10 +6214,10 @@ var imageReferenceSheetNode = delegated({
|
|
|
6067
6214
|
});
|
|
6068
6215
|
|
|
6069
6216
|
// src/engine/nodes/remote/imageSearch.ts
|
|
6070
|
-
import { z as
|
|
6071
|
-
var ImageSearchParams =
|
|
6072
|
-
prompt:
|
|
6073
|
-
count:
|
|
6217
|
+
import { z as z22 } from "zod";
|
|
6218
|
+
var ImageSearchParams = z22.object({
|
|
6219
|
+
prompt: z22.string().min(1),
|
|
6220
|
+
count: z22.number().int().min(1).max(20).default(5)
|
|
6074
6221
|
}).strict();
|
|
6075
6222
|
var imageSearchNode = delegated({
|
|
6076
6223
|
id: "image_search",
|
|
@@ -6078,15 +6225,15 @@ var imageSearchNode = delegated({
|
|
|
6078
6225
|
category: "image",
|
|
6079
6226
|
summary: "Agentic image search across Google Images, stock photography (Freepik), and Pinterest. An LLM agent picks the search tools and queries, selects the best matches, and the results are downloaded into canvas assets.",
|
|
6080
6227
|
when_to_use: "Use to gather real-world reference or inspiration images for a prompt (e.g. several photos of an australian shepherd) so a later step or the user can pick the best one. Not for creating new imagery \u2014 use image_generate for that.",
|
|
6081
|
-
inputs:
|
|
6228
|
+
inputs: z22.object({}).loose(),
|
|
6082
6229
|
params: ImageSearchParams,
|
|
6083
|
-
outputs:
|
|
6230
|
+
outputs: z22.object({ images: z22.array(ImageRef).min(1) }).strict(),
|
|
6084
6231
|
outputKinds: { images: "image" },
|
|
6085
6232
|
cost: ({ params }) => ({ credits: Math.ceil(2 + params.count / 2), seconds_estimate: 30 })
|
|
6086
6233
|
});
|
|
6087
6234
|
|
|
6088
6235
|
// src/engine/nodes/remote/imageSelect.ts
|
|
6089
|
-
import { z as
|
|
6236
|
+
import { z as z23 } from "zod";
|
|
6090
6237
|
var IMAGE_SELECT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6091
6238
|
var imageSelectNode = delegated({
|
|
6092
6239
|
id: "image_select",
|
|
@@ -6094,15 +6241,15 @@ var imageSelectNode = delegated({
|
|
|
6094
6241
|
category: "vision",
|
|
6095
6242
|
summary: "Pick the best `count` images out of 2+ candidates with a vision LLM, judged against a prompt. Outputs a passthrough subset of the input refs (no new pixels) plus the model's comparative reasoning.",
|
|
6096
6243
|
when_to_use: "Use after fanning out several image_generate variants (or any pool of 2+ images) to keep only the strongest before expensive downstream steps \u2014 video generation, reference sheets, final delivery. `count` fixes the output size, so `images#0`\u2026`images#count-1` are always safe to wire. Pick `~google/gemini-flash-latest` for cheap/fast picks and `~google/gemini-pro-latest` for harder aesthetic judgement.",
|
|
6097
|
-
inputs:
|
|
6098
|
-
params:
|
|
6099
|
-
model:
|
|
6100
|
-
prompt:
|
|
6101
|
-
count:
|
|
6102
|
-
temperature:
|
|
6103
|
-
max_tokens:
|
|
6244
|
+
inputs: z23.object({ images: z23.array(ImageRef).min(2) }).loose(),
|
|
6245
|
+
params: z23.object({
|
|
6246
|
+
model: z23.enum(IMAGE_SELECT_MODELS),
|
|
6247
|
+
prompt: z23.string().min(1),
|
|
6248
|
+
count: z23.number().int().min(1).default(1),
|
|
6249
|
+
temperature: z23.number().min(0).max(2).optional(),
|
|
6250
|
+
max_tokens: z23.number().int().positive().optional()
|
|
6104
6251
|
}).strict(),
|
|
6105
|
-
outputs:
|
|
6252
|
+
outputs: z23.object({ images: z23.array(ImageRef).min(1), reasoning: TextRef }).strict(),
|
|
6106
6253
|
outputKinds: { images: "image", reasoning: "text" },
|
|
6107
6254
|
cost: () => ({ credits: 1, seconds_estimate: 5 }),
|
|
6108
6255
|
// Arity is only knowable at validate time when `images` is a literal array
|
|
@@ -6127,34 +6274,34 @@ var imageSelectNode = delegated({
|
|
|
6127
6274
|
});
|
|
6128
6275
|
|
|
6129
6276
|
// src/engine/nodes/remote/music.ts
|
|
6130
|
-
import { z as
|
|
6277
|
+
import { z as z24 } from "zod";
|
|
6131
6278
|
var MUSIC_MODELS = ["elevenlabs/music-v1", "elevenlabs/video-background-music-v1"];
|
|
6132
|
-
var MusicParams =
|
|
6133
|
-
model:
|
|
6279
|
+
var MusicParams = z24.object({
|
|
6280
|
+
model: z24.enum(MUSIC_MODELS),
|
|
6134
6281
|
/** Free-form prompt. Used by `elevenlabs/music-v1` (compose-detailed). */
|
|
6135
|
-
prompt:
|
|
6282
|
+
prompt: z24.string().optional(),
|
|
6136
6283
|
/**
|
|
6137
6284
|
* Structured composition plan (intro / hook / verse / outro sections with
|
|
6138
6285
|
* per-section styles + durations). Mutually exclusive with `prompt`.
|
|
6139
6286
|
*/
|
|
6140
|
-
composition_plan:
|
|
6287
|
+
composition_plan: z24.record(z24.string(), z24.unknown()).optional(),
|
|
6141
6288
|
/** Target length when using `prompt`. 3000–454545ms (capped by the $10 per-node cost limit). */
|
|
6142
|
-
music_length_ms:
|
|
6143
|
-
seed:
|
|
6289
|
+
music_length_ms: z24.number().int().min(3e3).max(ELEVENLABS_MAX_MUSIC_LENGTH_MS).optional(),
|
|
6290
|
+
seed: z24.number().int().optional(),
|
|
6144
6291
|
/** Prompt mode only — forces an instrumental (no vocals) track. */
|
|
6145
|
-
force_instrumental:
|
|
6292
|
+
force_instrumental: z24.boolean().optional(),
|
|
6146
6293
|
/** composition_plan only — honor exact section durations. */
|
|
6147
|
-
respect_sections_durations:
|
|
6294
|
+
respect_sections_durations: z24.boolean().optional(),
|
|
6148
6295
|
/** Emit word-level timestamps alongside the audio. */
|
|
6149
|
-
with_timestamps:
|
|
6296
|
+
with_timestamps: z24.boolean().optional(),
|
|
6150
6297
|
/**
|
|
6151
6298
|
* video-to-music only — short description of the desired score
|
|
6152
6299
|
* ("upbeat synth, fast cuts, 80s") used to bias the model.
|
|
6153
6300
|
*/
|
|
6154
|
-
description:
|
|
6301
|
+
description: z24.string().max(1e3).optional(),
|
|
6155
6302
|
/** video-to-music only — up to 10 style tags. */
|
|
6156
|
-
tags:
|
|
6157
|
-
output_format:
|
|
6303
|
+
tags: z24.array(z24.string()).max(10).optional(),
|
|
6304
|
+
output_format: z24.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
6158
6305
|
}).strict();
|
|
6159
6306
|
var musicNode = delegated({
|
|
6160
6307
|
id: "music",
|
|
@@ -6162,9 +6309,9 @@ var musicNode = delegated({
|
|
|
6162
6309
|
category: "audio",
|
|
6163
6310
|
summary: "Generate music for ad creatives and website video content. `elevenlabs/music-v1` composes from a text prompt or structured composition plan; `elevenlabs/video-background-music-v1` scores an existing video clip provided via `inputs.video`.",
|
|
6164
6311
|
when_to_use: "Use to produce background music or a full score for video ads, hero-section reels, or any motion content. Prefer the video-to-music model when you already have a cut and want music timed to it; use compose-detailed when you have only a brief or want section-level control (intro / hook / outro). Pair the resulting audio with `video_generate` or `video_lipsync` at compose time.",
|
|
6165
|
-
inputs:
|
|
6312
|
+
inputs: z24.object({ video: VideoRef.optional() }).loose(),
|
|
6166
6313
|
params: MusicParams,
|
|
6167
|
-
outputs:
|
|
6314
|
+
outputs: z24.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
6168
6315
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
6169
6316
|
cost: ({ params }) => {
|
|
6170
6317
|
const seconds = params.music_length_ms ? Math.ceil(params.music_length_ms / 1e3) : 30;
|
|
@@ -6195,25 +6342,25 @@ var musicNode = delegated({
|
|
|
6195
6342
|
});
|
|
6196
6343
|
|
|
6197
6344
|
// src/engine/nodes/remote/soundEffect.ts
|
|
6198
|
-
import { z as
|
|
6345
|
+
import { z as z25 } from "zod";
|
|
6199
6346
|
var SOUND_EFFECT_MODELS = ["elevenlabs/eleven_text_to_sound_v2"];
|
|
6200
|
-
var SoundEffectParams =
|
|
6201
|
-
model:
|
|
6347
|
+
var SoundEffectParams = z25.object({
|
|
6348
|
+
model: z25.enum(SOUND_EFFECT_MODELS),
|
|
6202
6349
|
/** Prompt describing the SFX ("metal door slam", "soft UI tap", "ocean waves"). */
|
|
6203
|
-
text:
|
|
6350
|
+
text: z25.string().min(1),
|
|
6204
6351
|
/**
|
|
6205
6352
|
* Target length in seconds. 0.5–30. Leave unset to let the model pick the
|
|
6206
6353
|
* natural length for the described effect.
|
|
6207
6354
|
*/
|
|
6208
|
-
duration_seconds:
|
|
6355
|
+
duration_seconds: z25.number().min(0.5).max(30).optional(),
|
|
6209
6356
|
/**
|
|
6210
6357
|
* 0–1. Higher = stick closer to the prompt at the cost of variety; lower
|
|
6211
6358
|
* = let the model interpret more freely. Defaults to 0.3 on the provider.
|
|
6212
6359
|
*/
|
|
6213
|
-
prompt_influence:
|
|
6360
|
+
prompt_influence: z25.number().min(0).max(1).optional(),
|
|
6214
6361
|
/** Only valid on `eleven_text_to_sound_v2` — produce a seamless loop. */
|
|
6215
|
-
loop:
|
|
6216
|
-
output_format:
|
|
6362
|
+
loop: z25.boolean().optional(),
|
|
6363
|
+
output_format: z25.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
6217
6364
|
}).strict();
|
|
6218
6365
|
var soundEffectNode = delegated({
|
|
6219
6366
|
id: "sound_effect",
|
|
@@ -6221,9 +6368,9 @@ var soundEffectNode = delegated({
|
|
|
6221
6368
|
category: "audio",
|
|
6222
6369
|
summary: "Generate short sound effects from a text prompt via ElevenLabs Text-to-Sound. Use for whooshes, impacts, UI clicks, ambient beds, or signature stingers in ad creatives and product videos.",
|
|
6223
6370
|
when_to_use: "Reach for this when you need a punch-in SFX layered against `video_generate` or `hyperframe_render` output \u2014 e.g. a logo whoosh on a hero shot, a click on a CTA cut, a swelling ambient bed under VO. Set `loop: true` for atmospheric beds that need to tile under longer footage; leave `duration_seconds` unset and the model picks a natural length.",
|
|
6224
|
-
inputs:
|
|
6371
|
+
inputs: z25.object({}).loose(),
|
|
6225
6372
|
params: SoundEffectParams,
|
|
6226
|
-
outputs:
|
|
6373
|
+
outputs: z25.object({ audio: AudioRef }).strict(),
|
|
6227
6374
|
outputKinds: { audio: "audio" },
|
|
6228
6375
|
cost: ({ params }) => {
|
|
6229
6376
|
const seconds = params.duration_seconds ?? 5;
|
|
@@ -6232,7 +6379,7 @@ var soundEffectNode = delegated({
|
|
|
6232
6379
|
});
|
|
6233
6380
|
|
|
6234
6381
|
// src/engine/nodes/remote/textGenerate.ts
|
|
6235
|
-
import { z as
|
|
6382
|
+
import { z as z26 } from "zod";
|
|
6236
6383
|
var TEXT_GENERATE_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6237
6384
|
var textGenerateNode = delegated({
|
|
6238
6385
|
id: "text_generate",
|
|
@@ -6240,58 +6387,58 @@ var textGenerateNode = delegated({
|
|
|
6240
6387
|
category: "language",
|
|
6241
6388
|
summary: "Single-turn LLM text generation via OpenRouter. Returns a text response.",
|
|
6242
6389
|
when_to_use: 'Use for any short text generation step in a canvas \u2014 ad copy, hooks, headlines, JSON outputs for downstream nodes. Pick `~google/gemini-flash-latest` for cheap/fast work and `~google/gemini-pro-latest` for harder reasoning. When the output must be JSON for a downstream `{{slot}}` (e.g. the ad-blueprint transform), set `response_format: "json_object"` so the model returns clean JSON with no markdown fences or prose. Set `web_search: true` to let the model search the live web first (OpenRouter `:online`) \u2014 useful when the transform must adapt copy to the target brand\'s real facts (current pricing, the trust signals it actually has) rather than guess.',
|
|
6243
|
-
inputs:
|
|
6244
|
-
params:
|
|
6245
|
-
model:
|
|
6246
|
-
prompt:
|
|
6247
|
-
system:
|
|
6248
|
-
response_format:
|
|
6249
|
-
web_search:
|
|
6250
|
-
temperature:
|
|
6251
|
-
max_tokens:
|
|
6390
|
+
inputs: z26.object({}).loose(),
|
|
6391
|
+
params: z26.object({
|
|
6392
|
+
model: z26.enum(TEXT_GENERATE_MODELS),
|
|
6393
|
+
prompt: z26.string().min(1),
|
|
6394
|
+
system: z26.string().optional(),
|
|
6395
|
+
response_format: z26.enum(["text", "json_object"]).optional(),
|
|
6396
|
+
web_search: z26.boolean().optional(),
|
|
6397
|
+
temperature: z26.number().min(0).max(2).optional(),
|
|
6398
|
+
max_tokens: z26.number().int().positive().optional()
|
|
6252
6399
|
}).strict(),
|
|
6253
|
-
outputs:
|
|
6400
|
+
outputs: z26.object({ text: TextRef }).strict(),
|
|
6254
6401
|
outputKinds: { text: "text" },
|
|
6255
6402
|
cost: () => ({ credits: 1, seconds_estimate: 3 })
|
|
6256
6403
|
});
|
|
6257
6404
|
|
|
6258
6405
|
// src/engine/nodes/remote/tts.ts
|
|
6259
|
-
import { z as
|
|
6406
|
+
import { z as z27 } from "zod";
|
|
6260
6407
|
var TTS_MODELS = ["elevenlabs/eleven_v3"];
|
|
6261
|
-
var TtsVoiceSettings =
|
|
6262
|
-
stability:
|
|
6263
|
-
similarity_boost:
|
|
6264
|
-
style:
|
|
6265
|
-
use_speaker_boost:
|
|
6266
|
-
speed:
|
|
6408
|
+
var TtsVoiceSettings = z27.object({
|
|
6409
|
+
stability: z27.number().min(0).max(1).optional(),
|
|
6410
|
+
similarity_boost: z27.number().min(0).max(1).optional(),
|
|
6411
|
+
style: z27.number().min(0).max(1).optional(),
|
|
6412
|
+
use_speaker_boost: z27.boolean().optional(),
|
|
6413
|
+
speed: z27.number().min(0.25).max(4).optional()
|
|
6267
6414
|
}).strict();
|
|
6268
|
-
var TtsPronunciationLocator =
|
|
6269
|
-
pronunciation_dictionary_id:
|
|
6270
|
-
version_id:
|
|
6415
|
+
var TtsPronunciationLocator = z27.object({
|
|
6416
|
+
pronunciation_dictionary_id: z27.string().min(1),
|
|
6417
|
+
version_id: z27.string().nullable().optional()
|
|
6271
6418
|
}).strict();
|
|
6272
|
-
var TtsParams =
|
|
6273
|
-
model:
|
|
6274
|
-
text:
|
|
6275
|
-
voice:
|
|
6419
|
+
var TtsParams = z27.object({
|
|
6420
|
+
model: z27.enum(TTS_MODELS),
|
|
6421
|
+
text: z27.string().min(1).max(ELEVENLABS_MAX_TEXT_CHARS),
|
|
6422
|
+
voice: z27.string().min(1),
|
|
6276
6423
|
/** Provider output_format (mp3 family only — assets are stored as audio/mpeg). */
|
|
6277
|
-
output_format:
|
|
6278
|
-
seed:
|
|
6424
|
+
output_format: z27.enum(ELEVENLABS_OUTPUT_FORMATS).optional(),
|
|
6425
|
+
seed: z27.number().int().min(0).max(4294967295).optional(),
|
|
6279
6426
|
// Top-level shortcuts; structured form is `voice_settings`.
|
|
6280
|
-
stability:
|
|
6281
|
-
similarity_boost:
|
|
6427
|
+
stability: z27.number().min(0).max(1).optional(),
|
|
6428
|
+
similarity_boost: z27.number().min(0).max(1).optional(),
|
|
6282
6429
|
voice_settings: TtsVoiceSettings.optional(),
|
|
6283
6430
|
/** ISO 639-1 language code. eleven_v3 supports language hints. */
|
|
6284
|
-
language_code:
|
|
6285
|
-
pronunciation_dictionary_locators:
|
|
6286
|
-
apply_text_normalization:
|
|
6431
|
+
language_code: z27.string().optional(),
|
|
6432
|
+
pronunciation_dictionary_locators: z27.array(TtsPronunciationLocator).max(3).optional(),
|
|
6433
|
+
apply_text_normalization: z27.enum(["auto", "on", "off"]).optional(),
|
|
6287
6434
|
/** Currently Japanese-only. Adds latency. */
|
|
6288
|
-
apply_language_text_normalization:
|
|
6435
|
+
apply_language_text_normalization: z27.boolean().optional(),
|
|
6289
6436
|
/**
|
|
6290
6437
|
* When true, hits `/v1/text-to-speech/{voice_id}/with-timestamps` and
|
|
6291
6438
|
* adds a `timestamps` output (character-level alignment) for caption
|
|
6292
6439
|
* rendering, lipsync, and beat-matched cuts.
|
|
6293
6440
|
*/
|
|
6294
|
-
with_timestamps:
|
|
6441
|
+
with_timestamps: z27.boolean().optional()
|
|
6295
6442
|
}).strict();
|
|
6296
6443
|
var ttsNode = delegated({
|
|
6297
6444
|
id: "tts",
|
|
@@ -6299,9 +6446,9 @@ var ttsNode = delegated({
|
|
|
6299
6446
|
category: "audio",
|
|
6300
6447
|
summary: "Single-voice text-to-speech via ElevenLabs Eleven v3. Optional character-level timestamps for caption rendering and beat-matched cuts.",
|
|
6301
6448
|
when_to_use: "Use for single-speaker VO \u2014 ad reads, hero-section narration, product walkthroughs. Reach for `dialogue` when you need multiple voices in one stitched track. Set `with_timestamps: true` when downstream needs character-level alignment (captions, lipsync).",
|
|
6302
|
-
inputs:
|
|
6449
|
+
inputs: z27.object({}).loose(),
|
|
6303
6450
|
params: TtsParams,
|
|
6304
|
-
outputs:
|
|
6451
|
+
outputs: z27.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
6305
6452
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
6306
6453
|
cost: ({ params }) => ({
|
|
6307
6454
|
credits: Math.max(1, Math.ceil(params.text.length * 15e-4)),
|
|
@@ -6310,23 +6457,23 @@ var ttsNode = delegated({
|
|
|
6310
6457
|
});
|
|
6311
6458
|
|
|
6312
6459
|
// src/engine/nodes/remote/video.ts
|
|
6313
|
-
import { z as
|
|
6460
|
+
import { z as z28 } from "zod";
|
|
6314
6461
|
var VIDEO_GENERATE_MODELS = ["bytedance/seedance-2.0", "google/veo-3.1-fast"];
|
|
6315
|
-
var VideoGenerateParams =
|
|
6316
|
-
model:
|
|
6317
|
-
prompt:
|
|
6318
|
-
duration:
|
|
6319
|
-
resolution:
|
|
6462
|
+
var VideoGenerateParams = z28.object({
|
|
6463
|
+
model: z28.enum(VIDEO_GENERATE_MODELS),
|
|
6464
|
+
prompt: z28.string().min(1),
|
|
6465
|
+
duration: z28.number().int().positive().optional(),
|
|
6466
|
+
resolution: z28.string().optional(),
|
|
6320
6467
|
// Union of ratios accepted by at least one curated model (registry gates
|
|
6321
6468
|
// per-model). 3:2/2:3 are deliberately absent: no registered model takes them.
|
|
6322
|
-
aspect_ratio:
|
|
6323
|
-
generate_audio:
|
|
6324
|
-
seed:
|
|
6469
|
+
aspect_ratio: z28.enum(["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"]).optional(),
|
|
6470
|
+
generate_audio: z28.boolean().optional(),
|
|
6471
|
+
seed: z28.number().int().nonnegative().optional(),
|
|
6325
6472
|
// Veo-only passthroughs (routed via `provider.options.google-vertex.parameters`).
|
|
6326
|
-
negative_prompt:
|
|
6327
|
-
person_generation:
|
|
6328
|
-
enhance_prompt:
|
|
6329
|
-
conditioning_scale:
|
|
6473
|
+
negative_prompt: z28.string().optional(),
|
|
6474
|
+
person_generation: z28.string().optional(),
|
|
6475
|
+
enhance_prompt: z28.boolean().optional(),
|
|
6476
|
+
conditioning_scale: z28.number().optional()
|
|
6330
6477
|
}).strict();
|
|
6331
6478
|
var videoGenerateNode = delegated({
|
|
6332
6479
|
id: "video_generate",
|
|
@@ -6334,23 +6481,23 @@ var videoGenerateNode = delegated({
|
|
|
6334
6481
|
category: "video",
|
|
6335
6482
|
summary: "Generate video for ad creatives. Two curated models: `bytedance/seedance-2.0` (production quality, photorealistic humans via fal.ai) and `google/veo-3.1-fast` (cheap/fast for iteration and tests). Async with polling.",
|
|
6336
6483
|
when_to_use: "Use `bytedance/seedance-2.0` for final ad output (photoreal subjects, image-to-video with first/last frames). Use `google/veo-3.1-fast` while iterating to keep cost low. Each model has different supported durations, resolutions, and aspect ratios \u2014 see the README per-model section.",
|
|
6337
|
-
inputs:
|
|
6484
|
+
inputs: z28.object({
|
|
6338
6485
|
first_frame: ImageRef.optional(),
|
|
6339
6486
|
last_frame: ImageRef.optional(),
|
|
6340
6487
|
reference: ImageRef.optional()
|
|
6341
6488
|
}).loose(),
|
|
6342
6489
|
params: VideoGenerateParams,
|
|
6343
|
-
outputs:
|
|
6490
|
+
outputs: z28.object({ video: VideoRef }).strict(),
|
|
6344
6491
|
outputKinds: { video: "video" },
|
|
6345
6492
|
cost: () => ({ credits: 50, seconds_estimate: 120 })
|
|
6346
6493
|
});
|
|
6347
6494
|
|
|
6348
6495
|
// src/engine/nodes/remote/videoBackgroundRemove.ts
|
|
6349
|
-
import { z as
|
|
6350
|
-
var VideoBackgroundRemoveParams =
|
|
6351
|
-
model:
|
|
6352
|
-
edge_refinement:
|
|
6353
|
-
output_codec:
|
|
6496
|
+
import { z as z29 } from "zod";
|
|
6497
|
+
var VideoBackgroundRemoveParams = z29.object({
|
|
6498
|
+
model: z29.literal("fal/veed-video-background-removal").optional().default("fal/veed-video-background-removal"),
|
|
6499
|
+
edge_refinement: z29.boolean().optional().default(true),
|
|
6500
|
+
output_codec: z29.enum(["vp9", "h264"]).optional().default("vp9")
|
|
6354
6501
|
}).strict();
|
|
6355
6502
|
var videoBackgroundRemoveNode = delegated({
|
|
6356
6503
|
id: "video_background_remove",
|
|
@@ -6358,18 +6505,18 @@ var videoBackgroundRemoveNode = delegated({
|
|
|
6358
6505
|
category: "video",
|
|
6359
6506
|
summary: "Remove the background from a video and return a transparent VP9-with-alpha WebM (or H264 RGB+alpha pair). Drops directly into a hyperframe composition as `<video src='...'>` for chroma-keyed picture-in-picture overlays. Powered by fal.ai `veed/video-background-removal/fast`.",
|
|
6360
6507
|
when_to_use: "Use when you need a talking-head or subject to float over a custom background in a hyperframe composition. Pair with hyperframe_render(composition: screencast-with-talker) for screencast-with-narrator videos. Output is `video/webm` with alpha \u2014 feed straight into `<video src>` in a composition.",
|
|
6361
|
-
inputs:
|
|
6508
|
+
inputs: z29.object({
|
|
6362
6509
|
video: VideoRef
|
|
6363
6510
|
}).strict(),
|
|
6364
6511
|
params: VideoBackgroundRemoveParams,
|
|
6365
|
-
outputs:
|
|
6512
|
+
outputs: z29.object({ video: VideoRef }).strict(),
|
|
6366
6513
|
outputKinds: { video: "video" },
|
|
6367
6514
|
// $0.012 per 30 frames (edge refinement on) — assume ~30fps; refine via fal dashboard.
|
|
6368
6515
|
cost: () => ({ credits: 50, seconds_estimate: 60 })
|
|
6369
6516
|
});
|
|
6370
6517
|
|
|
6371
6518
|
// src/engine/nodes/remote/videoDeconstruct.ts
|
|
6372
|
-
import { z as
|
|
6519
|
+
import { z as z30 } from "zod";
|
|
6373
6520
|
var VIDEO_DECONSTRUCT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6374
6521
|
var videoDeconstructNode = delegated({
|
|
6375
6522
|
id: "video_deconstruct",
|
|
@@ -6377,34 +6524,34 @@ var videoDeconstructNode = delegated({
|
|
|
6377
6524
|
category: "video",
|
|
6378
6525
|
summary: 'Deconstruct a video into a replication-grade blueprint: scene boundaries, the real start/end frame of every scene (extracted from the video as images), and an exhaustive JSON analysis \u2014 per-scene action detail, camera motion, generation-ready frame/motion prompts, overlay text with full typographic style, floating elements, deeply detailed cast (perceived demographics, ethnicity/skin-tone, styling, market-recasting notes), brand-identified logos (named by brand and what they signal, not by appearance, with on-screen timestamps), dialogue with voice descriptions, music spec, SFX list, plus a word-level transcript. `mode:"index"` is the cheap structure-first pass: scene boundaries + global blueprint only (one LLM call, no frames).',
|
|
6379
6526
|
when_to_use: 'Use to reverse-engineer a reference video (e.g. a competitor ad) so a new canvas can reproduce or remix it scene by scene. Agent loop: (1) optionally run `mode:"index"` to see the structure cheaply (scene count, boundaries, transcript) before planning; (2) run the full deconstruct; (3) read `analysis` and author the reproduction canvas. The blueprint maps 1:1 onto generation nodes: `analysis.scenes[i]` aligns positionally with `start_frames#i`/`end_frames#i`; per scene, `start_frame_prompt`/`end_frame_prompt` feed image_generate (overlay text is excluded from them by contract \u2014 recomposite it from `overlays`), `motion_prompt` + the two frames feed video_generate (first_frame/last_frame), `dialogue[].voice_description` casts tts/dialogue voices, `global.music.music_prompt` feeds music, `sfx[].sound_effect_prompt` feeds sound_effect, and `overlays`/`floating_elements` drive an ffmpeg/hyperframe overlay pass. Long videos (over ~8 min single-shot): run `mode:"index"` first, then several full nodes IN PARALLEL each with a `start_s`/`end_s` window (\u2264480s, snap edges to index scene boundaries), and merge by concatenating `analysis.scenes`; over-length errors include suggested windows. Inject fields into downstream prompts via `{{slot}}`. Pick `~google/gemini-pro-latest` for the densest extraction, `~google/gemini-flash-latest` for cheap/fast passes.',
|
|
6380
|
-
inputs:
|
|
6381
|
-
params:
|
|
6382
|
-
model:
|
|
6383
|
-
mode:
|
|
6384
|
-
language:
|
|
6385
|
-
max_scenes:
|
|
6386
|
-
focus:
|
|
6387
|
-
start_s:
|
|
6388
|
-
end_s:
|
|
6527
|
+
inputs: z30.object({ video: VideoRef }).loose(),
|
|
6528
|
+
params: z30.object({
|
|
6529
|
+
model: z30.enum(VIDEO_DECONSTRUCT_MODELS),
|
|
6530
|
+
mode: z30.enum(["full", "index"]).optional(),
|
|
6531
|
+
language: z30.string().min(2).max(8).optional(),
|
|
6532
|
+
max_scenes: z30.number().int().min(1).max(60).optional(),
|
|
6533
|
+
focus: z30.string().optional(),
|
|
6534
|
+
start_s: z30.number().min(0).optional(),
|
|
6535
|
+
end_s: z30.number().positive().optional(),
|
|
6389
6536
|
// Real visual shot-cut timestamps (absolute seconds), detected locally with
|
|
6390
6537
|
// ffmpeg before the deconstruct. The backend SNAPS its LLM scene boundaries
|
|
6391
6538
|
// onto these and SPLITS any scene that spans one, so a scene's frames never
|
|
6392
6539
|
// straddle a hard cut. `scaffold-video` populates this; omit for LLM-only cuts.
|
|
6393
|
-
shot_cuts:
|
|
6540
|
+
shot_cuts: z30.array(z30.number().min(0)).max(200).optional(),
|
|
6394
6541
|
// The video model's per-clip ceiling (seconds). A shot longer than this is
|
|
6395
6542
|
// split into seamless continuation sub-scenes (shared splice frame), so long
|
|
6396
6543
|
// shots reproduce in full instead of being truncated. `scaffold-video` sets
|
|
6397
6544
|
// the Seedance ceiling (15); omit to disable length splitting.
|
|
6398
|
-
max_clip_s:
|
|
6545
|
+
max_clip_s: z30.number().positive().max(60).optional(),
|
|
6399
6546
|
// Transcript provider for the blueprint's dialogue/transcript. Default
|
|
6400
6547
|
// Groq Whisper; "deepgram" routes to Nova-3 so words carry punctuation.
|
|
6401
|
-
transcriber:
|
|
6548
|
+
transcriber: z30.enum(["groq", "deepgram"]).optional()
|
|
6402
6549
|
}).strict(),
|
|
6403
|
-
outputs:
|
|
6550
|
+
outputs: z30.object({
|
|
6404
6551
|
analysis: JsonRef,
|
|
6405
6552
|
// Absent in mode:"index" (structure only, no Mux frame extraction).
|
|
6406
|
-
start_frames:
|
|
6407
|
-
end_frames:
|
|
6553
|
+
start_frames: z30.array(ImageRef).min(1).optional(),
|
|
6554
|
+
end_frames: z30.array(ImageRef).min(1).optional(),
|
|
6408
6555
|
transcript: JsonRef
|
|
6409
6556
|
}).strict(),
|
|
6410
6557
|
outputKinds: { analysis: "json", start_frames: "image", end_frames: "image", transcript: "json" },
|
|
@@ -6412,22 +6559,22 @@ var videoDeconstructNode = delegated({
|
|
|
6412
6559
|
});
|
|
6413
6560
|
|
|
6414
6561
|
// src/engine/nodes/remote/videoLipsync.ts
|
|
6415
|
-
import { z as
|
|
6416
|
-
var FalLipsyncParams =
|
|
6417
|
-
model:
|
|
6562
|
+
import { z as z31 } from "zod";
|
|
6563
|
+
var FalLipsyncParams = z31.object({
|
|
6564
|
+
model: z31.literal("fal/veed-lipsync")
|
|
6418
6565
|
}).strict();
|
|
6419
|
-
var VideoLipsyncParams =
|
|
6566
|
+
var VideoLipsyncParams = z31.discriminatedUnion("model", [FalLipsyncParams]);
|
|
6420
6567
|
var videoLipsyncNode = delegated({
|
|
6421
6568
|
id: "video_lipsync",
|
|
6422
6569
|
version: "1.0.0",
|
|
6423
6570
|
category: "video",
|
|
6424
6571
|
summary: "Lip-sync a video to an audio track. Currently backed by VEED via fal.ai (`fal/veed-lipsync`). $0.40/min of output.",
|
|
6425
|
-
inputs:
|
|
6572
|
+
inputs: z31.object({
|
|
6426
6573
|
video: VideoRef,
|
|
6427
6574
|
audio: AudioRef
|
|
6428
6575
|
}).strict(),
|
|
6429
6576
|
params: VideoLipsyncParams,
|
|
6430
|
-
outputs:
|
|
6577
|
+
outputs: z31.object({ video: VideoRef }).strict(),
|
|
6431
6578
|
outputKinds: { video: "video" },
|
|
6432
6579
|
cost: () => ({ credits: 20, seconds_estimate: 120 })
|
|
6433
6580
|
});
|
|
@@ -6436,7 +6583,7 @@ var videoLipsyncNode = delegated({
|
|
|
6436
6583
|
import { mkdtemp as mkdtemp6, readFile as readFile10, rm as rm6 } from "fs/promises";
|
|
6437
6584
|
import { tmpdir as tmpdir6 } from "os";
|
|
6438
6585
|
import path13 from "path";
|
|
6439
|
-
import { z as
|
|
6586
|
+
import { z as z32 } from "zod";
|
|
6440
6587
|
|
|
6441
6588
|
// src/engine/nodes/local/lib/ffmpeg.ts
|
|
6442
6589
|
import { execFile as execFile7 } from "child_process";
|
|
@@ -6515,21 +6662,21 @@ ${detail.slice(-4e3)}`);
|
|
|
6515
6662
|
}
|
|
6516
6663
|
|
|
6517
6664
|
// src/engine/nodes/remote/videoTranscribe.ts
|
|
6518
|
-
var VideoTranscribeParams =
|
|
6519
|
-
language:
|
|
6665
|
+
var VideoTranscribeParams = z32.object({
|
|
6666
|
+
language: z32.string().min(2).max(8).optional(),
|
|
6520
6667
|
// Provider choice is explicit (no env-based silent branching). Default Groq
|
|
6521
6668
|
// Whisper; "deepgram" routes to Deepgram Nova-3, which additionally emits a
|
|
6522
6669
|
// `rich` JSON output with punctuated words + paragraph/sentence grouping.
|
|
6523
|
-
transcriber:
|
|
6670
|
+
transcriber: z32.enum(["groq", "deepgram"]).optional()
|
|
6524
6671
|
}).strict();
|
|
6525
|
-
var VideoTranscribeInputs =
|
|
6672
|
+
var VideoTranscribeInputs = z32.object({
|
|
6526
6673
|
video: VideoRef
|
|
6527
6674
|
}).strict();
|
|
6528
|
-
var VideoTranscribeOutputs =
|
|
6529
|
-
transcript:
|
|
6675
|
+
var VideoTranscribeOutputs = z32.object({
|
|
6676
|
+
transcript: z32.custom(),
|
|
6530
6677
|
// Only emitted by the Deepgram path: full punctuated words + paragraph /
|
|
6531
6678
|
// sentence grouping with speaker indices. Absent for the default Groq path.
|
|
6532
|
-
rich:
|
|
6679
|
+
rich: z32.custom().optional()
|
|
6533
6680
|
}).strict();
|
|
6534
6681
|
var AUDIO_EXTRACT_TIMEOUT_MS = 6e4;
|
|
6535
6682
|
var videoTranscribeNode = defineNode({
|
|
@@ -6614,29 +6761,29 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
6614
6761
|
}
|
|
6615
6762
|
|
|
6616
6763
|
// src/engine/nodes/remote/voiceSelect.ts
|
|
6617
|
-
import { z as
|
|
6764
|
+
import { z as z33 } from "zod";
|
|
6618
6765
|
var voiceSelectNode = delegated({
|
|
6619
6766
|
id: "voice_select",
|
|
6620
6767
|
version: "1.0.0",
|
|
6621
6768
|
category: "audio",
|
|
6622
6769
|
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.',
|
|
6623
6770
|
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.',
|
|
6624
|
-
inputs:
|
|
6625
|
-
params:
|
|
6626
|
-
description:
|
|
6627
|
-
gender:
|
|
6628
|
-
age:
|
|
6629
|
-
accent:
|
|
6630
|
-
language:
|
|
6631
|
-
limit:
|
|
6771
|
+
inputs: z33.object({}).loose(),
|
|
6772
|
+
params: z33.object({
|
|
6773
|
+
description: z33.string().min(1),
|
|
6774
|
+
gender: z33.string().optional(),
|
|
6775
|
+
age: z33.string().optional(),
|
|
6776
|
+
accent: z33.string().optional(),
|
|
6777
|
+
language: z33.string().optional(),
|
|
6778
|
+
limit: z33.number().int().min(1).max(20).optional()
|
|
6632
6779
|
}).strict(),
|
|
6633
|
-
outputs:
|
|
6780
|
+
outputs: z33.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
|
|
6634
6781
|
outputKinds: { voice_id: "text", candidates: "json" },
|
|
6635
6782
|
cost: () => ({ credits: 0, seconds_estimate: 5 })
|
|
6636
6783
|
});
|
|
6637
6784
|
|
|
6638
6785
|
// src/engine/schema/catalog.ts
|
|
6639
|
-
import { z as
|
|
6786
|
+
import { z as z34 } from "zod";
|
|
6640
6787
|
function generateCatalog(registry, opts = {}) {
|
|
6641
6788
|
const entries = registry.all().map((def) => {
|
|
6642
6789
|
const cost = def.cost ? safeCost(def) : void 0;
|
|
@@ -6647,9 +6794,9 @@ function generateCatalog(registry, opts = {}) {
|
|
|
6647
6794
|
summary: def.summary,
|
|
6648
6795
|
when_to_use: def.when_to_use,
|
|
6649
6796
|
location: def.location,
|
|
6650
|
-
inputs:
|
|
6651
|
-
params:
|
|
6652
|
-
outputs:
|
|
6797
|
+
inputs: z34.toJSONSchema(def.inputs, { unrepresentable: "any" }),
|
|
6798
|
+
params: z34.toJSONSchema(def.params, { unrepresentable: "any" }),
|
|
6799
|
+
outputs: z34.toJSONSchema(def.outputs, { unrepresentable: "any" }),
|
|
6653
6800
|
cost_estimate_credits: cost?.credits,
|
|
6654
6801
|
runtime_estimate_seconds: cost?.seconds_estimate
|
|
6655
6802
|
};
|
|
@@ -6726,7 +6873,8 @@ var LOCAL_NODES = [
|
|
|
6726
6873
|
imagemagickNode,
|
|
6727
6874
|
videoTranscribeNode,
|
|
6728
6875
|
fontSpecimenNode,
|
|
6729
|
-
audioTimelineNode
|
|
6876
|
+
audioTimelineNode,
|
|
6877
|
+
collectNode
|
|
6730
6878
|
];
|
|
6731
6879
|
var REMOTE_NODES = [
|
|
6732
6880
|
textGenerateNode,
|
|
@@ -6792,6 +6940,8 @@ export {
|
|
|
6792
6940
|
ulid,
|
|
6793
6941
|
isPersistedAssetRef,
|
|
6794
6942
|
collectAssetRefLikes,
|
|
6943
|
+
REF_PREFIX,
|
|
6944
|
+
parseRefExpr,
|
|
6795
6945
|
sha256Hex,
|
|
6796
6946
|
elementMentionKeywords,
|
|
6797
6947
|
toModelSafeImage,
|
|
@@ -6805,4 +6955,4 @@ export {
|
|
|
6805
6955
|
defaultRegistry,
|
|
6806
6956
|
createEngineFromEnv
|
|
6807
6957
|
};
|
|
6808
|
-
//# sourceMappingURL=chunk-
|
|
6958
|
+
//# sourceMappingURL=chunk-Q3K5TXC6.js.map
|