@koda-sl/baker-cli 0.124.0-dev.8e4328629 → 0.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -345
- package/dist/{chunk-Q3K5TXC6.js → chunk-IWPAXJC3.js} +428 -841
- package/dist/chunk-IWPAXJC3.js.map +1 -0
- package/dist/cli.js +3181 -3189
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +0 -97
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-Q3K5TXC6.js.map +0 -1
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
__toESM
|
|
4
4
|
} from "./chunk-5WRI5ZAA.js";
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
|
|
7
7
|
var require_safe_stable_stringify = __commonJS({
|
|
8
|
-
"
|
|
8
|
+
"../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
|
|
9
9
|
"use strict";
|
|
10
10
|
var { hasOwnProperty } = Object.prototype;
|
|
11
11
|
var stringify = configure2();
|
|
@@ -138,9 +138,9 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
138
138
|
}
|
|
139
139
|
if (value) {
|
|
140
140
|
return (value2) => {
|
|
141
|
-
let
|
|
142
|
-
if (typeof value2 !== "function")
|
|
143
|
-
throw new Error(
|
|
141
|
+
let message = `Object can not safely be stringified. Received type ${typeof value2}`;
|
|
142
|
+
if (typeof value2 !== "function") message += ` (${value2.toString()})`;
|
|
143
|
+
throw new Error(message);
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
}
|
|
@@ -652,9 +652,6 @@ var HttpClient = class {
|
|
|
652
652
|
async postJson(path16, body, signal) {
|
|
653
653
|
return await this.requestJson("POST", path16, body, signal);
|
|
654
654
|
}
|
|
655
|
-
async putJson(path16, body, signal) {
|
|
656
|
-
return await this.requestJson("PUT", path16, body, signal);
|
|
657
|
-
}
|
|
658
655
|
async getJson(path16, signal) {
|
|
659
656
|
return await this.requestJson("GET", path16, void 0, signal);
|
|
660
657
|
}
|
|
@@ -682,8 +679,8 @@ var HttpClient = class {
|
|
|
682
679
|
try {
|
|
683
680
|
const res = await this.fetchFn(url, {
|
|
684
681
|
method,
|
|
685
|
-
headers: method === "
|
|
686
|
-
body: method === "
|
|
682
|
+
headers: method === "POST" ? { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` } : { Authorization: `Bearer ${this.apiKey}` },
|
|
683
|
+
body: method === "POST" ? JSON.stringify(body) : void 0,
|
|
687
684
|
signal: controller.signal
|
|
688
685
|
});
|
|
689
686
|
if (res.ok) return { kind: "value", value: await res.json() };
|
|
@@ -720,33 +717,33 @@ async function parseErrorBody(res) {
|
|
|
720
717
|
const errObj = body.error ?? {};
|
|
721
718
|
return classifyHttpError(res.status, errObj, errObj.message ?? `HTTP ${res.status}`);
|
|
722
719
|
}
|
|
723
|
-
function classifyHttpError(status, errObj,
|
|
720
|
+
function classifyHttpError(status, errObj, message) {
|
|
724
721
|
if (errObj.code === CONTENT_POLICY_CODE) {
|
|
725
|
-
return { kind: "content_policy", status, provider: errObj.provider, message
|
|
722
|
+
return { kind: "content_policy", status, provider: errObj.provider, message };
|
|
726
723
|
}
|
|
727
724
|
if (status === 401 || status === 403) {
|
|
728
|
-
return { kind: "unauthorized", status, message
|
|
725
|
+
return { kind: "unauthorized", status, message };
|
|
729
726
|
}
|
|
730
727
|
if (status === 400 || status === 422) {
|
|
731
|
-
return { kind: "validation", status, message
|
|
728
|
+
return { kind: "validation", status, message, details: errObj.details };
|
|
732
729
|
}
|
|
733
730
|
if (status === 502 || status === 504) {
|
|
734
731
|
if (errObj.code === "provider_timeout" || status === 504) {
|
|
735
|
-
return { kind: "timeout", provider: errObj.provider, message
|
|
732
|
+
return { kind: "timeout", provider: errObj.provider, message };
|
|
736
733
|
}
|
|
737
734
|
return {
|
|
738
735
|
kind: "provider",
|
|
739
736
|
status,
|
|
740
737
|
provider: errObj.provider,
|
|
741
738
|
code: errObj.code ?? "provider_error",
|
|
742
|
-
message
|
|
739
|
+
message,
|
|
743
740
|
retryable: errObj.retryable ?? true
|
|
744
741
|
};
|
|
745
742
|
}
|
|
746
743
|
if (status >= 500 || status === 429) {
|
|
747
|
-
return { kind: "server", status, message
|
|
744
|
+
return { kind: "server", status, message };
|
|
748
745
|
}
|
|
749
|
-
return { kind: "validation", status, message
|
|
746
|
+
return { kind: "validation", status, message, details: errObj.details };
|
|
750
747
|
}
|
|
751
748
|
function backoffMs(attempt) {
|
|
752
749
|
return 1e3 * 2 ** attempt;
|
|
@@ -780,9 +777,7 @@ function failedJobError(error) {
|
|
|
780
777
|
retryable: error.retryable ?? false
|
|
781
778
|
});
|
|
782
779
|
}
|
|
783
|
-
|
|
784
|
-
return attempt < 15 ? 1e3 : 3e3;
|
|
785
|
-
}
|
|
780
|
+
var JOB_POLL_INTERVAL_MS = 3e3;
|
|
786
781
|
var JOB_POLL_MAX_MS = 20 * 60 * 1e3;
|
|
787
782
|
var BackendClient = class {
|
|
788
783
|
http;
|
|
@@ -799,7 +794,7 @@ var BackendClient = class {
|
|
|
799
794
|
async pollJob(jobId, signal) {
|
|
800
795
|
const deadline = Date.now() + JOB_POLL_MAX_MS;
|
|
801
796
|
const path16 = `/api/canvas/jobs/${encodeURIComponent(jobId)}`;
|
|
802
|
-
|
|
797
|
+
while (true) {
|
|
803
798
|
if (signal?.aborted) {
|
|
804
799
|
throw new BackendHttpError({ kind: "network", cause: signal.reason ?? new Error("aborted") });
|
|
805
800
|
}
|
|
@@ -809,7 +804,7 @@ var BackendClient = class {
|
|
|
809
804
|
if (Date.now() > deadline) {
|
|
810
805
|
throw new BackendHttpError({ kind: "timeout", message: `job ${jobId} did not finish in time` });
|
|
811
806
|
}
|
|
812
|
-
await sleep(
|
|
807
|
+
await sleep(JOB_POLL_INTERVAL_MS);
|
|
813
808
|
}
|
|
814
809
|
}
|
|
815
810
|
presignAssetUpload(sha256, mime, signal) {
|
|
@@ -819,35 +814,6 @@ var BackendClient = class {
|
|
|
819
814
|
signal
|
|
820
815
|
);
|
|
821
816
|
}
|
|
822
|
-
/** Remote cache lookup. A miss (404) — or an old backend without the route — returns null. */
|
|
823
|
-
async getCacheEntry(cacheKey, signal) {
|
|
824
|
-
try {
|
|
825
|
-
const res = await this.http.getJson(`/api/canvas/cache/${encodeURIComponent(cacheKey)}`, signal);
|
|
826
|
-
return res.entry;
|
|
827
|
-
} catch (e) {
|
|
828
|
-
if (e instanceof BackendHttpError && "status" in e.detail && e.detail.status === 404) return null;
|
|
829
|
-
throw e;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
async putCacheEntry(entry, signal) {
|
|
833
|
-
await this.http.putJson(
|
|
834
|
-
`/api/canvas/cache/${encodeURIComponent(entry.cacheKey)}`,
|
|
835
|
-
entry,
|
|
836
|
-
signal
|
|
837
|
-
);
|
|
838
|
-
}
|
|
839
|
-
/** Durable run-history record — POST /api/canvas/runs (idempotent server-side on runId). */
|
|
840
|
-
async recordRun(payload, signal) {
|
|
841
|
-
await this.http.postJson("/api/canvas/runs", payload, signal);
|
|
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
|
-
}
|
|
851
817
|
getArtifact(kind, name, version, signal) {
|
|
852
818
|
const path16 = version ? `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}` : `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`;
|
|
853
819
|
return this.http.getJson(path16, signal);
|
|
@@ -871,17 +837,14 @@ function requireCredentialsFromEnv(env = process.env) {
|
|
|
871
837
|
}
|
|
872
838
|
return c;
|
|
873
839
|
}
|
|
874
|
-
function remoteCacheEnabledFromEnv(env = process.env) {
|
|
875
|
-
return env.BAKER_CANVAS_REMOTE_CACHE !== "off";
|
|
876
|
-
}
|
|
877
840
|
|
|
878
841
|
// src/engine/engine/errors.ts
|
|
879
842
|
function isBlocking(issue) {
|
|
880
843
|
return issue.severity !== "warning";
|
|
881
844
|
}
|
|
882
845
|
var CanvasError = class extends Error {
|
|
883
|
-
constructor(
|
|
884
|
-
super(
|
|
846
|
+
constructor(message) {
|
|
847
|
+
super(message);
|
|
885
848
|
this.name = "CanvasError";
|
|
886
849
|
}
|
|
887
850
|
};
|
|
@@ -938,7 +901,7 @@ function describeCause(c) {
|
|
|
938
901
|
}
|
|
939
902
|
}
|
|
940
903
|
|
|
941
|
-
//
|
|
904
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
942
905
|
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
943
906
|
var configure = import__.default.configure;
|
|
944
907
|
var wrapper_default = import__.default;
|
|
@@ -996,10 +959,10 @@ var ELEVENLABS_OUTPUT_FORMATS = [
|
|
|
996
959
|
var ELEVENLABS_MAX_TEXT_CHARS = 45454;
|
|
997
960
|
var ELEVENLABS_MAX_MUSIC_LENGTH_MS = 454545;
|
|
998
961
|
var OPENROUTER_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
999
|
-
var
|
|
1000
|
-
var
|
|
962
|
+
var FAL_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp"];
|
|
963
|
+
var FAL_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
1001
964
|
var DECONSTRUCT_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
1002
|
-
var
|
|
965
|
+
var FAL_AUDIO_MIMES = ["audio/wav", "audio/mpeg", "audio/mp3"];
|
|
1003
966
|
var IMAGE_GENERATE_MODELS = [
|
|
1004
967
|
"openai/gpt-5.4-image-2",
|
|
1005
968
|
"google/gemini-3.5-flash",
|
|
@@ -1217,23 +1180,20 @@ var MODEL_REGISTRY = {
|
|
|
1217
1180
|
},
|
|
1218
1181
|
video_generate: {
|
|
1219
1182
|
"bytedance/seedance-2.0": {
|
|
1220
|
-
// Routed via
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1223
|
-
// presenter face or routing real faces to Veo, not the provider choice.
|
|
1183
|
+
// Routed via fal.ai (not OpenRouter) because OpenRouter's Seedance
|
|
1184
|
+
// passthrough rejects photorealistic human reference frames via
|
|
1185
|
+
// ByteDance's "real person" safety filter.
|
|
1224
1186
|
label: "ByteDance Seedance 2.0",
|
|
1225
1187
|
inputs: [],
|
|
1226
|
-
optional_inputs: [{ kind: "image", mimes:
|
|
1188
|
+
optional_inputs: [{ kind: "image", mimes: FAL_IMAGE_MIMES }],
|
|
1227
1189
|
required: ["prompt"],
|
|
1228
1190
|
params: {
|
|
1229
|
-
|
|
1230
|
-
// it here so an over-length prompt fails validate (free) not the billed call.
|
|
1231
|
-
prompt: { kind: "string", maxLength: 4e3 },
|
|
1191
|
+
prompt: { kind: "string" },
|
|
1232
1192
|
aspect_ratio: {
|
|
1233
1193
|
kind: "string",
|
|
1234
1194
|
enum: ["1:1", "3:4", "9:16", "4:3", "16:9", "21:9", "9:21"]
|
|
1235
1195
|
},
|
|
1236
|
-
resolution: { kind: "string", enum: ["480p", "720p", "1080p"
|
|
1196
|
+
resolution: { kind: "string", enum: ["480p", "720p", "1080p"] },
|
|
1237
1197
|
duration: { kind: "number", enum: SEEDANCE_DURATIONS },
|
|
1238
1198
|
seed: { kind: "number" },
|
|
1239
1199
|
generate_audio: { kind: "boolean" }
|
|
@@ -1254,10 +1214,7 @@ var MODEL_REGISTRY = {
|
|
|
1254
1214
|
duration: { kind: "number", enum: [4, 6, 8] },
|
|
1255
1215
|
seed: { kind: "number" },
|
|
1256
1216
|
generate_audio: { kind: "boolean" },
|
|
1257
|
-
|
|
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"] },
|
|
1217
|
+
person_generation: { kind: "string", enum: ["allow_all"] },
|
|
1261
1218
|
enhance_prompt: { kind: "boolean" },
|
|
1262
1219
|
conditioning_scale: { kind: "number" }
|
|
1263
1220
|
}
|
|
@@ -1308,8 +1265,8 @@ var MODEL_REGISTRY = {
|
|
|
1308
1265
|
"fal/veed-lipsync": {
|
|
1309
1266
|
label: "VEED Lipsync (fal.ai)",
|
|
1310
1267
|
inputs: [
|
|
1311
|
-
{ kind: "video", mimes:
|
|
1312
|
-
{ kind: "audio", mimes:
|
|
1268
|
+
{ kind: "video", mimes: FAL_VIDEO_MIMES },
|
|
1269
|
+
{ kind: "audio", mimes: FAL_AUDIO_MIMES }
|
|
1313
1270
|
],
|
|
1314
1271
|
required: [],
|
|
1315
1272
|
params: {}
|
|
@@ -1345,7 +1302,7 @@ var MODEL_REGISTRY = {
|
|
|
1345
1302
|
// TARGET voice, preserving timing/prosody. Used to normalize a talking-head
|
|
1346
1303
|
// clip's native (generator-chosen) voice into ONE consistent brand voice.
|
|
1347
1304
|
label: "ElevenLabs Voice Changer (multilingual STS v2)",
|
|
1348
|
-
inputs: [{ kind: "audio", mimes:
|
|
1305
|
+
inputs: [{ kind: "audio", mimes: FAL_AUDIO_MIMES }],
|
|
1349
1306
|
required: ["voice"],
|
|
1350
1307
|
params: {
|
|
1351
1308
|
voice: { kind: "string" },
|
|
@@ -1372,7 +1329,7 @@ var MODEL_REGISTRY = {
|
|
|
1372
1329
|
},
|
|
1373
1330
|
"elevenlabs/video-background-music-v1": {
|
|
1374
1331
|
label: "ElevenLabs Video Background Music v1",
|
|
1375
|
-
inputs: [{ kind: "video", mimes:
|
|
1332
|
+
inputs: [{ kind: "video", mimes: FAL_VIDEO_MIMES }],
|
|
1376
1333
|
required: [],
|
|
1377
1334
|
params: {
|
|
1378
1335
|
description: { kind: "string" },
|
|
@@ -1543,7 +1500,7 @@ function validateValue(key, value, schema, model) {
|
|
|
1543
1500
|
}
|
|
1544
1501
|
|
|
1545
1502
|
// src/engine/lib/concurrency.ts
|
|
1546
|
-
var DEFAULT_CONCURRENCY =
|
|
1503
|
+
var DEFAULT_CONCURRENCY = 5;
|
|
1547
1504
|
function resolveConcurrency(...candidates) {
|
|
1548
1505
|
for (const candidate of candidates) {
|
|
1549
1506
|
if (candidate === void 0 || candidate === "") continue;
|
|
@@ -1605,160 +1562,6 @@ function encodeRandom() {
|
|
|
1605
1562
|
return out;
|
|
1606
1563
|
}
|
|
1607
1564
|
|
|
1608
|
-
// src/engine/storage/remote-cache-store.ts
|
|
1609
|
-
var CANVAS_ASSETS_URL_SEGMENT = "/canvas-assets/";
|
|
1610
|
-
function isPersistedAssetRef(ref) {
|
|
1611
|
-
const { url, sha256 } = ref;
|
|
1612
|
-
if (typeof url !== "string" || typeof sha256 !== "string") return false;
|
|
1613
|
-
return url.includes(`${CANVAS_ASSETS_URL_SEGMENT}${sha256.slice(0, 2)}/${sha256}`) || url.includes(`${CANVAS_ASSETS_URL_SEGMENT}${sha256}`);
|
|
1614
|
-
}
|
|
1615
|
-
function isAssetRefLike(value) {
|
|
1616
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.sha256 === "string" && typeof value.mime === "string";
|
|
1617
|
-
}
|
|
1618
|
-
function collectAssetRefLikes(value, out = []) {
|
|
1619
|
-
if (Array.isArray(value)) {
|
|
1620
|
-
for (const item of value) collectAssetRefLikes(item, out);
|
|
1621
|
-
return out;
|
|
1622
|
-
}
|
|
1623
|
-
if (typeof value !== "object" || value === null) return out;
|
|
1624
|
-
if (isAssetRefLike(value)) {
|
|
1625
|
-
out.push(value);
|
|
1626
|
-
}
|
|
1627
|
-
for (const item of Object.values(value)) collectAssetRefLikes(item, out);
|
|
1628
|
-
return out;
|
|
1629
|
-
}
|
|
1630
|
-
function entryFullyPersisted(entry) {
|
|
1631
|
-
return collectAssetRefLikes(entry.outputs).every((ref) => isPersistedAssetRef(ref));
|
|
1632
|
-
}
|
|
1633
|
-
function stripLocalFields(entry) {
|
|
1634
|
-
const clone = JSON.parse(JSON.stringify(entry));
|
|
1635
|
-
for (const ref of collectAssetRefLikes(clone.outputs)) {
|
|
1636
|
-
delete ref.path;
|
|
1637
|
-
delete ref.bytes;
|
|
1638
|
-
}
|
|
1639
|
-
return clone;
|
|
1640
|
-
}
|
|
1641
|
-
var RemoteCacheStore = class {
|
|
1642
|
-
client;
|
|
1643
|
-
log;
|
|
1644
|
-
constructor(client, log) {
|
|
1645
|
-
this.client = client;
|
|
1646
|
-
this.log = log ?? (() => void 0);
|
|
1647
|
-
}
|
|
1648
|
-
async get(cacheKey) {
|
|
1649
|
-
return await this.client.getCacheEntry(cacheKey);
|
|
1650
|
-
}
|
|
1651
|
-
async put(entry) {
|
|
1652
|
-
if (!entryFullyPersisted(entry)) {
|
|
1653
|
-
this.log(`[cache ] ${entry.cacheKey.slice(0, 12)}\u2026 has local-only assets, kept local`);
|
|
1654
|
-
return;
|
|
1655
|
-
}
|
|
1656
|
-
const stripped = stripLocalFields(entry);
|
|
1657
|
-
if (stripped.refs.length > MAX_REMOTE_REFS) {
|
|
1658
|
-
stripped.refs = stripped.refs.slice(0, MAX_REMOTE_REFS);
|
|
1659
|
-
}
|
|
1660
|
-
await this.client.putCacheEntry(stripped);
|
|
1661
|
-
}
|
|
1662
|
-
};
|
|
1663
|
-
var MAX_REMOTE_REFS = 512;
|
|
1664
|
-
var LayeredCacheStore = class {
|
|
1665
|
-
rootDir;
|
|
1666
|
-
local;
|
|
1667
|
-
remote;
|
|
1668
|
-
assets;
|
|
1669
|
-
log;
|
|
1670
|
-
constructor(opts) {
|
|
1671
|
-
this.local = opts.local;
|
|
1672
|
-
this.remote = opts.remote;
|
|
1673
|
-
this.assets = opts.assets;
|
|
1674
|
-
this.rootDir = opts.local.rootDir;
|
|
1675
|
-
this.log = opts.log ?? (() => void 0);
|
|
1676
|
-
}
|
|
1677
|
-
async get(cacheKey) {
|
|
1678
|
-
const localHit = await this.local.get(cacheKey);
|
|
1679
|
-
if (localHit) return localHit;
|
|
1680
|
-
let remoteEntry;
|
|
1681
|
-
try {
|
|
1682
|
-
remoteEntry = await this.remote.get(cacheKey);
|
|
1683
|
-
} catch (e) {
|
|
1684
|
-
this.log(`[cache ] remote lookup failed (${message(e)}) \u2014 treating as miss`);
|
|
1685
|
-
return null;
|
|
1686
|
-
}
|
|
1687
|
-
if (!remoteEntry) return null;
|
|
1688
|
-
let rehydrated;
|
|
1689
|
-
try {
|
|
1690
|
-
rehydrated = await this.rehydrate(remoteEntry);
|
|
1691
|
-
} catch (e) {
|
|
1692
|
-
this.log(`[cache ] ${cacheKey.slice(0, 12)}\u2026 rehydration failed (${message(e)}) \u2014 treating as miss`);
|
|
1693
|
-
return null;
|
|
1694
|
-
}
|
|
1695
|
-
await this.local.put(rehydrated);
|
|
1696
|
-
return rehydrated;
|
|
1697
|
-
}
|
|
1698
|
-
async put(entry) {
|
|
1699
|
-
await this.local.put(entry);
|
|
1700
|
-
try {
|
|
1701
|
-
await this.remote.put(entry);
|
|
1702
|
-
} catch (e) {
|
|
1703
|
-
this.log(`[cache ] remote write failed (${message(e)}) \u2014 entry kept local`);
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
/**
|
|
1707
|
-
* Download every referenced asset into the local content-addressed store
|
|
1708
|
-
* (sha-verified) and stamp fresh local paths. Any ref that cannot be
|
|
1709
|
-
* rehydrated fails the WHOLE entry — a partially-hydrated cache hit would
|
|
1710
|
-
* crash materialization later with a far less actionable error.
|
|
1711
|
-
*/
|
|
1712
|
-
async rehydrate(entry) {
|
|
1713
|
-
const clone = JSON.parse(JSON.stringify(entry));
|
|
1714
|
-
for (const ref of collectAssetRefLikes(clone.outputs)) {
|
|
1715
|
-
if (!isPersistedAssetRef(ref)) {
|
|
1716
|
-
throw new Error(`ref ${ref.sha256.slice(0, 12)}\u2026 has no persisted url`);
|
|
1717
|
-
}
|
|
1718
|
-
const ingested = await this.assets.ingestRemote({
|
|
1719
|
-
kind: typeof ref.kind === "string" ? ref.kind : "json",
|
|
1720
|
-
url: ref.url,
|
|
1721
|
-
sha256: ref.sha256,
|
|
1722
|
-
mime: ref.mime,
|
|
1723
|
-
metadata: ref.metadata ?? void 0
|
|
1724
|
-
});
|
|
1725
|
-
ref.path = ingested.path;
|
|
1726
|
-
}
|
|
1727
|
-
return clone;
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
function message(e) {
|
|
1731
|
-
return e instanceof Error ? e.message : String(e);
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
// src/engine/nodes/remote/upload.ts
|
|
1735
|
-
async function presignAndPut(args) {
|
|
1736
|
-
const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
|
|
1737
|
-
const putRes = await fetch(putUrl, {
|
|
1738
|
-
method: "PUT",
|
|
1739
|
-
body: new Uint8Array(args.bytes),
|
|
1740
|
-
headers: { "Content-Type": args.mime },
|
|
1741
|
-
signal: args.ctx.signal
|
|
1742
|
-
});
|
|
1743
|
-
if (!putRes.ok) {
|
|
1744
|
-
throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
|
|
1745
|
-
}
|
|
1746
|
-
return publicUrl;
|
|
1747
|
-
}
|
|
1748
|
-
async function ensureUploaded(ref, ctx) {
|
|
1749
|
-
if (ref.url) return ref;
|
|
1750
|
-
const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
|
|
1751
|
-
const url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
|
|
1752
|
-
return { ...ref, url };
|
|
1753
|
-
}
|
|
1754
|
-
async function persistOutputAssetUrls(outputs, ctx) {
|
|
1755
|
-
for (const ref of collectAssetRefLikes(outputs)) {
|
|
1756
|
-
if (isPersistedAssetRef(ref)) continue;
|
|
1757
|
-
const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
|
|
1758
|
-
ref.url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
1565
|
// src/engine/schema/canvas.ts
|
|
1763
1566
|
import { z } from "zod";
|
|
1764
1567
|
var REF_PREFIX = "$ref:";
|
|
@@ -1788,14 +1591,7 @@ var NodeDecl = z.object({
|
|
|
1788
1591
|
version: z.string().min(1).optional(),
|
|
1789
1592
|
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
1790
1593
|
params: z.record(z.string(), 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()
|
|
1594
|
+
when: z.unknown().optional()
|
|
1799
1595
|
}).strict();
|
|
1800
1596
|
var OutputRef = z.object({
|
|
1801
1597
|
node: z.string(),
|
|
@@ -3045,7 +2841,6 @@ var Engine = class {
|
|
|
3045
2841
|
cache;
|
|
3046
2842
|
outputsDir;
|
|
3047
2843
|
log;
|
|
3048
|
-
persistAssets;
|
|
3049
2844
|
constructor(opts) {
|
|
3050
2845
|
this.registry = opts.registry;
|
|
3051
2846
|
this.client = opts.client;
|
|
@@ -3053,7 +2848,6 @@ var Engine = class {
|
|
|
3053
2848
|
this.cache = opts.cache;
|
|
3054
2849
|
this.outputsDir = opts.outputsDir;
|
|
3055
2850
|
this.log = opts.log ?? (() => void 0);
|
|
3056
|
-
this.persistAssets = opts.persistAssets ?? false;
|
|
3057
2851
|
}
|
|
3058
2852
|
validate(canvas) {
|
|
3059
2853
|
return validateCanvas(canvas, this.registry);
|
|
@@ -3077,16 +2871,7 @@ var Engine = class {
|
|
|
3077
2871
|
const outputs = {};
|
|
3078
2872
|
const counters = { cachedNodes: 0, totalCredits: 0 };
|
|
3079
2873
|
const nodeRuns = [];
|
|
3080
|
-
|
|
3081
|
-
const needsBytes = computeNeedsLocalBytes(canvas, graph, this.registry);
|
|
3082
|
-
this.emitProgress(opts, {
|
|
3083
|
-
kind: "plan",
|
|
3084
|
-
nodes: [...graph.entries()].map(([id, deps]) => {
|
|
3085
|
-
const node = canvas.nodes.find((n) => n.id === id);
|
|
3086
|
-
return { node_id: id, node_type: node?.type ?? "unknown", deps: [...deps], params: node?.params };
|
|
3087
|
-
})
|
|
3088
|
-
});
|
|
3089
|
-
await this.runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns, needsBytes);
|
|
2874
|
+
await this.runLayers(canvas, outputs, runId, writer, opts, counters, nodeRuns);
|
|
3090
2875
|
const output = pickFinalOutput(canvas, outputs);
|
|
3091
2876
|
const stats = {
|
|
3092
2877
|
total_nodes: canvas.nodes.length,
|
|
@@ -3109,51 +2894,39 @@ var Engine = class {
|
|
|
3109
2894
|
`[done ] ${stats.cached_nodes}/${stats.total_nodes} cached, ${stats.total_credits} credits, ${stats.duration_ms}ms`
|
|
3110
2895
|
);
|
|
3111
2896
|
this.log(`outputs in: ${writer.runDir}`);
|
|
3112
|
-
return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir
|
|
2897
|
+
return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir };
|
|
3113
2898
|
}
|
|
3114
|
-
async runLayers(canvas,
|
|
3115
|
-
const layers = topologicalLayers(
|
|
2899
|
+
async runLayers(canvas, outputs, runId, writer, opts, counters, nodeRuns) {
|
|
2900
|
+
const layers = topologicalLayers(this.pruneToOutput(canvas, buildGraph(canvas)));
|
|
3116
2901
|
const limit = resolveConcurrency(opts.concurrency);
|
|
3117
2902
|
for (const layer of layers) {
|
|
3118
|
-
const settled = await mapWithConcurrency(
|
|
3119
|
-
|
|
3120
|
-
|
|
2903
|
+
const settled = await mapWithConcurrency(
|
|
2904
|
+
layer,
|
|
2905
|
+
limit,
|
|
2906
|
+
(nodeId) => this.executeOne(canvas, nodeId, outputs, runId, writer, opts).then((r) => {
|
|
3121
2907
|
if (r.cached) counters.cachedNodes++;
|
|
3122
2908
|
counters.totalCredits += r.credits;
|
|
3123
2909
|
const node = canvas.nodes.find((n) => n.id === nodeId);
|
|
3124
2910
|
if (node) {
|
|
3125
|
-
|
|
2911
|
+
nodeRuns.push({
|
|
3126
2912
|
node_id: nodeId,
|
|
3127
2913
|
node_type: node.type,
|
|
3128
2914
|
cached: r.cached,
|
|
3129
2915
|
duration_ms: r.durationMs,
|
|
3130
2916
|
credits: r.credits
|
|
3131
|
-
};
|
|
3132
|
-
nodeRuns.push(run);
|
|
3133
|
-
this.emitProgress(opts, { kind: "node_settled", run, outputs: outputs[nodeId] ?? {} });
|
|
2917
|
+
});
|
|
3134
2918
|
}
|
|
3135
|
-
})
|
|
3136
|
-
|
|
2919
|
+
})
|
|
2920
|
+
);
|
|
3137
2921
|
const failures = [];
|
|
3138
2922
|
settled.forEach((result, i) => {
|
|
3139
2923
|
const nodeId = layer[i];
|
|
3140
|
-
if (result.status === "rejected" && nodeId) {
|
|
3141
|
-
failures.push({ nodeId, reason: result.reason });
|
|
3142
|
-
this.emitProgress(opts, { kind: "node_failed", node_id: nodeId });
|
|
3143
|
-
}
|
|
2924
|
+
if (result.status === "rejected" && nodeId) failures.push({ nodeId, reason: result.reason });
|
|
3144
2925
|
});
|
|
3145
2926
|
if (failures.length === 1 && failures[0]) throw failures[0].reason;
|
|
3146
2927
|
if (failures.length > 1) throw new LayerExecutionError(failures);
|
|
3147
2928
|
}
|
|
3148
2929
|
}
|
|
3149
|
-
/** Progress consumers are observers only — an exception there must never fail the run. */
|
|
3150
|
-
emitProgress(opts, event) {
|
|
3151
|
-
if (!opts.onProgress) return;
|
|
3152
|
-
try {
|
|
3153
|
-
opts.onProgress(event);
|
|
3154
|
-
} catch {
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3157
2930
|
/**
|
|
3158
2931
|
* Dead-node elimination: when the canvas declares an `output`, execute only the
|
|
3159
2932
|
* nodes that output transitively depends on. Orphaned nodes (left by an edit or
|
|
@@ -3184,13 +2957,12 @@ var Engine = class {
|
|
|
3184
2957
|
}
|
|
3185
2958
|
await writer.writeManifest("_final", output);
|
|
3186
2959
|
}
|
|
3187
|
-
async executeOne(canvas, nodeId, outputs, runId, writer, opts
|
|
2960
|
+
async executeOne(canvas, nodeId, outputs, runId, writer, opts) {
|
|
3188
2961
|
const node = canvas.nodes.find((n) => n.id === nodeId);
|
|
3189
2962
|
if (!node) throw new Error(`executor: missing node ${nodeId}`);
|
|
3190
2963
|
const def = this.registry.get(node.type);
|
|
3191
2964
|
if (!def) throw new Error(`executor: missing registry entry for type ${node.type}`);
|
|
3192
|
-
const
|
|
3193
|
-
const prepared = await prepareForExecution(node, outputs, def, canvas.cache_salt, regenerateToken, this.assets);
|
|
2965
|
+
const prepared = await prepareForExecution(node, outputs, def, canvas.cache_salt, this.assets);
|
|
3194
2966
|
const policy = opts.cache_policy ?? "read_write";
|
|
3195
2967
|
if (policy !== "bypass") {
|
|
3196
2968
|
const cacheT0 = Date.now();
|
|
@@ -3208,27 +2980,17 @@ var Engine = class {
|
|
|
3208
2980
|
nodeId: node.id,
|
|
3209
2981
|
nodeType: node.type,
|
|
3210
2982
|
cacheKey: prepared.cacheKey,
|
|
3211
|
-
downloadOutputs,
|
|
3212
2983
|
client: this.client,
|
|
3213
2984
|
assets: this.assets,
|
|
3214
2985
|
log: this.log,
|
|
3215
2986
|
signal: opts.signal
|
|
3216
2987
|
};
|
|
3217
|
-
const
|
|
3218
|
-
const { parsedInputs, parsedParams } = parseNodeArgs(def, preparedForExec, node.id, node.type);
|
|
2988
|
+
const { parsedInputs, parsedParams } = parseNodeArgs(def, prepared, node.id, node.type);
|
|
3219
2989
|
const result = await invokeExecute(def, parsedInputs, parsedParams, ctx, node.id, node.type);
|
|
3220
2990
|
const elapsed = Date.now() - t0;
|
|
3221
2991
|
const credits = def.cost ? def.cost({ params: parsedParams }).credits : 0;
|
|
3222
2992
|
const outputsObj = result;
|
|
3223
2993
|
outputs[node.id] = outputsObj;
|
|
3224
|
-
if (this.persistAssets) {
|
|
3225
|
-
try {
|
|
3226
|
-
await persistOutputAssetUrls(outputsObj, ctx);
|
|
3227
|
-
} catch (e) {
|
|
3228
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
3229
|
-
this.log(`[warn ] ${node.id}: asset persistence failed (${msg}) \u2014 outputs stay local-only`);
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
2994
|
if (policy === "read_write") {
|
|
3233
2995
|
await this.cache.put({
|
|
3234
2996
|
cacheKey: prepared.cacheKey,
|
|
@@ -3257,42 +3019,8 @@ var Engine = class {
|
|
|
3257
3019
|
}
|
|
3258
3020
|
}
|
|
3259
3021
|
}
|
|
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
|
-
}
|
|
3289
3022
|
};
|
|
3290
|
-
function
|
|
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) {
|
|
3023
|
+
async function prepareForExecution(node, outputs, def, cacheSalt, assets) {
|
|
3296
3024
|
const resolvedInputs = resolveRefs(node.inputs ?? {}, { outputs }) ?? {};
|
|
3297
3025
|
const resolvedParams = resolveRefs(node.params ?? {}, { outputs }) ?? {};
|
|
3298
3026
|
const slotValues = await hydrateTextSlots(resolvedInputs, assets, node.id, node.type);
|
|
@@ -3305,9 +3033,6 @@ async function prepareForExecution(node, outputs, def, cacheSalt, regenerateToke
|
|
|
3305
3033
|
throw new NodeExecutionError(node.id, node.type, { kind: "local", cause: e });
|
|
3306
3034
|
}
|
|
3307
3035
|
}
|
|
3308
|
-
if (regenerateToken !== void 0) {
|
|
3309
|
-
extras = { ...extras ?? {}, __regenerate__: regenerateToken };
|
|
3310
|
-
}
|
|
3311
3036
|
const cacheKey = computeCacheKey({
|
|
3312
3037
|
node_id: node.type,
|
|
3313
3038
|
node_version: def.version,
|
|
@@ -3336,9 +3061,6 @@ async function invokeExecute(def, parsedInputs, parsedParams, ctx, nodeId, nodeT
|
|
|
3336
3061
|
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3337
3062
|
}
|
|
3338
3063
|
}
|
|
3339
|
-
function needsLocalMaterialization(def) {
|
|
3340
|
-
return def.location === "local" && !def.passthroughRefs;
|
|
3341
|
-
}
|
|
3342
3064
|
function pickFinalOutput(canvas, outputs) {
|
|
3343
3065
|
if (canvas.output) {
|
|
3344
3066
|
const node = outputs[canvas.output.node];
|
|
@@ -3349,16 +3071,6 @@ function pickFinalOutput(canvas, outputs) {
|
|
|
3349
3071
|
const lastOut = outputs[last.id];
|
|
3350
3072
|
return lastOut ? Object.values(lastOut)[0] : void 0;
|
|
3351
3073
|
}
|
|
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
|
-
}
|
|
3362
3074
|
function buildGraph(canvas) {
|
|
3363
3075
|
const graph = /* @__PURE__ */ new Map();
|
|
3364
3076
|
for (const n of canvas.nodes) graph.set(n.id, /* @__PURE__ */ new Set());
|
|
@@ -3489,16 +3201,7 @@ async function hydrateSlotValue(value, assets, nodeId, nodeType) {
|
|
|
3489
3201
|
try {
|
|
3490
3202
|
bytes = await assets.readBytes(value.sha256, value.mime);
|
|
3491
3203
|
} catch (e) {
|
|
3492
|
-
|
|
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
|
-
}
|
|
3204
|
+
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3502
3205
|
}
|
|
3503
3206
|
if (bytes.length > MAX_INLINE_TEXT_BYTES) {
|
|
3504
3207
|
throw new NodeExecutionError(nodeId, nodeType, {
|
|
@@ -3606,6 +3309,27 @@ var FontRef = BaseAssetRef.extend({
|
|
|
3606
3309
|
});
|
|
3607
3310
|
var AssetRef = z4.discriminatedUnion("kind", [ImageRef, VideoRef, AudioRef, JsonRef, TextRef, FontRef]);
|
|
3608
3311
|
|
|
3312
|
+
// src/engine/nodes/remote/upload.ts
|
|
3313
|
+
async function presignAndPut(args) {
|
|
3314
|
+
const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
|
|
3315
|
+
const putRes = await fetch(putUrl, {
|
|
3316
|
+
method: "PUT",
|
|
3317
|
+
body: new Uint8Array(args.bytes),
|
|
3318
|
+
headers: { "Content-Type": args.mime },
|
|
3319
|
+
signal: args.ctx.signal
|
|
3320
|
+
});
|
|
3321
|
+
if (!putRes.ok) {
|
|
3322
|
+
throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
|
|
3323
|
+
}
|
|
3324
|
+
return publicUrl;
|
|
3325
|
+
}
|
|
3326
|
+
async function ensureUploaded(ref, ctx) {
|
|
3327
|
+
if (ref.url) return ref;
|
|
3328
|
+
const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
|
|
3329
|
+
const url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
|
|
3330
|
+
return { ...ref, url };
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3609
3333
|
// src/engine/nodes/remote/delegate.ts
|
|
3610
3334
|
function delegated(spec) {
|
|
3611
3335
|
return {
|
|
@@ -3633,9 +3357,7 @@ async function callBackendExec(args) {
|
|
|
3633
3357
|
nodeVersion: args.nodeVersion,
|
|
3634
3358
|
params: args.params,
|
|
3635
3359
|
inputs: serialized,
|
|
3636
|
-
idempotency_key: idempotencyKey
|
|
3637
|
-
canvas_run_id: args.ctx.canvasRunId,
|
|
3638
|
-
node_id: args.ctx.nodeId
|
|
3360
|
+
idempotency_key: idempotencyKey
|
|
3639
3361
|
},
|
|
3640
3362
|
args.ctx.signal
|
|
3641
3363
|
);
|
|
@@ -3687,9 +3409,6 @@ async function ingestValue(value, ctx, declaredKind) {
|
|
|
3687
3409
|
}
|
|
3688
3410
|
if (isRawAsset(value)) {
|
|
3689
3411
|
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
|
-
}
|
|
3693
3412
|
return ctx.assets.ingestRemote({
|
|
3694
3413
|
kind,
|
|
3695
3414
|
url: value.url,
|
|
@@ -3804,7 +3523,7 @@ function safePathname(rawUrl) {
|
|
|
3804
3523
|
}
|
|
3805
3524
|
var ingestNode = defineNode({
|
|
3806
3525
|
id: "ingest",
|
|
3807
|
-
version: "1.
|
|
3526
|
+
version: "1.1.0",
|
|
3808
3527
|
category: "io",
|
|
3809
3528
|
location: "local",
|
|
3810
3529
|
summary: "Ingest an external URL or a local file into the asset store. Declare the kind you expect (image/video/audio/text/json/font); the node picks the strategy. For source=url: yt-dlp for video/audio (YouTube/TikTok/Vimeo/etc. and direct file URLs), Handinger for HTML/PDF pages \u2192 markdown, direct HTTP fetch for binary URLs (images, fonts) and raw .txt/.md. For source=path: read from the local filesystem and upload to R2.",
|
|
@@ -3847,9 +3566,6 @@ function runStrategy(strategy, params, ctx) {
|
|
|
3847
3566
|
}
|
|
3848
3567
|
}
|
|
3849
3568
|
async function execDirectFetch(params, ctx) {
|
|
3850
|
-
if (params.expect === "image") {
|
|
3851
|
-
return ingestImageUrl(params.url, ctx);
|
|
3852
|
-
}
|
|
3853
3569
|
const result = await callBackendExec({
|
|
3854
3570
|
nodeType: "ingest",
|
|
3855
3571
|
nodeVersion: ingestNode.version,
|
|
@@ -3860,37 +3576,6 @@ async function execDirectFetch(params, ctx) {
|
|
|
3860
3576
|
});
|
|
3861
3577
|
return assertAssetOutput(result, params.expect);
|
|
3862
3578
|
}
|
|
3863
|
-
async function ingestImageUrl(url, ctx) {
|
|
3864
|
-
const res = await fetch(url);
|
|
3865
|
-
if (!res.ok) {
|
|
3866
|
-
throw localExecError(ctx, `fetch ${url} \u2192 ${res.status}`);
|
|
3867
|
-
}
|
|
3868
|
-
const ab = await res.arrayBuffer();
|
|
3869
|
-
if (ab.byteLength > MAX_ASSET_BYTES) {
|
|
3870
|
-
throw localExecError(ctx, `file_too_large: ${url} is ${ab.byteLength} bytes (limit ${MAX_ASSET_BYTES})`);
|
|
3871
|
-
}
|
|
3872
|
-
let normalized;
|
|
3873
|
-
try {
|
|
3874
|
-
normalized = await toModelSafeImage(Buffer.from(ab));
|
|
3875
|
-
} catch (e) {
|
|
3876
|
-
throw localExecError(ctx, `${url}: ${e.message}`);
|
|
3877
|
-
}
|
|
3878
|
-
if (normalized.rasterizedFrom) {
|
|
3879
|
-
ctx.log(`ingest: normalized ${normalized.rasterizedFrom} URL -> PNG (${normalized.bytes.length}B)`);
|
|
3880
|
-
}
|
|
3881
|
-
return uploadAndIngest({
|
|
3882
|
-
bytes: normalized.bytes,
|
|
3883
|
-
kind: "image",
|
|
3884
|
-
mime: normalized.mime,
|
|
3885
|
-
metadata: {
|
|
3886
|
-
source_url: url,
|
|
3887
|
-
strategy: "direct_fetch",
|
|
3888
|
-
ingested_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3889
|
-
...normalized.rasterizedFrom ? { rasterized_from: normalized.rasterizedFrom } : {}
|
|
3890
|
-
},
|
|
3891
|
-
ctx
|
|
3892
|
-
});
|
|
3893
|
-
}
|
|
3894
3579
|
async function execHandinger(params, ctx) {
|
|
3895
3580
|
const result = await callBackendExec({
|
|
3896
3581
|
nodeType: "ingest",
|
|
@@ -3927,15 +3612,7 @@ var EXT_TO_MIME = {
|
|
|
3927
3612
|
jpeg: "image/jpeg",
|
|
3928
3613
|
webp: "image/webp",
|
|
3929
3614
|
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.
|
|
3933
3615
|
avif: "image/avif",
|
|
3934
|
-
heic: "image/heic",
|
|
3935
|
-
heif: "image/heif",
|
|
3936
|
-
tif: "image/tiff",
|
|
3937
|
-
tiff: "image/tiff",
|
|
3938
|
-
bmp: "image/bmp",
|
|
3939
3616
|
mp4: "video/mp4",
|
|
3940
3617
|
webm: "video/webm",
|
|
3941
3618
|
mov: "video/quicktime",
|
|
@@ -3986,45 +3663,15 @@ async function rasterizeSvgToPng(bytes) {
|
|
|
3986
3663
|
}
|
|
3987
3664
|
return await sharp(bytes, { density }).png({ force: true, palette: false }).toBuffer();
|
|
3988
3665
|
}
|
|
3989
|
-
var MODEL_SAFE_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
|
|
3990
|
-
async function toModelSafeImage(bytes) {
|
|
3991
|
-
const safe = sniffImageMime(bytes);
|
|
3992
|
-
if (safe && MODEL_SAFE_IMAGE_MIMES.has(safe)) {
|
|
3993
|
-
return { bytes, mime: safe };
|
|
3994
|
-
}
|
|
3995
|
-
if (sniffSvg(bytes)) {
|
|
3996
|
-
return { bytes: await rasterizeSvgToPng(bytes), mime: "image/png", rasterizedFrom: "svg" };
|
|
3997
|
-
}
|
|
3998
|
-
const { default: sharp } = await import("sharp");
|
|
3999
|
-
try {
|
|
4000
|
-
const img = sharp(bytes);
|
|
4001
|
-
const format = (await img.metadata()).format;
|
|
4002
|
-
const png = await img.png({ force: true }).toBuffer();
|
|
4003
|
-
return { bytes: png, mime: "image/png", rasterizedFrom: format ?? "unknown" };
|
|
4004
|
-
} catch (e) {
|
|
4005
|
-
throw new Error(`bytes are not a decodable image (${e.message})`);
|
|
4006
|
-
}
|
|
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
|
-
}
|
|
4019
3666
|
function sniffImageMime(buf) {
|
|
4020
3667
|
if (buf.length < 4) return null;
|
|
4021
|
-
if (buf[0] === 137 &&
|
|
3668
|
+
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
4022
3669
|
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
4023
|
-
if (
|
|
4024
|
-
if (
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
return
|
|
3670
|
+
if (buf[0] === 71 && buf[1] === 73 && buf[2] === 70) return "image/gif";
|
|
3671
|
+
if (buf.length >= 12 && buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) {
|
|
3672
|
+
return "image/webp";
|
|
3673
|
+
}
|
|
3674
|
+
return null;
|
|
4028
3675
|
}
|
|
4029
3676
|
function findBoxPayload(buf, start, end, type) {
|
|
4030
3677
|
let offset = start;
|
|
@@ -4077,10 +3724,10 @@ function inferKindFromMime(mime) {
|
|
|
4077
3724
|
if (mime.startsWith("font/")) return "font";
|
|
4078
3725
|
return null;
|
|
4079
3726
|
}
|
|
4080
|
-
function localExecError(ctx,
|
|
3727
|
+
function localExecError(ctx, message) {
|
|
4081
3728
|
return new NodeExecutionError(ctx.nodeId, ctx.nodeType, {
|
|
4082
3729
|
kind: "local",
|
|
4083
|
-
cause: new Error(`ingest: ${
|
|
3730
|
+
cause: new Error(`ingest: ${message}`)
|
|
4084
3731
|
});
|
|
4085
3732
|
}
|
|
4086
3733
|
async function execLocalFile(params, ctx) {
|
|
@@ -4127,20 +3774,17 @@ async function execLocalFile(params, ctx) {
|
|
|
4127
3774
|
ctx.log(`ingest: local file ${stats.size}B mime=${mime}`);
|
|
4128
3775
|
let outBytes = bytes;
|
|
4129
3776
|
let outMime = mime;
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
outMime = normalized.mime;
|
|
4135
|
-
rasterizedFrom = normalized.rasterizedFrom;
|
|
4136
|
-
if (rasterizedFrom) ctx.log(`ingest: normalized ${rasterizedFrom} -> PNG (${outBytes.length}B)`);
|
|
3777
|
+
if (mime === SVG_MIME) {
|
|
3778
|
+
outBytes = await rasterizeSvgToPng(bytes);
|
|
3779
|
+
outMime = "image/png";
|
|
3780
|
+
ctx.log(`ingest: rasterized SVG -> PNG (${outBytes.length}B)`);
|
|
4137
3781
|
}
|
|
4138
3782
|
const durationMs = probeVideoDurationMs(params.expect, outBytes, ctx);
|
|
4139
3783
|
const ref = await uploadAndIngest({
|
|
4140
3784
|
bytes: outBytes,
|
|
4141
3785
|
kind: params.expect,
|
|
4142
3786
|
mime: outMime,
|
|
4143
|
-
metadata: localFileMetadata({ absPath, fileSize: stats.size, mime, durationMs
|
|
3787
|
+
metadata: localFileMetadata({ absPath, fileSize: stats.size, mime, durationMs }),
|
|
4144
3788
|
ctx
|
|
4145
3789
|
});
|
|
4146
3790
|
return withProbedDuration(ref, durationMs);
|
|
@@ -4158,7 +3802,7 @@ function localFileMetadata(args) {
|
|
|
4158
3802
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4159
3803
|
file_size: args.fileSize,
|
|
4160
3804
|
original_filename: path3.basename(args.absPath),
|
|
4161
|
-
...args.
|
|
3805
|
+
...args.mime === SVG_MIME ? { rasterized_from: "svg" } : {},
|
|
4162
3806
|
...args.durationMs !== void 0 ? { duration_ms: args.durationMs } : {}
|
|
4163
3807
|
};
|
|
4164
3808
|
}
|
|
@@ -4685,56 +4329,19 @@ var audioTimelineNode = defineNode({
|
|
|
4685
4329
|
}
|
|
4686
4330
|
});
|
|
4687
4331
|
|
|
4688
|
-
// src/engine/nodes/local/collect.ts
|
|
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
4332
|
// src/engine/nodes/local/ffmpeg.ts
|
|
4726
|
-
import { z as
|
|
4333
|
+
import { z as z7 } from "zod";
|
|
4727
4334
|
var FFMPEG_BIN2 = "ffmpeg";
|
|
4728
|
-
var OutputDecl =
|
|
4729
|
-
kind:
|
|
4730
|
-
ext:
|
|
4335
|
+
var OutputDecl = z7.object({
|
|
4336
|
+
kind: z7.enum(["image", "video", "audio"]),
|
|
4337
|
+
ext: z7.string().min(1).max(8)
|
|
4731
4338
|
}).strict();
|
|
4732
|
-
var FfmpegParams =
|
|
4733
|
-
args:
|
|
4734
|
-
outputs:
|
|
4339
|
+
var FfmpegParams = z7.object({
|
|
4340
|
+
args: z7.array(z7.string()).min(1),
|
|
4341
|
+
outputs: z7.record(z7.string(), OutputDecl).default({})
|
|
4735
4342
|
}).strict();
|
|
4736
|
-
var FfmpegInputs =
|
|
4737
|
-
var FfmpegOutputs =
|
|
4343
|
+
var FfmpegInputs = z7.record(z7.string(), z7.unknown());
|
|
4344
|
+
var FfmpegOutputs = z7.record(z7.string(), z7.custom());
|
|
4738
4345
|
var ffmpegNode = defineNode({
|
|
4739
4346
|
id: "ffmpeg",
|
|
4740
4347
|
version: "2.0.0",
|
|
@@ -4765,7 +4372,7 @@ import { mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/prom
|
|
|
4765
4372
|
import { createRequire } from "module";
|
|
4766
4373
|
import { tmpdir as tmpdir3 } from "os";
|
|
4767
4374
|
import path6 from "path";
|
|
4768
|
-
import { z as
|
|
4375
|
+
import { z as z8 } from "zod";
|
|
4769
4376
|
|
|
4770
4377
|
// src/engine/nodes/local/lib/assets.ts
|
|
4771
4378
|
import { copyFile as copyFile3, readFile as readFile4 } from "fs/promises";
|
|
@@ -4787,7 +4394,7 @@ async function refToUrl(ref) {
|
|
|
4787
4394
|
return `data:${ref.mime};base64,${bytes.toString("base64")}`;
|
|
4788
4395
|
}
|
|
4789
4396
|
var ASSET_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "json", "text", "font"]);
|
|
4790
|
-
function
|
|
4397
|
+
function isAssetRefLike(value) {
|
|
4791
4398
|
if (!value || typeof value !== "object") return false;
|
|
4792
4399
|
const v = value;
|
|
4793
4400
|
return typeof v.kind === "string" && ASSET_KINDS.has(v.kind) && typeof v.mime === "string" && typeof v.sha256 === "string" && (typeof v.url === "string" || typeof v.path === "string");
|
|
@@ -4801,15 +4408,15 @@ var DEFAULT_SPECIMEN = [
|
|
|
4801
4408
|
"abcdefghijklmnopqrstuvwxyz",
|
|
4802
4409
|
`0123456789 !?&@#$%().,:;'"-`
|
|
4803
4410
|
].join("\n");
|
|
4804
|
-
var FontSpecimenParams =
|
|
4805
|
-
text:
|
|
4806
|
-
font_size:
|
|
4807
|
-
padding:
|
|
4808
|
-
line_height:
|
|
4809
|
-
max_width:
|
|
4411
|
+
var FontSpecimenParams = z8.object({
|
|
4412
|
+
text: z8.string().min(1).max(2e3).optional().default(DEFAULT_SPECIMEN),
|
|
4413
|
+
font_size: z8.number().int().min(8).max(512).optional().default(72),
|
|
4414
|
+
padding: z8.number().int().min(0).max(512).optional().default(64),
|
|
4415
|
+
line_height: z8.number().min(0.8).max(3).optional().default(1.35),
|
|
4416
|
+
max_width: z8.number().int().min(256).max(4096).optional()
|
|
4810
4417
|
}).strict();
|
|
4811
|
-
var FontSpecimenInputs =
|
|
4812
|
-
var FontSpecimenOutputs =
|
|
4418
|
+
var FontSpecimenInputs = z8.object({ font: FontRef }).loose();
|
|
4419
|
+
var FontSpecimenOutputs = z8.object({ image: ImageRef }).strict();
|
|
4813
4420
|
var DEVICE_SCALE_FACTOR = 2;
|
|
4814
4421
|
var PAGE_TIMEOUT_MS = 3e4;
|
|
4815
4422
|
function escapeHtml(text) {
|
|
@@ -4950,7 +4557,7 @@ import { createRequire as createRequire2 } from "module";
|
|
|
4950
4557
|
import { cpus, tmpdir as tmpdir4 } from "os";
|
|
4951
4558
|
import path11 from "path";
|
|
4952
4559
|
import { promisify as promisify4 } from "util";
|
|
4953
|
-
import { z as
|
|
4560
|
+
import { z as z10 } from "zod";
|
|
4954
4561
|
|
|
4955
4562
|
// src/engine/engine/composition-hash.ts
|
|
4956
4563
|
import { readdir as readdir2, readFile as readFile5, stat as stat4 } from "fs/promises";
|
|
@@ -4988,62 +4595,62 @@ async function collectFiles(root, current) {
|
|
|
4988
4595
|
// src/engine/engine/composition-meta.ts
|
|
4989
4596
|
import { readFile as readFile6 } from "fs/promises";
|
|
4990
4597
|
import path8 from "path";
|
|
4991
|
-
import { z as
|
|
4992
|
-
var InputKind =
|
|
4993
|
-
var InputSpec =
|
|
4598
|
+
import { z as z9 } from "zod";
|
|
4599
|
+
var InputKind = z9.enum(["video", "image", "audio", "json"]);
|
|
4600
|
+
var InputSpec = z9.object({
|
|
4994
4601
|
kind: InputKind,
|
|
4995
|
-
required:
|
|
4602
|
+
required: z9.boolean().optional().default(false),
|
|
4996
4603
|
// Filename the composition's HTML references (e.g. `input.mp4`, `logo.png`).
|
|
4997
4604
|
// Defaults to `<key><ext>` derived from the kind.
|
|
4998
|
-
staged_as:
|
|
4999
|
-
description:
|
|
4605
|
+
staged_as: z9.string().min(1).optional(),
|
|
4606
|
+
description: z9.string().optional()
|
|
5000
4607
|
}).strict();
|
|
5001
4608
|
var ParamSpecBase = {
|
|
5002
|
-
required:
|
|
5003
|
-
description:
|
|
4609
|
+
required: z9.boolean().optional().default(false),
|
|
4610
|
+
description: z9.string().optional()
|
|
5004
4611
|
};
|
|
5005
|
-
var StringParam =
|
|
4612
|
+
var StringParam = z9.object({
|
|
5006
4613
|
...ParamSpecBase,
|
|
5007
|
-
kind:
|
|
5008
|
-
default:
|
|
5009
|
-
enum:
|
|
4614
|
+
kind: z9.literal("string"),
|
|
4615
|
+
default: z9.string().optional(),
|
|
4616
|
+
enum: z9.array(z9.string()).optional()
|
|
5010
4617
|
}).strict();
|
|
5011
|
-
var IntegerParam =
|
|
4618
|
+
var IntegerParam = z9.object({
|
|
5012
4619
|
...ParamSpecBase,
|
|
5013
|
-
kind:
|
|
5014
|
-
default:
|
|
5015
|
-
min:
|
|
5016
|
-
max:
|
|
4620
|
+
kind: z9.literal("integer"),
|
|
4621
|
+
default: z9.number().int().optional(),
|
|
4622
|
+
min: z9.number().int().optional(),
|
|
4623
|
+
max: z9.number().int().optional()
|
|
5017
4624
|
}).strict();
|
|
5018
|
-
var NumberParam =
|
|
4625
|
+
var NumberParam = z9.object({
|
|
5019
4626
|
...ParamSpecBase,
|
|
5020
|
-
kind:
|
|
5021
|
-
default:
|
|
5022
|
-
min:
|
|
5023
|
-
max:
|
|
4627
|
+
kind: z9.literal("number"),
|
|
4628
|
+
default: z9.number().optional(),
|
|
4629
|
+
min: z9.number().optional(),
|
|
4630
|
+
max: z9.number().optional()
|
|
5024
4631
|
}).strict();
|
|
5025
|
-
var BooleanParam =
|
|
4632
|
+
var BooleanParam = z9.object({
|
|
5026
4633
|
...ParamSpecBase,
|
|
5027
|
-
kind:
|
|
5028
|
-
default:
|
|
4634
|
+
kind: z9.literal("boolean"),
|
|
4635
|
+
default: z9.boolean().optional()
|
|
5029
4636
|
}).strict();
|
|
5030
|
-
var ColorParam =
|
|
4637
|
+
var ColorParam = z9.object({
|
|
5031
4638
|
...ParamSpecBase,
|
|
5032
|
-
kind:
|
|
5033
|
-
default:
|
|
4639
|
+
kind: z9.literal("color"),
|
|
4640
|
+
default: z9.string().optional()
|
|
5034
4641
|
}).strict();
|
|
5035
|
-
var ImageParam =
|
|
4642
|
+
var ImageParam = z9.object({
|
|
5036
4643
|
...ParamSpecBase,
|
|
5037
|
-
kind:
|
|
5038
|
-
default:
|
|
4644
|
+
kind: z9.literal("image"),
|
|
4645
|
+
default: z9.string().optional()
|
|
5039
4646
|
}).strict();
|
|
5040
|
-
var JsonParam =
|
|
4647
|
+
var JsonParam = z9.object({
|
|
5041
4648
|
...ParamSpecBase,
|
|
5042
|
-
kind:
|
|
5043
|
-
schema:
|
|
5044
|
-
default:
|
|
4649
|
+
kind: z9.literal("json"),
|
|
4650
|
+
schema: z9.unknown().optional(),
|
|
4651
|
+
default: z9.unknown().optional()
|
|
5045
4652
|
}).strict();
|
|
5046
|
-
var ParamSpec =
|
|
4653
|
+
var ParamSpec = z9.discriminatedUnion("kind", [
|
|
5047
4654
|
StringParam,
|
|
5048
4655
|
IntegerParam,
|
|
5049
4656
|
NumberParam,
|
|
@@ -5052,16 +4659,16 @@ var ParamSpec = z10.discriminatedUnion("kind", [
|
|
|
5052
4659
|
ImageParam,
|
|
5053
4660
|
JsonParam
|
|
5054
4661
|
]);
|
|
5055
|
-
var CompositionMetaSchema =
|
|
5056
|
-
id:
|
|
5057
|
-
title:
|
|
5058
|
-
description:
|
|
5059
|
-
width:
|
|
5060
|
-
height:
|
|
5061
|
-
fps:
|
|
5062
|
-
default_duration:
|
|
5063
|
-
inputs:
|
|
5064
|
-
params:
|
|
4662
|
+
var CompositionMetaSchema = z9.object({
|
|
4663
|
+
id: z9.string().min(1),
|
|
4664
|
+
title: z9.string().min(1),
|
|
4665
|
+
description: z9.string().optional(),
|
|
4666
|
+
width: z9.number().int().positive(),
|
|
4667
|
+
height: z9.number().int().positive(),
|
|
4668
|
+
fps: z9.number().int().positive().default(30),
|
|
4669
|
+
default_duration: z9.number().positive().default(10),
|
|
4670
|
+
inputs: z9.record(z9.string(), InputSpec).default({}),
|
|
4671
|
+
params: z9.record(z9.string(), ParamSpec).default({})
|
|
5065
4672
|
}).strict();
|
|
5066
4673
|
async function loadCompositionMeta(compositionDir) {
|
|
5067
4674
|
const metaPath = path8.join(compositionDir, "meta.json");
|
|
@@ -5089,39 +4696,39 @@ function buildParamsSchema(meta) {
|
|
|
5089
4696
|
for (const [name, spec] of Object.entries(meta.params)) {
|
|
5090
4697
|
shape[name] = buildParamFieldSchema(name, spec);
|
|
5091
4698
|
}
|
|
5092
|
-
return
|
|
4699
|
+
return z9.object(shape).strict();
|
|
5093
4700
|
}
|
|
5094
4701
|
function buildParamFieldSchema(name, spec) {
|
|
5095
4702
|
switch (spec.kind) {
|
|
5096
4703
|
case "string": {
|
|
5097
|
-
const s = spec.enum && spec.enum.length > 0 ?
|
|
4704
|
+
const s = spec.enum && spec.enum.length > 0 ? z9.enum(spec.enum) : z9.string();
|
|
5098
4705
|
return finalize(s, spec.default, spec.required);
|
|
5099
4706
|
}
|
|
5100
4707
|
case "integer": {
|
|
5101
|
-
let s =
|
|
4708
|
+
let s = z9.number().int();
|
|
5102
4709
|
if (spec.min !== void 0) s = s.min(spec.min);
|
|
5103
4710
|
if (spec.max !== void 0) s = s.max(spec.max);
|
|
5104
4711
|
return finalize(s, spec.default, spec.required);
|
|
5105
4712
|
}
|
|
5106
4713
|
case "number": {
|
|
5107
|
-
let s =
|
|
4714
|
+
let s = z9.number();
|
|
5108
4715
|
if (spec.min !== void 0) s = s.min(spec.min);
|
|
5109
4716
|
if (spec.max !== void 0) s = s.max(spec.max);
|
|
5110
4717
|
return finalize(s, spec.default, spec.required);
|
|
5111
4718
|
}
|
|
5112
4719
|
case "boolean":
|
|
5113
|
-
return finalize(
|
|
4720
|
+
return finalize(z9.boolean(), spec.default, spec.required);
|
|
5114
4721
|
case "color": {
|
|
5115
|
-
const s =
|
|
4722
|
+
const s = z9.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
|
|
5116
4723
|
message: `param "${name}": must be a 3/6/8-digit hex color (e.g. "#ff0066")`
|
|
5117
4724
|
});
|
|
5118
4725
|
return finalize(s, spec.default, spec.required);
|
|
5119
4726
|
}
|
|
5120
4727
|
case "image":
|
|
5121
|
-
return finalize(
|
|
4728
|
+
return finalize(z9.union([z9.string().min(1), z9.record(z9.string(), z9.unknown())]), spec.default, spec.required);
|
|
5122
4729
|
case "json":
|
|
5123
4730
|
return finalize(
|
|
5124
|
-
|
|
4731
|
+
z9.unknown().refine((v) => v !== void 0, { message: `param "${name}" is required` }),
|
|
5125
4732
|
spec.default,
|
|
5126
4733
|
spec.required
|
|
5127
4734
|
);
|
|
@@ -5159,8 +4766,8 @@ var NEVER_BLOCK = [
|
|
|
5159
4766
|
/text[_-]?occluded/i
|
|
5160
4767
|
];
|
|
5161
4768
|
var UNAVAILABLE = /unknown command|command not found|not found|Did you mean|Unknown argument|ENOENT/i;
|
|
5162
|
-
function isAdvisory(code,
|
|
5163
|
-
const hay = `${code} ${
|
|
4769
|
+
function isAdvisory(code, message) {
|
|
4770
|
+
const hay = `${code} ${message}`;
|
|
5164
4771
|
return NEVER_BLOCK.some((re) => re.test(hay));
|
|
5165
4772
|
}
|
|
5166
4773
|
function parseCheckJson(raw) {
|
|
@@ -5188,10 +4795,10 @@ function classifyLint(json) {
|
|
|
5188
4795
|
for (const f of findings) {
|
|
5189
4796
|
const rec = f;
|
|
5190
4797
|
const code = String(rec?.code ?? "");
|
|
5191
|
-
const
|
|
4798
|
+
const message = String(rec?.message ?? "");
|
|
5192
4799
|
const severity = String(rec?.severity ?? "info");
|
|
5193
|
-
const blocking = severity === "error" && !isAdvisory(code,
|
|
5194
|
-
out.push({ source: "lint", code, message
|
|
4800
|
+
const blocking = severity === "error" && !isAdvisory(code, message);
|
|
4801
|
+
out.push({ source: "lint", code, message, severity: blocking ? "blocking" : "warning" });
|
|
5195
4802
|
}
|
|
5196
4803
|
return out;
|
|
5197
4804
|
}
|
|
@@ -5203,9 +4810,9 @@ function classifyInspect(json) {
|
|
|
5203
4810
|
for (const iss of issues) {
|
|
5204
4811
|
const rec = iss;
|
|
5205
4812
|
const code = String(rec?.code ?? rec?.type ?? "overflow");
|
|
5206
|
-
const
|
|
4813
|
+
const message = String(rec?.message ?? rec?.detail ?? JSON.stringify(iss));
|
|
5207
4814
|
const severity = rec?.severity ? String(rec.severity) : obj?.ok === false ? "error" : "warning";
|
|
5208
|
-
out.push({ source: "inspect", code, message
|
|
4815
|
+
out.push({ source: "inspect", code, message, severity: severity === "error" ? "blocking" : "warning" });
|
|
5209
4816
|
}
|
|
5210
4817
|
return out;
|
|
5211
4818
|
}
|
|
@@ -5421,17 +5028,17 @@ function literalize(value) {
|
|
|
5421
5028
|
// src/engine/nodes/local/hyperframe.ts
|
|
5422
5029
|
var execFileAsync2 = promisify4(execFile4);
|
|
5423
5030
|
var require_2 = createRequire2(import.meta.url);
|
|
5424
|
-
var HyperframeParams =
|
|
5425
|
-
composition:
|
|
5031
|
+
var HyperframeParams = z10.object({
|
|
5032
|
+
composition: z10.string().min(1),
|
|
5426
5033
|
// Output container. mp4 (default) for delivery; webm/mov render WITH
|
|
5427
5034
|
// transparency (alpha) when the composition background is transparent —
|
|
5428
5035
|
// use for motion-graphic overlays dropped into Premiere/AE/Nuke.
|
|
5429
|
-
format:
|
|
5430
|
-
timeout_ms:
|
|
5431
|
-
}).catchall(
|
|
5432
|
-
var HyperframeInputs =
|
|
5433
|
-
var HyperframeOutputs =
|
|
5434
|
-
video:
|
|
5036
|
+
format: z10.enum(["mp4", "webm", "mov"]).optional().default("mp4"),
|
|
5037
|
+
timeout_ms: z10.number().int().positive().optional().default(10 * 60 * 1e3)
|
|
5038
|
+
}).catchall(z10.unknown());
|
|
5039
|
+
var HyperframeInputs = z10.record(z10.string(), z10.custom()).optional().default({});
|
|
5040
|
+
var HyperframeOutputs = z10.object({
|
|
5041
|
+
video: z10.custom()
|
|
5435
5042
|
}).strict();
|
|
5436
5043
|
var NODE_OWNED_PARAM_KEYS = /* @__PURE__ */ new Set(["composition", "format", "timeout_ms"]);
|
|
5437
5044
|
var MIME_BY_FORMAT = {
|
|
@@ -5650,7 +5257,7 @@ async function buildSubstitutionValues(compositionParams, meta, duration) {
|
|
|
5650
5257
|
}
|
|
5651
5258
|
function coerceImageParam(value) {
|
|
5652
5259
|
if (typeof value === "string") return Promise.resolve(value);
|
|
5653
|
-
if (
|
|
5260
|
+
if (isAssetRefLike(value)) return refToUrl(value);
|
|
5654
5261
|
throw new Error("hyperframe_render: image param must be a URL string or AssetRef");
|
|
5655
5262
|
}
|
|
5656
5263
|
async function substituteCompositionFiles(tmp, values) {
|
|
@@ -5720,23 +5327,23 @@ import { createRequire as createRequire3 } from "module";
|
|
|
5720
5327
|
import { tmpdir as tmpdir5 } from "os";
|
|
5721
5328
|
import path12 from "path";
|
|
5722
5329
|
import { promisify as promisify5 } from "util";
|
|
5723
|
-
import { z as
|
|
5330
|
+
import { z as z11 } from "zod";
|
|
5724
5331
|
var _execFileAsync = promisify5(execFile5);
|
|
5725
5332
|
var require_3 = createRequire3(import.meta.url);
|
|
5726
|
-
var WaitForSpec =
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5333
|
+
var WaitForSpec = z11.discriminatedUnion("kind", [
|
|
5334
|
+
z11.object({ kind: z11.literal("auto") }),
|
|
5335
|
+
z11.object({ kind: z11.literal("selector"), value: z11.string().min(1) }),
|
|
5336
|
+
z11.object({ kind: z11.literal("function"), value: z11.string().min(1) }),
|
|
5337
|
+
z11.object({ kind: z11.literal("timeout"), ms: z11.number().int().min(0).max(6e4) })
|
|
5731
5338
|
]);
|
|
5732
|
-
var HyperframeSnapshotParams =
|
|
5733
|
-
composition:
|
|
5339
|
+
var HyperframeSnapshotParams = z11.object({
|
|
5340
|
+
composition: z11.string().min(1),
|
|
5734
5341
|
wait_for: WaitForSpec.optional().default({ kind: "auto" }),
|
|
5735
|
-
timeout_ms:
|
|
5736
|
-
}).catchall(
|
|
5737
|
-
var HyperframeSnapshotInputs =
|
|
5738
|
-
var HyperframeSnapshotOutputs =
|
|
5739
|
-
image:
|
|
5342
|
+
timeout_ms: z11.number().int().positive().optional().default(6e4)
|
|
5343
|
+
}).catchall(z11.unknown());
|
|
5344
|
+
var HyperframeSnapshotInputs = z11.record(z11.string(), z11.custom()).optional().default({});
|
|
5345
|
+
var HyperframeSnapshotOutputs = z11.object({
|
|
5346
|
+
image: z11.custom()
|
|
5740
5347
|
}).strict();
|
|
5741
5348
|
var NODE_OWNED_PARAM_KEYS2 = /* @__PURE__ */ new Set(["composition", "wait_for", "timeout_ms"]);
|
|
5742
5349
|
var DEVICE_SCALE_FACTOR2 = 2;
|
|
@@ -5880,7 +5487,7 @@ async function buildSubstitutionValues2(compositionParams, meta) {
|
|
|
5880
5487
|
}
|
|
5881
5488
|
function coerceImageParam2(value) {
|
|
5882
5489
|
if (typeof value === "string") return Promise.resolve(value);
|
|
5883
|
-
if (
|
|
5490
|
+
if (isAssetRefLike(value)) return refToUrl(value);
|
|
5884
5491
|
throw new Error("hyperframe_snapshot: image param must be a URL string or AssetRef");
|
|
5885
5492
|
}
|
|
5886
5493
|
async function substituteCompositionFiles2(tmp, values) {
|
|
@@ -5928,18 +5535,18 @@ async function waitForReady(page, waitFor, timeoutMs) {
|
|
|
5928
5535
|
// src/engine/nodes/local/imagemagick.ts
|
|
5929
5536
|
import { execFile as execFile6 } from "child_process";
|
|
5930
5537
|
import { promisify as promisify6 } from "util";
|
|
5931
|
-
import { z as
|
|
5538
|
+
import { z as z12 } from "zod";
|
|
5932
5539
|
var execFileAsync3 = promisify6(execFile6);
|
|
5933
|
-
var OutputDecl2 =
|
|
5934
|
-
kind:
|
|
5935
|
-
ext:
|
|
5540
|
+
var OutputDecl2 = z12.object({
|
|
5541
|
+
kind: z12.enum(["image", "video", "audio"]),
|
|
5542
|
+
ext: z12.string().min(1).max(8)
|
|
5936
5543
|
}).strict();
|
|
5937
|
-
var ImageMagickParams =
|
|
5938
|
-
args:
|
|
5939
|
-
outputs:
|
|
5544
|
+
var ImageMagickParams = z12.object({
|
|
5545
|
+
args: z12.array(z12.string()).min(1),
|
|
5546
|
+
outputs: z12.record(z12.string(), OutputDecl2).default({})
|
|
5940
5547
|
}).strict();
|
|
5941
|
-
var ImageMagickInputs =
|
|
5942
|
-
var ImageMagickOutputs =
|
|
5548
|
+
var ImageMagickInputs = z12.record(z12.string(), z12.unknown());
|
|
5549
|
+
var ImageMagickOutputs = z12.record(z12.string(), z12.custom());
|
|
5943
5550
|
var resolvedBin;
|
|
5944
5551
|
async function resolveBin() {
|
|
5945
5552
|
if (resolvedBin) return resolvedBin;
|
|
@@ -5981,29 +5588,29 @@ var imagemagickNode = defineNode({
|
|
|
5981
5588
|
});
|
|
5982
5589
|
|
|
5983
5590
|
// src/engine/nodes/local/text.ts
|
|
5984
|
-
import { z as
|
|
5591
|
+
import { z as z13 } from "zod";
|
|
5985
5592
|
var textNode = defineNode({
|
|
5986
5593
|
id: "text",
|
|
5987
5594
|
version: "1.0.0",
|
|
5988
5595
|
category: "data",
|
|
5989
5596
|
location: "local",
|
|
5990
5597
|
summary: "A literal text value. Use for prompts, descriptions, copy.",
|
|
5991
|
-
inputs:
|
|
5992
|
-
params:
|
|
5993
|
-
outputs:
|
|
5598
|
+
inputs: z13.object({}).strict(),
|
|
5599
|
+
params: z13.object({ value: z13.string() }).strict(),
|
|
5600
|
+
outputs: z13.object({ text: z13.string() }).strict(),
|
|
5994
5601
|
cost: () => ({ credits: 0, seconds_estimate: 0 }),
|
|
5995
5602
|
execute: ({ params }) => Promise.resolve({ text: params.value })
|
|
5996
5603
|
});
|
|
5997
5604
|
|
|
5998
5605
|
// src/engine/nodes/remote/audioVoiceConvert.ts
|
|
5999
|
-
import { z as
|
|
6000
|
-
var AudioVoiceConvertParams =
|
|
6001
|
-
model:
|
|
5606
|
+
import { z as z14 } from "zod";
|
|
5607
|
+
var AudioVoiceConvertParams = z14.object({
|
|
5608
|
+
model: z14.literal("elevenlabs/eleven_multilingual_sts_v2"),
|
|
6002
5609
|
/** Target voice id. Splice an upstream `voice_select` via `"{{voice_ref}}"`. */
|
|
6003
|
-
voice:
|
|
6004
|
-
output_format:
|
|
5610
|
+
voice: z14.string().min(1),
|
|
5611
|
+
output_format: z14.string().optional(),
|
|
6005
5612
|
/** Strip the source clip's background noise before re-voicing. */
|
|
6006
|
-
remove_background_noise:
|
|
5613
|
+
remove_background_noise: z14.boolean().optional()
|
|
6007
5614
|
}).strict();
|
|
6008
5615
|
var audioVoiceConvertNode = delegated({
|
|
6009
5616
|
id: "audio_voice_convert",
|
|
@@ -6011,44 +5618,44 @@ var audioVoiceConvertNode = delegated({
|
|
|
6011
5618
|
category: "audio",
|
|
6012
5619
|
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.",
|
|
6013
5620
|
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}}"`.',
|
|
6014
|
-
inputs:
|
|
5621
|
+
inputs: z14.object({
|
|
6015
5622
|
audio: AudioRef,
|
|
6016
5623
|
voice_ref: TextRef.optional()
|
|
6017
5624
|
}).strict(),
|
|
6018
5625
|
params: AudioVoiceConvertParams,
|
|
6019
|
-
outputs:
|
|
5626
|
+
outputs: z14.object({ audio: AudioRef }).strict(),
|
|
6020
5627
|
outputKinds: { audio: "audio" },
|
|
6021
5628
|
cost: () => ({ credits: 1, seconds_estimate: 20 })
|
|
6022
5629
|
});
|
|
6023
5630
|
|
|
6024
5631
|
// src/engine/nodes/remote/dialogue.ts
|
|
6025
|
-
import { z as
|
|
6026
|
-
var DialogueInput =
|
|
6027
|
-
text:
|
|
6028
|
-
voice_id:
|
|
5632
|
+
import { z as z15 } from "zod";
|
|
5633
|
+
var DialogueInput = z15.object({
|
|
5634
|
+
text: z15.string().min(1),
|
|
5635
|
+
voice_id: z15.string().min(1)
|
|
6029
5636
|
});
|
|
6030
5637
|
var DIALOGUE_MODELS = ["elevenlabs/eleven_v3"];
|
|
6031
|
-
var DialogueParams =
|
|
6032
|
-
model:
|
|
5638
|
+
var DialogueParams = z15.object({
|
|
5639
|
+
model: z15.enum(DIALOGUE_MODELS),
|
|
6033
5640
|
/**
|
|
6034
5641
|
* Ordered list of lines, each tagged with the voice that should speak it.
|
|
6035
5642
|
* Up to 10 unique voice_ids; total text across all lines should stay under
|
|
6036
5643
|
* ~2000 characters for best quality (ElevenLabs guidance).
|
|
6037
5644
|
*/
|
|
6038
|
-
inputs:
|
|
6039
|
-
language_code:
|
|
5645
|
+
inputs: z15.array(DialogueInput).min(1).max(50),
|
|
5646
|
+
language_code: z15.string().optional(),
|
|
6040
5647
|
/** ElevenLabs voice/model settings passthrough (e.g. `{ stability: 0.5 }`). */
|
|
6041
|
-
settings:
|
|
6042
|
-
seed:
|
|
6043
|
-
apply_text_normalization:
|
|
5648
|
+
settings: z15.record(z15.string(), z15.unknown()).optional(),
|
|
5649
|
+
seed: z15.number().int().min(0).max(4294967295).optional(),
|
|
5650
|
+
apply_text_normalization: z15.enum(["auto", "on", "off"]).optional(),
|
|
6044
5651
|
/**
|
|
6045
5652
|
* When true, hits `/v1/text-to-dialogue/with-timestamps` and emits a
|
|
6046
5653
|
* separate `timestamps` output — character-level alignment plus
|
|
6047
5654
|
* per-voice segment markers usable for captions, lipsync, or
|
|
6048
5655
|
* beat-matched cuts in ad creatives.
|
|
6049
5656
|
*/
|
|
6050
|
-
with_timestamps:
|
|
6051
|
-
output_format:
|
|
5657
|
+
with_timestamps: z15.boolean().optional(),
|
|
5658
|
+
output_format: z15.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
6052
5659
|
}).strict().refine((p) => p.inputs.reduce((sum, line) => sum + line.text.length, 0) <= ELEVENLABS_MAX_TEXT_CHARS, {
|
|
6053
5660
|
message: `total dialogue text exceeds ${ELEVENLABS_MAX_TEXT_CHARS} characters`,
|
|
6054
5661
|
path: ["inputs"]
|
|
@@ -6059,9 +5666,9 @@ var dialogueNode = delegated({
|
|
|
6059
5666
|
category: "audio",
|
|
6060
5667
|
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.",
|
|
6061
5668
|
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.",
|
|
6062
|
-
inputs:
|
|
5669
|
+
inputs: z15.object({}).loose(),
|
|
6063
5670
|
params: DialogueParams,
|
|
6064
|
-
outputs:
|
|
5671
|
+
outputs: z15.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
6065
5672
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
6066
5673
|
cost: ({ params }) => {
|
|
6067
5674
|
const chars = params.inputs.reduce((sum, line) => sum + line.text.length, 0);
|
|
@@ -6070,7 +5677,7 @@ var dialogueNode = delegated({
|
|
|
6070
5677
|
});
|
|
6071
5678
|
|
|
6072
5679
|
// src/engine/nodes/remote/image.ts
|
|
6073
|
-
import { z as
|
|
5680
|
+
import { z as z16 } from "zod";
|
|
6074
5681
|
var IMAGE_GENERATE_MODELS2 = [
|
|
6075
5682
|
"openai/gpt-5.4-image-2",
|
|
6076
5683
|
"google/gemini-3.5-flash",
|
|
@@ -6078,16 +5685,16 @@ var IMAGE_GENERATE_MODELS2 = [
|
|
|
6078
5685
|
"google/gemini-3-pro-image-preview",
|
|
6079
5686
|
"recraft/recraft-v4.1-pro-vector"
|
|
6080
5687
|
];
|
|
6081
|
-
var ImageGenerateParams =
|
|
6082
|
-
model:
|
|
6083
|
-
prompt:
|
|
6084
|
-
aspect_ratio:
|
|
6085
|
-
image_size:
|
|
5688
|
+
var ImageGenerateParams = z16.object({
|
|
5689
|
+
model: z16.enum(IMAGE_GENERATE_MODELS2),
|
|
5690
|
+
prompt: z16.string().min(1),
|
|
5691
|
+
aspect_ratio: z16.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(),
|
|
5692
|
+
image_size: z16.enum(["0.5K", "1K", "2K", "4K"]).optional(),
|
|
6086
5693
|
// Recraft v4 vector controls — forwarded into `image_config`. Registry
|
|
6087
5694
|
// rejects them on non-Recraft models.
|
|
6088
|
-
strength:
|
|
6089
|
-
rgb_colors:
|
|
6090
|
-
background_rgb_color:
|
|
5695
|
+
strength: z16.number().min(0).max(1).optional(),
|
|
5696
|
+
rgb_colors: z16.array(z16.array(z16.number().int().min(0).max(255))).optional(),
|
|
5697
|
+
background_rgb_color: z16.array(z16.number().int().min(0).max(255)).optional()
|
|
6091
5698
|
}).strict();
|
|
6092
5699
|
var imageGenerateNode = delegated({
|
|
6093
5700
|
id: "image_generate",
|
|
@@ -6097,22 +5704,22 @@ var imageGenerateNode = delegated({
|
|
|
6097
5704
|
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.",
|
|
6098
5705
|
// `reference` is one image or an ordered array of images. The backend forwards
|
|
6099
5706
|
// each as a separate `image_url` to the provider (OpenRouter accepts many).
|
|
6100
|
-
inputs:
|
|
5707
|
+
inputs: z16.object({ reference: z16.union([ImageRef, z16.array(ImageRef).min(1)]).optional() }).loose(),
|
|
6101
5708
|
params: ImageGenerateParams,
|
|
6102
|
-
outputs:
|
|
5709
|
+
outputs: z16.object({ images: z16.array(ImageRef).min(1) }).strict(),
|
|
6103
5710
|
outputKinds: { images: "image" },
|
|
6104
5711
|
cost: () => ({ credits: 5, seconds_estimate: 10 })
|
|
6105
5712
|
});
|
|
6106
5713
|
|
|
6107
5714
|
// src/engine/nodes/remote/imageAspectAdapt.ts
|
|
6108
|
-
import { z as
|
|
5715
|
+
import { z as z17 } from "zod";
|
|
6109
5716
|
var ASPECT_ADAPT_MODELS = ["google/gemini-3-pro-image-preview", "google/gemini-3.1-flash-image-preview"];
|
|
6110
5717
|
var ASPECT_ADAPT_FORMATS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
|
|
6111
|
-
var ImageAspectAdaptParams =
|
|
6112
|
-
model:
|
|
6113
|
-
formats:
|
|
6114
|
-
guidance:
|
|
6115
|
-
image_size:
|
|
5718
|
+
var ImageAspectAdaptParams = z17.object({
|
|
5719
|
+
model: z17.enum(ASPECT_ADAPT_MODELS),
|
|
5720
|
+
formats: z17.array(z17.enum(ASPECT_ADAPT_FORMATS)).min(1).max(6).refine((formats) => new Set(formats).size === formats.length, { message: "formats must be unique" }),
|
|
5721
|
+
guidance: z17.string().min(1).optional(),
|
|
5722
|
+
image_size: z17.enum(["0.5K", "1K", "2K", "4K"]).optional()
|
|
6116
5723
|
}).strict();
|
|
6117
5724
|
var imageAspectAdaptNode = delegated({
|
|
6118
5725
|
id: "image_aspect_adapt",
|
|
@@ -6120,9 +5727,9 @@ var imageAspectAdaptNode = delegated({
|
|
|
6120
5727
|
category: "image",
|
|
6121
5728
|
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`.",
|
|
6122
5729
|
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.",
|
|
6123
|
-
inputs:
|
|
5730
|
+
inputs: z17.object({ source: ImageRef }).loose(),
|
|
6124
5731
|
params: ImageAspectAdaptParams,
|
|
6125
|
-
outputs:
|
|
5732
|
+
outputs: z17.object({ images: z17.array(ImageRef).min(1) }).strict(),
|
|
6126
5733
|
outputKinds: { images: "image" },
|
|
6127
5734
|
cost: ({ params }) => {
|
|
6128
5735
|
const p = params;
|
|
@@ -6135,12 +5742,12 @@ var imageAspectAdaptNode = delegated({
|
|
|
6135
5742
|
});
|
|
6136
5743
|
|
|
6137
5744
|
// src/engine/nodes/remote/imageBackgroundRemove.ts
|
|
6138
|
-
import { z as
|
|
6139
|
-
var ImageBackgroundRemoveParams =
|
|
6140
|
-
model:
|
|
6141
|
-
model_variant:
|
|
6142
|
-
operating_resolution:
|
|
6143
|
-
mask_only:
|
|
5745
|
+
import { z as z18 } from "zod";
|
|
5746
|
+
var ImageBackgroundRemoveParams = z18.object({
|
|
5747
|
+
model: z18.literal("fal/birefnet-v2").optional().default("fal/birefnet-v2"),
|
|
5748
|
+
model_variant: z18.enum(["General Use (Light)", "General Use (Heavy)", "Matting", "Portrait", "DIS", "HRSOD", "COD"]).optional().default("General Use (Light)"),
|
|
5749
|
+
operating_resolution: z18.enum(["1024x1024", "2048x2048", "2304x2304"]).optional(),
|
|
5750
|
+
mask_only: z18.boolean().optional().default(false)
|
|
6144
5751
|
}).strict();
|
|
6145
5752
|
var imageBackgroundRemoveNode = delegated({
|
|
6146
5753
|
id: "image_background_remove",
|
|
@@ -6148,11 +5755,11 @@ var imageBackgroundRemoveNode = delegated({
|
|
|
6148
5755
|
category: "image",
|
|
6149
5756
|
summary: "Remove the background from an image and return a transparent PNG (or the segmentation mask). Powered by fal.ai `fal-ai/birefnet/v2`.",
|
|
6150
5757
|
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.",
|
|
6151
|
-
inputs:
|
|
5758
|
+
inputs: z18.object({
|
|
6152
5759
|
image: ImageRef
|
|
6153
5760
|
}).strict(),
|
|
6154
5761
|
params: ImageBackgroundRemoveParams,
|
|
6155
|
-
outputs:
|
|
5762
|
+
outputs: z18.object({
|
|
6156
5763
|
image: ImageRef,
|
|
6157
5764
|
mask: ImageRef.optional()
|
|
6158
5765
|
}).strict(),
|
|
@@ -6161,7 +5768,7 @@ var imageBackgroundRemoveNode = delegated({
|
|
|
6161
5768
|
});
|
|
6162
5769
|
|
|
6163
5770
|
// src/engine/nodes/remote/imageDescribe.ts
|
|
6164
|
-
import { z as
|
|
5771
|
+
import { z as z19 } from "zod";
|
|
6165
5772
|
var IMAGE_DESCRIBE_MODELS = ["~google/gemini-pro-latest", "~google/gemini-flash-latest"];
|
|
6166
5773
|
var imageDescribeNode = delegated({
|
|
6167
5774
|
id: "image_describe",
|
|
@@ -6169,33 +5776,33 @@ var imageDescribeNode = delegated({
|
|
|
6169
5776
|
category: "vision",
|
|
6170
5777
|
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.",
|
|
6171
5778
|
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.',
|
|
6172
|
-
inputs:
|
|
6173
|
-
params:
|
|
6174
|
-
model:
|
|
6175
|
-
focus:
|
|
6176
|
-
context:
|
|
6177
|
-
temperature:
|
|
6178
|
-
max_tokens:
|
|
5779
|
+
inputs: z19.object({ image: ImageRef }).loose(),
|
|
5780
|
+
params: z19.object({
|
|
5781
|
+
model: z19.enum(IMAGE_DESCRIBE_MODELS),
|
|
5782
|
+
focus: z19.string().optional(),
|
|
5783
|
+
context: z19.string().optional(),
|
|
5784
|
+
temperature: z19.number().min(0).max(2).optional(),
|
|
5785
|
+
max_tokens: z19.number().int().positive().optional()
|
|
6179
5786
|
}).strict(),
|
|
6180
|
-
outputs:
|
|
5787
|
+
outputs: z19.object({ description: JsonRef }).strict(),
|
|
6181
5788
|
outputKinds: { description: "json" },
|
|
6182
5789
|
cost: () => ({ credits: 2, seconds_estimate: 10 })
|
|
6183
5790
|
});
|
|
6184
5791
|
|
|
6185
5792
|
// src/engine/nodes/remote/imageReferenceSheet.ts
|
|
6186
|
-
import { z as
|
|
5793
|
+
import { z as z20 } from "zod";
|
|
6187
5794
|
var REFERENCE_SHEET_MODELS = ["google/gemini-3-pro-image-preview", "google/gemini-3.1-flash-image-preview"];
|
|
6188
|
-
var ImageReferenceSheetParams =
|
|
6189
|
-
model:
|
|
6190
|
-
subject_description:
|
|
5795
|
+
var ImageReferenceSheetParams = z20.object({
|
|
5796
|
+
model: z20.enum(REFERENCE_SHEET_MODELS),
|
|
5797
|
+
subject_description: z20.string().min(1),
|
|
6191
5798
|
// `location` = a set/room shown from several camera ANGLES (not a rotated subject),
|
|
6192
5799
|
// so a multi-scene shoot keeps one consistent set.
|
|
6193
|
-
subject_type:
|
|
6194
|
-
views:
|
|
6195
|
-
style:
|
|
6196
|
-
prompt_override:
|
|
6197
|
-
aspect_ratio:
|
|
6198
|
-
image_size:
|
|
5800
|
+
subject_type: z20.enum(["character", "person", "product", "location"]),
|
|
5801
|
+
views: z20.array(z20.string().min(1)).min(2).max(8).optional(),
|
|
5802
|
+
style: z20.string().optional(),
|
|
5803
|
+
prompt_override: z20.string().min(1).optional(),
|
|
5804
|
+
aspect_ratio: z20.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(),
|
|
5805
|
+
image_size: z20.enum(["0.5K", "1K", "2K", "4K"]).optional()
|
|
6199
5806
|
}).strict();
|
|
6200
5807
|
var imageReferenceSheetNode = delegated({
|
|
6201
5808
|
id: "image_reference_sheet",
|
|
@@ -6203,9 +5810,9 @@ var imageReferenceSheetNode = delegated({
|
|
|
6203
5810
|
category: "image",
|
|
6204
5811
|
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).",
|
|
6205
5812
|
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.",
|
|
6206
|
-
inputs:
|
|
5813
|
+
inputs: z20.object({ references: z20.array(ImageRef).min(1).max(6) }).loose(),
|
|
6207
5814
|
params: ImageReferenceSheetParams,
|
|
6208
|
-
outputs:
|
|
5815
|
+
outputs: z20.object({ sheet: ImageRef }).strict(),
|
|
6209
5816
|
outputKinds: { sheet: "image" },
|
|
6210
5817
|
cost: ({ params }) => ({
|
|
6211
5818
|
credits: params?.model === "google/gemini-3-pro-image-preview" ? 20 : 5,
|
|
@@ -6214,10 +5821,10 @@ var imageReferenceSheetNode = delegated({
|
|
|
6214
5821
|
});
|
|
6215
5822
|
|
|
6216
5823
|
// src/engine/nodes/remote/imageSearch.ts
|
|
6217
|
-
import { z as
|
|
6218
|
-
var ImageSearchParams =
|
|
6219
|
-
prompt:
|
|
6220
|
-
count:
|
|
5824
|
+
import { z as z21 } from "zod";
|
|
5825
|
+
var ImageSearchParams = z21.object({
|
|
5826
|
+
prompt: z21.string().min(1),
|
|
5827
|
+
count: z21.number().int().min(1).max(20).default(5)
|
|
6221
5828
|
}).strict();
|
|
6222
5829
|
var imageSearchNode = delegated({
|
|
6223
5830
|
id: "image_search",
|
|
@@ -6225,15 +5832,15 @@ var imageSearchNode = delegated({
|
|
|
6225
5832
|
category: "image",
|
|
6226
5833
|
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.",
|
|
6227
5834
|
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.",
|
|
6228
|
-
inputs:
|
|
5835
|
+
inputs: z21.object({}).loose(),
|
|
6229
5836
|
params: ImageSearchParams,
|
|
6230
|
-
outputs:
|
|
5837
|
+
outputs: z21.object({ images: z21.array(ImageRef).min(1) }).strict(),
|
|
6231
5838
|
outputKinds: { images: "image" },
|
|
6232
5839
|
cost: ({ params }) => ({ credits: Math.ceil(2 + params.count / 2), seconds_estimate: 30 })
|
|
6233
5840
|
});
|
|
6234
5841
|
|
|
6235
5842
|
// src/engine/nodes/remote/imageSelect.ts
|
|
6236
|
-
import { z as
|
|
5843
|
+
import { z as z22 } from "zod";
|
|
6237
5844
|
var IMAGE_SELECT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6238
5845
|
var imageSelectNode = delegated({
|
|
6239
5846
|
id: "image_select",
|
|
@@ -6241,15 +5848,15 @@ var imageSelectNode = delegated({
|
|
|
6241
5848
|
category: "vision",
|
|
6242
5849
|
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.",
|
|
6243
5850
|
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.",
|
|
6244
|
-
inputs:
|
|
6245
|
-
params:
|
|
6246
|
-
model:
|
|
6247
|
-
prompt:
|
|
6248
|
-
count:
|
|
6249
|
-
temperature:
|
|
6250
|
-
max_tokens:
|
|
5851
|
+
inputs: z22.object({ images: z22.array(ImageRef).min(2) }).loose(),
|
|
5852
|
+
params: z22.object({
|
|
5853
|
+
model: z22.enum(IMAGE_SELECT_MODELS),
|
|
5854
|
+
prompt: z22.string().min(1),
|
|
5855
|
+
count: z22.number().int().min(1).default(1),
|
|
5856
|
+
temperature: z22.number().min(0).max(2).optional(),
|
|
5857
|
+
max_tokens: z22.number().int().positive().optional()
|
|
6251
5858
|
}).strict(),
|
|
6252
|
-
outputs:
|
|
5859
|
+
outputs: z22.object({ images: z22.array(ImageRef).min(1), reasoning: TextRef }).strict(),
|
|
6253
5860
|
outputKinds: { images: "image", reasoning: "text" },
|
|
6254
5861
|
cost: () => ({ credits: 1, seconds_estimate: 5 }),
|
|
6255
5862
|
// Arity is only knowable at validate time when `images` is a literal array
|
|
@@ -6274,34 +5881,34 @@ var imageSelectNode = delegated({
|
|
|
6274
5881
|
});
|
|
6275
5882
|
|
|
6276
5883
|
// src/engine/nodes/remote/music.ts
|
|
6277
|
-
import { z as
|
|
5884
|
+
import { z as z23 } from "zod";
|
|
6278
5885
|
var MUSIC_MODELS = ["elevenlabs/music-v1", "elevenlabs/video-background-music-v1"];
|
|
6279
|
-
var MusicParams =
|
|
6280
|
-
model:
|
|
5886
|
+
var MusicParams = z23.object({
|
|
5887
|
+
model: z23.enum(MUSIC_MODELS),
|
|
6281
5888
|
/** Free-form prompt. Used by `elevenlabs/music-v1` (compose-detailed). */
|
|
6282
|
-
prompt:
|
|
5889
|
+
prompt: z23.string().optional(),
|
|
6283
5890
|
/**
|
|
6284
5891
|
* Structured composition plan (intro / hook / verse / outro sections with
|
|
6285
5892
|
* per-section styles + durations). Mutually exclusive with `prompt`.
|
|
6286
5893
|
*/
|
|
6287
|
-
composition_plan:
|
|
5894
|
+
composition_plan: z23.record(z23.string(), z23.unknown()).optional(),
|
|
6288
5895
|
/** Target length when using `prompt`. 3000–454545ms (capped by the $10 per-node cost limit). */
|
|
6289
|
-
music_length_ms:
|
|
6290
|
-
seed:
|
|
5896
|
+
music_length_ms: z23.number().int().min(3e3).max(ELEVENLABS_MAX_MUSIC_LENGTH_MS).optional(),
|
|
5897
|
+
seed: z23.number().int().optional(),
|
|
6291
5898
|
/** Prompt mode only — forces an instrumental (no vocals) track. */
|
|
6292
|
-
force_instrumental:
|
|
5899
|
+
force_instrumental: z23.boolean().optional(),
|
|
6293
5900
|
/** composition_plan only — honor exact section durations. */
|
|
6294
|
-
respect_sections_durations:
|
|
5901
|
+
respect_sections_durations: z23.boolean().optional(),
|
|
6295
5902
|
/** Emit word-level timestamps alongside the audio. */
|
|
6296
|
-
with_timestamps:
|
|
5903
|
+
with_timestamps: z23.boolean().optional(),
|
|
6297
5904
|
/**
|
|
6298
5905
|
* video-to-music only — short description of the desired score
|
|
6299
5906
|
* ("upbeat synth, fast cuts, 80s") used to bias the model.
|
|
6300
5907
|
*/
|
|
6301
|
-
description:
|
|
5908
|
+
description: z23.string().max(1e3).optional(),
|
|
6302
5909
|
/** video-to-music only — up to 10 style tags. */
|
|
6303
|
-
tags:
|
|
6304
|
-
output_format:
|
|
5910
|
+
tags: z23.array(z23.string()).max(10).optional(),
|
|
5911
|
+
output_format: z23.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
6305
5912
|
}).strict();
|
|
6306
5913
|
var musicNode = delegated({
|
|
6307
5914
|
id: "music",
|
|
@@ -6309,9 +5916,9 @@ var musicNode = delegated({
|
|
|
6309
5916
|
category: "audio",
|
|
6310
5917
|
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`.",
|
|
6311
5918
|
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.",
|
|
6312
|
-
inputs:
|
|
5919
|
+
inputs: z23.object({ video: VideoRef.optional() }).loose(),
|
|
6313
5920
|
params: MusicParams,
|
|
6314
|
-
outputs:
|
|
5921
|
+
outputs: z23.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
6315
5922
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
6316
5923
|
cost: ({ params }) => {
|
|
6317
5924
|
const seconds = params.music_length_ms ? Math.ceil(params.music_length_ms / 1e3) : 30;
|
|
@@ -6342,25 +5949,25 @@ var musicNode = delegated({
|
|
|
6342
5949
|
});
|
|
6343
5950
|
|
|
6344
5951
|
// src/engine/nodes/remote/soundEffect.ts
|
|
6345
|
-
import { z as
|
|
5952
|
+
import { z as z24 } from "zod";
|
|
6346
5953
|
var SOUND_EFFECT_MODELS = ["elevenlabs/eleven_text_to_sound_v2"];
|
|
6347
|
-
var SoundEffectParams =
|
|
6348
|
-
model:
|
|
5954
|
+
var SoundEffectParams = z24.object({
|
|
5955
|
+
model: z24.enum(SOUND_EFFECT_MODELS),
|
|
6349
5956
|
/** Prompt describing the SFX ("metal door slam", "soft UI tap", "ocean waves"). */
|
|
6350
|
-
text:
|
|
5957
|
+
text: z24.string().min(1),
|
|
6351
5958
|
/**
|
|
6352
5959
|
* Target length in seconds. 0.5–30. Leave unset to let the model pick the
|
|
6353
5960
|
* natural length for the described effect.
|
|
6354
5961
|
*/
|
|
6355
|
-
duration_seconds:
|
|
5962
|
+
duration_seconds: z24.number().min(0.5).max(30).optional(),
|
|
6356
5963
|
/**
|
|
6357
5964
|
* 0–1. Higher = stick closer to the prompt at the cost of variety; lower
|
|
6358
5965
|
* = let the model interpret more freely. Defaults to 0.3 on the provider.
|
|
6359
5966
|
*/
|
|
6360
|
-
prompt_influence:
|
|
5967
|
+
prompt_influence: z24.number().min(0).max(1).optional(),
|
|
6361
5968
|
/** Only valid on `eleven_text_to_sound_v2` — produce a seamless loop. */
|
|
6362
|
-
loop:
|
|
6363
|
-
output_format:
|
|
5969
|
+
loop: z24.boolean().optional(),
|
|
5970
|
+
output_format: z24.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
6364
5971
|
}).strict();
|
|
6365
5972
|
var soundEffectNode = delegated({
|
|
6366
5973
|
id: "sound_effect",
|
|
@@ -6368,9 +5975,9 @@ var soundEffectNode = delegated({
|
|
|
6368
5975
|
category: "audio",
|
|
6369
5976
|
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.",
|
|
6370
5977
|
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.",
|
|
6371
|
-
inputs:
|
|
5978
|
+
inputs: z24.object({}).loose(),
|
|
6372
5979
|
params: SoundEffectParams,
|
|
6373
|
-
outputs:
|
|
5980
|
+
outputs: z24.object({ audio: AudioRef }).strict(),
|
|
6374
5981
|
outputKinds: { audio: "audio" },
|
|
6375
5982
|
cost: ({ params }) => {
|
|
6376
5983
|
const seconds = params.duration_seconds ?? 5;
|
|
@@ -6379,7 +5986,7 @@ var soundEffectNode = delegated({
|
|
|
6379
5986
|
});
|
|
6380
5987
|
|
|
6381
5988
|
// src/engine/nodes/remote/textGenerate.ts
|
|
6382
|
-
import { z as
|
|
5989
|
+
import { z as z25 } from "zod";
|
|
6383
5990
|
var TEXT_GENERATE_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6384
5991
|
var textGenerateNode = delegated({
|
|
6385
5992
|
id: "text_generate",
|
|
@@ -6387,58 +5994,58 @@ var textGenerateNode = delegated({
|
|
|
6387
5994
|
category: "language",
|
|
6388
5995
|
summary: "Single-turn LLM text generation via OpenRouter. Returns a text response.",
|
|
6389
5996
|
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.',
|
|
6390
|
-
inputs:
|
|
6391
|
-
params:
|
|
6392
|
-
model:
|
|
6393
|
-
prompt:
|
|
6394
|
-
system:
|
|
6395
|
-
response_format:
|
|
6396
|
-
web_search:
|
|
6397
|
-
temperature:
|
|
6398
|
-
max_tokens:
|
|
5997
|
+
inputs: z25.object({}).loose(),
|
|
5998
|
+
params: z25.object({
|
|
5999
|
+
model: z25.enum(TEXT_GENERATE_MODELS),
|
|
6000
|
+
prompt: z25.string().min(1),
|
|
6001
|
+
system: z25.string().optional(),
|
|
6002
|
+
response_format: z25.enum(["text", "json_object"]).optional(),
|
|
6003
|
+
web_search: z25.boolean().optional(),
|
|
6004
|
+
temperature: z25.number().min(0).max(2).optional(),
|
|
6005
|
+
max_tokens: z25.number().int().positive().optional()
|
|
6399
6006
|
}).strict(),
|
|
6400
|
-
outputs:
|
|
6007
|
+
outputs: z25.object({ text: TextRef }).strict(),
|
|
6401
6008
|
outputKinds: { text: "text" },
|
|
6402
6009
|
cost: () => ({ credits: 1, seconds_estimate: 3 })
|
|
6403
6010
|
});
|
|
6404
6011
|
|
|
6405
6012
|
// src/engine/nodes/remote/tts.ts
|
|
6406
|
-
import { z as
|
|
6013
|
+
import { z as z26 } from "zod";
|
|
6407
6014
|
var TTS_MODELS = ["elevenlabs/eleven_v3"];
|
|
6408
|
-
var TtsVoiceSettings =
|
|
6409
|
-
stability:
|
|
6410
|
-
similarity_boost:
|
|
6411
|
-
style:
|
|
6412
|
-
use_speaker_boost:
|
|
6413
|
-
speed:
|
|
6015
|
+
var TtsVoiceSettings = z26.object({
|
|
6016
|
+
stability: z26.number().min(0).max(1).optional(),
|
|
6017
|
+
similarity_boost: z26.number().min(0).max(1).optional(),
|
|
6018
|
+
style: z26.number().min(0).max(1).optional(),
|
|
6019
|
+
use_speaker_boost: z26.boolean().optional(),
|
|
6020
|
+
speed: z26.number().min(0.25).max(4).optional()
|
|
6414
6021
|
}).strict();
|
|
6415
|
-
var TtsPronunciationLocator =
|
|
6416
|
-
pronunciation_dictionary_id:
|
|
6417
|
-
version_id:
|
|
6022
|
+
var TtsPronunciationLocator = z26.object({
|
|
6023
|
+
pronunciation_dictionary_id: z26.string().min(1),
|
|
6024
|
+
version_id: z26.string().nullable().optional()
|
|
6418
6025
|
}).strict();
|
|
6419
|
-
var TtsParams =
|
|
6420
|
-
model:
|
|
6421
|
-
text:
|
|
6422
|
-
voice:
|
|
6026
|
+
var TtsParams = z26.object({
|
|
6027
|
+
model: z26.enum(TTS_MODELS),
|
|
6028
|
+
text: z26.string().min(1).max(ELEVENLABS_MAX_TEXT_CHARS),
|
|
6029
|
+
voice: z26.string().min(1),
|
|
6423
6030
|
/** Provider output_format (mp3 family only — assets are stored as audio/mpeg). */
|
|
6424
|
-
output_format:
|
|
6425
|
-
seed:
|
|
6031
|
+
output_format: z26.enum(ELEVENLABS_OUTPUT_FORMATS).optional(),
|
|
6032
|
+
seed: z26.number().int().min(0).max(4294967295).optional(),
|
|
6426
6033
|
// Top-level shortcuts; structured form is `voice_settings`.
|
|
6427
|
-
stability:
|
|
6428
|
-
similarity_boost:
|
|
6034
|
+
stability: z26.number().min(0).max(1).optional(),
|
|
6035
|
+
similarity_boost: z26.number().min(0).max(1).optional(),
|
|
6429
6036
|
voice_settings: TtsVoiceSettings.optional(),
|
|
6430
6037
|
/** ISO 639-1 language code. eleven_v3 supports language hints. */
|
|
6431
|
-
language_code:
|
|
6432
|
-
pronunciation_dictionary_locators:
|
|
6433
|
-
apply_text_normalization:
|
|
6038
|
+
language_code: z26.string().optional(),
|
|
6039
|
+
pronunciation_dictionary_locators: z26.array(TtsPronunciationLocator).max(3).optional(),
|
|
6040
|
+
apply_text_normalization: z26.enum(["auto", "on", "off"]).optional(),
|
|
6434
6041
|
/** Currently Japanese-only. Adds latency. */
|
|
6435
|
-
apply_language_text_normalization:
|
|
6042
|
+
apply_language_text_normalization: z26.boolean().optional(),
|
|
6436
6043
|
/**
|
|
6437
6044
|
* When true, hits `/v1/text-to-speech/{voice_id}/with-timestamps` and
|
|
6438
6045
|
* adds a `timestamps` output (character-level alignment) for caption
|
|
6439
6046
|
* rendering, lipsync, and beat-matched cuts.
|
|
6440
6047
|
*/
|
|
6441
|
-
with_timestamps:
|
|
6048
|
+
with_timestamps: z26.boolean().optional()
|
|
6442
6049
|
}).strict();
|
|
6443
6050
|
var ttsNode = delegated({
|
|
6444
6051
|
id: "tts",
|
|
@@ -6446,9 +6053,9 @@ var ttsNode = delegated({
|
|
|
6446
6053
|
category: "audio",
|
|
6447
6054
|
summary: "Single-voice text-to-speech via ElevenLabs Eleven v3. Optional character-level timestamps for caption rendering and beat-matched cuts.",
|
|
6448
6055
|
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).",
|
|
6449
|
-
inputs:
|
|
6056
|
+
inputs: z26.object({}).loose(),
|
|
6450
6057
|
params: TtsParams,
|
|
6451
|
-
outputs:
|
|
6058
|
+
outputs: z26.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
6452
6059
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
6453
6060
|
cost: ({ params }) => ({
|
|
6454
6061
|
credits: Math.max(1, Math.ceil(params.text.length * 15e-4)),
|
|
@@ -6457,23 +6064,23 @@ var ttsNode = delegated({
|
|
|
6457
6064
|
});
|
|
6458
6065
|
|
|
6459
6066
|
// src/engine/nodes/remote/video.ts
|
|
6460
|
-
import { z as
|
|
6067
|
+
import { z as z27 } from "zod";
|
|
6461
6068
|
var VIDEO_GENERATE_MODELS = ["bytedance/seedance-2.0", "google/veo-3.1-fast"];
|
|
6462
|
-
var VideoGenerateParams =
|
|
6463
|
-
model:
|
|
6464
|
-
prompt:
|
|
6465
|
-
duration:
|
|
6466
|
-
resolution:
|
|
6069
|
+
var VideoGenerateParams = z27.object({
|
|
6070
|
+
model: z27.enum(VIDEO_GENERATE_MODELS),
|
|
6071
|
+
prompt: z27.string().min(1),
|
|
6072
|
+
duration: z27.number().int().positive().optional(),
|
|
6073
|
+
resolution: z27.string().optional(),
|
|
6467
6074
|
// Union of ratios accepted by at least one curated model (registry gates
|
|
6468
6075
|
// per-model). 3:2/2:3 are deliberately absent: no registered model takes them.
|
|
6469
|
-
aspect_ratio:
|
|
6470
|
-
generate_audio:
|
|
6471
|
-
seed:
|
|
6076
|
+
aspect_ratio: z27.enum(["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"]).optional(),
|
|
6077
|
+
generate_audio: z27.boolean().optional(),
|
|
6078
|
+
seed: z27.number().int().nonnegative().optional(),
|
|
6472
6079
|
// Veo-only passthroughs (routed via `provider.options.google-vertex.parameters`).
|
|
6473
|
-
negative_prompt:
|
|
6474
|
-
person_generation:
|
|
6475
|
-
enhance_prompt:
|
|
6476
|
-
conditioning_scale:
|
|
6080
|
+
negative_prompt: z27.string().optional(),
|
|
6081
|
+
person_generation: z27.string().optional(),
|
|
6082
|
+
enhance_prompt: z27.boolean().optional(),
|
|
6083
|
+
conditioning_scale: z27.number().optional()
|
|
6477
6084
|
}).strict();
|
|
6478
6085
|
var videoGenerateNode = delegated({
|
|
6479
6086
|
id: "video_generate",
|
|
@@ -6481,23 +6088,23 @@ var videoGenerateNode = delegated({
|
|
|
6481
6088
|
category: "video",
|
|
6482
6089
|
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.",
|
|
6483
6090
|
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.",
|
|
6484
|
-
inputs:
|
|
6091
|
+
inputs: z27.object({
|
|
6485
6092
|
first_frame: ImageRef.optional(),
|
|
6486
6093
|
last_frame: ImageRef.optional(),
|
|
6487
6094
|
reference: ImageRef.optional()
|
|
6488
6095
|
}).loose(),
|
|
6489
6096
|
params: VideoGenerateParams,
|
|
6490
|
-
outputs:
|
|
6097
|
+
outputs: z27.object({ video: VideoRef }).strict(),
|
|
6491
6098
|
outputKinds: { video: "video" },
|
|
6492
6099
|
cost: () => ({ credits: 50, seconds_estimate: 120 })
|
|
6493
6100
|
});
|
|
6494
6101
|
|
|
6495
6102
|
// src/engine/nodes/remote/videoBackgroundRemove.ts
|
|
6496
|
-
import { z as
|
|
6497
|
-
var VideoBackgroundRemoveParams =
|
|
6498
|
-
model:
|
|
6499
|
-
edge_refinement:
|
|
6500
|
-
output_codec:
|
|
6103
|
+
import { z as z28 } from "zod";
|
|
6104
|
+
var VideoBackgroundRemoveParams = z28.object({
|
|
6105
|
+
model: z28.literal("fal/veed-video-background-removal").optional().default("fal/veed-video-background-removal"),
|
|
6106
|
+
edge_refinement: z28.boolean().optional().default(true),
|
|
6107
|
+
output_codec: z28.enum(["vp9", "h264"]).optional().default("vp9")
|
|
6501
6108
|
}).strict();
|
|
6502
6109
|
var videoBackgroundRemoveNode = delegated({
|
|
6503
6110
|
id: "video_background_remove",
|
|
@@ -6505,18 +6112,18 @@ var videoBackgroundRemoveNode = delegated({
|
|
|
6505
6112
|
category: "video",
|
|
6506
6113
|
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`.",
|
|
6507
6114
|
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.",
|
|
6508
|
-
inputs:
|
|
6115
|
+
inputs: z28.object({
|
|
6509
6116
|
video: VideoRef
|
|
6510
6117
|
}).strict(),
|
|
6511
6118
|
params: VideoBackgroundRemoveParams,
|
|
6512
|
-
outputs:
|
|
6119
|
+
outputs: z28.object({ video: VideoRef }).strict(),
|
|
6513
6120
|
outputKinds: { video: "video" },
|
|
6514
6121
|
// $0.012 per 30 frames (edge refinement on) — assume ~30fps; refine via fal dashboard.
|
|
6515
6122
|
cost: () => ({ credits: 50, seconds_estimate: 60 })
|
|
6516
6123
|
});
|
|
6517
6124
|
|
|
6518
6125
|
// src/engine/nodes/remote/videoDeconstruct.ts
|
|
6519
|
-
import { z as
|
|
6126
|
+
import { z as z29 } from "zod";
|
|
6520
6127
|
var VIDEO_DECONSTRUCT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6521
6128
|
var videoDeconstructNode = delegated({
|
|
6522
6129
|
id: "video_deconstruct",
|
|
@@ -6524,34 +6131,34 @@ var videoDeconstructNode = delegated({
|
|
|
6524
6131
|
category: "video",
|
|
6525
6132
|
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).',
|
|
6526
6133
|
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.',
|
|
6527
|
-
inputs:
|
|
6528
|
-
params:
|
|
6529
|
-
model:
|
|
6530
|
-
mode:
|
|
6531
|
-
language:
|
|
6532
|
-
max_scenes:
|
|
6533
|
-
focus:
|
|
6534
|
-
start_s:
|
|
6535
|
-
end_s:
|
|
6134
|
+
inputs: z29.object({ video: VideoRef }).loose(),
|
|
6135
|
+
params: z29.object({
|
|
6136
|
+
model: z29.enum(VIDEO_DECONSTRUCT_MODELS),
|
|
6137
|
+
mode: z29.enum(["full", "index"]).optional(),
|
|
6138
|
+
language: z29.string().min(2).max(8).optional(),
|
|
6139
|
+
max_scenes: z29.number().int().min(1).max(60).optional(),
|
|
6140
|
+
focus: z29.string().optional(),
|
|
6141
|
+
start_s: z29.number().min(0).optional(),
|
|
6142
|
+
end_s: z29.number().positive().optional(),
|
|
6536
6143
|
// Real visual shot-cut timestamps (absolute seconds), detected locally with
|
|
6537
6144
|
// ffmpeg before the deconstruct. The backend SNAPS its LLM scene boundaries
|
|
6538
6145
|
// onto these and SPLITS any scene that spans one, so a scene's frames never
|
|
6539
6146
|
// straddle a hard cut. `scaffold-video` populates this; omit for LLM-only cuts.
|
|
6540
|
-
shot_cuts:
|
|
6147
|
+
shot_cuts: z29.array(z29.number().min(0)).max(200).optional(),
|
|
6541
6148
|
// The video model's per-clip ceiling (seconds). A shot longer than this is
|
|
6542
6149
|
// split into seamless continuation sub-scenes (shared splice frame), so long
|
|
6543
6150
|
// shots reproduce in full instead of being truncated. `scaffold-video` sets
|
|
6544
6151
|
// the Seedance ceiling (15); omit to disable length splitting.
|
|
6545
|
-
max_clip_s:
|
|
6152
|
+
max_clip_s: z29.number().positive().max(60).optional(),
|
|
6546
6153
|
// Transcript provider for the blueprint's dialogue/transcript. Default
|
|
6547
6154
|
// Groq Whisper; "deepgram" routes to Nova-3 so words carry punctuation.
|
|
6548
|
-
transcriber:
|
|
6155
|
+
transcriber: z29.enum(["groq", "deepgram"]).optional()
|
|
6549
6156
|
}).strict(),
|
|
6550
|
-
outputs:
|
|
6157
|
+
outputs: z29.object({
|
|
6551
6158
|
analysis: JsonRef,
|
|
6552
6159
|
// Absent in mode:"index" (structure only, no Mux frame extraction).
|
|
6553
|
-
start_frames:
|
|
6554
|
-
end_frames:
|
|
6160
|
+
start_frames: z29.array(ImageRef).min(1).optional(),
|
|
6161
|
+
end_frames: z29.array(ImageRef).min(1).optional(),
|
|
6555
6162
|
transcript: JsonRef
|
|
6556
6163
|
}).strict(),
|
|
6557
6164
|
outputKinds: { analysis: "json", start_frames: "image", end_frames: "image", transcript: "json" },
|
|
@@ -6559,22 +6166,22 @@ var videoDeconstructNode = delegated({
|
|
|
6559
6166
|
});
|
|
6560
6167
|
|
|
6561
6168
|
// src/engine/nodes/remote/videoLipsync.ts
|
|
6562
|
-
import { z as
|
|
6563
|
-
var FalLipsyncParams =
|
|
6564
|
-
model:
|
|
6169
|
+
import { z as z30 } from "zod";
|
|
6170
|
+
var FalLipsyncParams = z30.object({
|
|
6171
|
+
model: z30.literal("fal/veed-lipsync")
|
|
6565
6172
|
}).strict();
|
|
6566
|
-
var VideoLipsyncParams =
|
|
6173
|
+
var VideoLipsyncParams = z30.discriminatedUnion("model", [FalLipsyncParams]);
|
|
6567
6174
|
var videoLipsyncNode = delegated({
|
|
6568
6175
|
id: "video_lipsync",
|
|
6569
6176
|
version: "1.0.0",
|
|
6570
6177
|
category: "video",
|
|
6571
6178
|
summary: "Lip-sync a video to an audio track. Currently backed by VEED via fal.ai (`fal/veed-lipsync`). $0.40/min of output.",
|
|
6572
|
-
inputs:
|
|
6179
|
+
inputs: z30.object({
|
|
6573
6180
|
video: VideoRef,
|
|
6574
6181
|
audio: AudioRef
|
|
6575
6182
|
}).strict(),
|
|
6576
6183
|
params: VideoLipsyncParams,
|
|
6577
|
-
outputs:
|
|
6184
|
+
outputs: z30.object({ video: VideoRef }).strict(),
|
|
6578
6185
|
outputKinds: { video: "video" },
|
|
6579
6186
|
cost: () => ({ credits: 20, seconds_estimate: 120 })
|
|
6580
6187
|
});
|
|
@@ -6583,7 +6190,7 @@ var videoLipsyncNode = delegated({
|
|
|
6583
6190
|
import { mkdtemp as mkdtemp6, readFile as readFile10, rm as rm6 } from "fs/promises";
|
|
6584
6191
|
import { tmpdir as tmpdir6 } from "os";
|
|
6585
6192
|
import path13 from "path";
|
|
6586
|
-
import { z as
|
|
6193
|
+
import { z as z31 } from "zod";
|
|
6587
6194
|
|
|
6588
6195
|
// src/engine/nodes/local/lib/ffmpeg.ts
|
|
6589
6196
|
import { execFile as execFile7 } from "child_process";
|
|
@@ -6662,21 +6269,21 @@ ${detail.slice(-4e3)}`);
|
|
|
6662
6269
|
}
|
|
6663
6270
|
|
|
6664
6271
|
// src/engine/nodes/remote/videoTranscribe.ts
|
|
6665
|
-
var VideoTranscribeParams =
|
|
6666
|
-
language:
|
|
6272
|
+
var VideoTranscribeParams = z31.object({
|
|
6273
|
+
language: z31.string().min(2).max(8).optional(),
|
|
6667
6274
|
// Provider choice is explicit (no env-based silent branching). Default Groq
|
|
6668
6275
|
// Whisper; "deepgram" routes to Deepgram Nova-3, which additionally emits a
|
|
6669
6276
|
// `rich` JSON output with punctuated words + paragraph/sentence grouping.
|
|
6670
|
-
transcriber:
|
|
6277
|
+
transcriber: z31.enum(["groq", "deepgram"]).optional()
|
|
6671
6278
|
}).strict();
|
|
6672
|
-
var VideoTranscribeInputs =
|
|
6279
|
+
var VideoTranscribeInputs = z31.object({
|
|
6673
6280
|
video: VideoRef
|
|
6674
6281
|
}).strict();
|
|
6675
|
-
var VideoTranscribeOutputs =
|
|
6676
|
-
transcript:
|
|
6282
|
+
var VideoTranscribeOutputs = z31.object({
|
|
6283
|
+
transcript: z31.custom(),
|
|
6677
6284
|
// Only emitted by the Deepgram path: full punctuated words + paragraph /
|
|
6678
6285
|
// sentence grouping with speaker indices. Absent for the default Groq path.
|
|
6679
|
-
rich:
|
|
6286
|
+
rich: z31.custom().optional()
|
|
6680
6287
|
}).strict();
|
|
6681
6288
|
var AUDIO_EXTRACT_TIMEOUT_MS = 6e4;
|
|
6682
6289
|
var videoTranscribeNode = defineNode({
|
|
@@ -6761,29 +6368,29 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
6761
6368
|
}
|
|
6762
6369
|
|
|
6763
6370
|
// src/engine/nodes/remote/voiceSelect.ts
|
|
6764
|
-
import { z as
|
|
6371
|
+
import { z as z32 } from "zod";
|
|
6765
6372
|
var voiceSelectNode = delegated({
|
|
6766
6373
|
id: "voice_select",
|
|
6767
6374
|
version: "1.0.0",
|
|
6768
6375
|
category: "audio",
|
|
6769
6376
|
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.',
|
|
6770
6377
|
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.',
|
|
6771
|
-
inputs:
|
|
6772
|
-
params:
|
|
6773
|
-
description:
|
|
6774
|
-
gender:
|
|
6775
|
-
age:
|
|
6776
|
-
accent:
|
|
6777
|
-
language:
|
|
6778
|
-
limit:
|
|
6378
|
+
inputs: z32.object({}).loose(),
|
|
6379
|
+
params: z32.object({
|
|
6380
|
+
description: z32.string().min(1),
|
|
6381
|
+
gender: z32.string().optional(),
|
|
6382
|
+
age: z32.string().optional(),
|
|
6383
|
+
accent: z32.string().optional(),
|
|
6384
|
+
language: z32.string().optional(),
|
|
6385
|
+
limit: z32.number().int().min(1).max(20).optional()
|
|
6779
6386
|
}).strict(),
|
|
6780
|
-
outputs:
|
|
6387
|
+
outputs: z32.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
|
|
6781
6388
|
outputKinds: { voice_id: "text", candidates: "json" },
|
|
6782
6389
|
cost: () => ({ credits: 0, seconds_estimate: 5 })
|
|
6783
6390
|
});
|
|
6784
6391
|
|
|
6785
6392
|
// src/engine/schema/catalog.ts
|
|
6786
|
-
import { z as
|
|
6393
|
+
import { z as z33 } from "zod";
|
|
6787
6394
|
function generateCatalog(registry, opts = {}) {
|
|
6788
6395
|
const entries = registry.all().map((def) => {
|
|
6789
6396
|
const cost = def.cost ? safeCost(def) : void 0;
|
|
@@ -6794,9 +6401,9 @@ function generateCatalog(registry, opts = {}) {
|
|
|
6794
6401
|
summary: def.summary,
|
|
6795
6402
|
when_to_use: def.when_to_use,
|
|
6796
6403
|
location: def.location,
|
|
6797
|
-
inputs:
|
|
6798
|
-
params:
|
|
6799
|
-
outputs:
|
|
6404
|
+
inputs: z33.toJSONSchema(def.inputs, { unrepresentable: "any" }),
|
|
6405
|
+
params: z33.toJSONSchema(def.params, { unrepresentable: "any" }),
|
|
6406
|
+
outputs: z33.toJSONSchema(def.outputs, { unrepresentable: "any" }),
|
|
6800
6407
|
cost_estimate_credits: cost?.credits,
|
|
6801
6408
|
runtime_estimate_seconds: cost?.seconds_estimate
|
|
6802
6409
|
};
|
|
@@ -6873,8 +6480,7 @@ var LOCAL_NODES = [
|
|
|
6873
6480
|
imagemagickNode,
|
|
6874
6481
|
videoTranscribeNode,
|
|
6875
6482
|
fontSpecimenNode,
|
|
6876
|
-
audioTimelineNode
|
|
6877
|
-
collectNode
|
|
6483
|
+
audioTimelineNode
|
|
6878
6484
|
];
|
|
6879
6485
|
var REMOTE_NODES = [
|
|
6880
6486
|
textGenerateNode,
|
|
@@ -6907,29 +6513,17 @@ function createEngineFromEnv(opts = {}) {
|
|
|
6907
6513
|
const cacheDir = opts.cacheDir ?? path15.join(cwd, "canvas", ".cache");
|
|
6908
6514
|
const outputsDir = opts.outputsDir ?? path15.join(cwd, "canvas");
|
|
6909
6515
|
const creds = requireCredentialsFromEnv();
|
|
6910
|
-
const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
|
|
6911
|
-
const assets = new LocalAssetStore(path15.join(cacheDir, "assets"));
|
|
6912
|
-
const localCache = new LocalCacheStore(path15.join(cacheDir, "index"));
|
|
6913
|
-
const remoteCacheEnabled = opts.remoteCache ?? remoteCacheEnabledFromEnv();
|
|
6914
|
-
const cache = remoteCacheEnabled ? new LayeredCacheStore({
|
|
6915
|
-
local: localCache,
|
|
6916
|
-
remote: new RemoteCacheStore(client, opts.log),
|
|
6917
|
-
assets,
|
|
6918
|
-
log: opts.log
|
|
6919
|
-
}) : localCache;
|
|
6920
6516
|
return new Engine({
|
|
6921
6517
|
registry: defaultRegistry(),
|
|
6922
|
-
client,
|
|
6923
|
-
assets,
|
|
6924
|
-
cache,
|
|
6518
|
+
client: new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey }),
|
|
6519
|
+
assets: new LocalAssetStore(path15.join(cacheDir, "assets")),
|
|
6520
|
+
cache: new LocalCacheStore(path15.join(cacheDir, "index")),
|
|
6925
6521
|
outputsDir,
|
|
6926
|
-
log: opts.log
|
|
6927
|
-
persistAssets: remoteCacheEnabled
|
|
6522
|
+
log: opts.log
|
|
6928
6523
|
});
|
|
6929
6524
|
}
|
|
6930
6525
|
|
|
6931
6526
|
export {
|
|
6932
|
-
requireCredentialsFromEnv,
|
|
6933
6527
|
LayerExecutionError,
|
|
6934
6528
|
describeFailureReason,
|
|
6935
6529
|
SEEDANCE_DURATIONS,
|
|
@@ -6937,14 +6531,7 @@ export {
|
|
|
6937
6531
|
IMAGE_GENERATE_MODELS,
|
|
6938
6532
|
MODEL_REGISTRY,
|
|
6939
6533
|
resolveConcurrency,
|
|
6940
|
-
ulid,
|
|
6941
|
-
isPersistedAssetRef,
|
|
6942
|
-
collectAssetRefLikes,
|
|
6943
|
-
REF_PREFIX,
|
|
6944
|
-
parseRefExpr,
|
|
6945
|
-
sha256Hex,
|
|
6946
6534
|
elementMentionKeywords,
|
|
6947
|
-
toModelSafeImage,
|
|
6948
6535
|
BackendClient2 as BackendClient,
|
|
6949
6536
|
Engine2 as Engine,
|
|
6950
6537
|
LocalAssetStore2 as LocalAssetStore,
|
|
@@ -6955,4 +6542,4 @@ export {
|
|
|
6955
6542
|
defaultRegistry,
|
|
6956
6543
|
createEngineFromEnv
|
|
6957
6544
|
};
|
|
6958
|
-
//# sourceMappingURL=chunk-
|
|
6545
|
+
//# sourceMappingURL=chunk-IWPAXJC3.js.map
|