@koda-sl/baker-cli 0.123.0-dev.8e4328629 → 0.123.0-dev.b74ab562
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 +76 -71
- package/dist/{chunk-43KBQLP5.js → chunk-MWFJ5NOP.js} +104 -476
- package/dist/chunk-MWFJ5NOP.js.map +1 -0
- package/dist/cli.js +2169 -2387
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +0 -91
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-43KBQLP5.js.map +0 -1
|
@@ -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
|
};
|
|
@@ -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,
|
|
@@ -3346,16 +3071,6 @@ function pickFinalOutput(canvas, outputs) {
|
|
|
3346
3071
|
const lastOut = outputs[last.id];
|
|
3347
3072
|
return lastOut ? Object.values(lastOut)[0] : void 0;
|
|
3348
3073
|
}
|
|
3349
|
-
function computeNeedsLocalBytes(canvas, graph, registry) {
|
|
3350
|
-
const typeById = new Map(canvas.nodes.map((n) => [n.id, n.type]));
|
|
3351
|
-
const needs = /* @__PURE__ */ new Set();
|
|
3352
|
-
for (const [consumerId, deps] of graph) {
|
|
3353
|
-
const def = registry.get(typeById.get(consumerId) ?? "");
|
|
3354
|
-
if (def?.location !== "local") continue;
|
|
3355
|
-
for (const dep of deps) needs.add(dep);
|
|
3356
|
-
}
|
|
3357
|
-
return needs;
|
|
3358
|
-
}
|
|
3359
3074
|
function buildGraph(canvas) {
|
|
3360
3075
|
const graph = /* @__PURE__ */ new Map();
|
|
3361
3076
|
for (const n of canvas.nodes) graph.set(n.id, /* @__PURE__ */ new Set());
|
|
@@ -3486,16 +3201,7 @@ async function hydrateSlotValue(value, assets, nodeId, nodeType) {
|
|
|
3486
3201
|
try {
|
|
3487
3202
|
bytes = await assets.readBytes(value.sha256, value.mime);
|
|
3488
3203
|
} catch (e) {
|
|
3489
|
-
|
|
3490
|
-
try {
|
|
3491
|
-
await assets.ingestRemote({ kind: value.kind, url: value.url, sha256: value.sha256, mime: value.mime });
|
|
3492
|
-
bytes = await assets.readBytes(value.sha256, value.mime);
|
|
3493
|
-
} catch (e2) {
|
|
3494
|
-
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e2 });
|
|
3495
|
-
}
|
|
3496
|
-
} else {
|
|
3497
|
-
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3498
|
-
}
|
|
3204
|
+
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3499
3205
|
}
|
|
3500
3206
|
if (bytes.length > MAX_INLINE_TEXT_BYTES) {
|
|
3501
3207
|
throw new NodeExecutionError(nodeId, nodeType, {
|
|
@@ -3603,6 +3309,27 @@ var FontRef = BaseAssetRef.extend({
|
|
|
3603
3309
|
});
|
|
3604
3310
|
var AssetRef = z4.discriminatedUnion("kind", [ImageRef, VideoRef, AudioRef, JsonRef, TextRef, FontRef]);
|
|
3605
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
|
+
|
|
3606
3333
|
// src/engine/nodes/remote/delegate.ts
|
|
3607
3334
|
function delegated(spec) {
|
|
3608
3335
|
return {
|
|
@@ -3630,9 +3357,7 @@ async function callBackendExec(args) {
|
|
|
3630
3357
|
nodeVersion: args.nodeVersion,
|
|
3631
3358
|
params: args.params,
|
|
3632
3359
|
inputs: serialized,
|
|
3633
|
-
idempotency_key: idempotencyKey
|
|
3634
|
-
canvas_run_id: args.ctx.canvasRunId,
|
|
3635
|
-
node_id: args.ctx.nodeId
|
|
3360
|
+
idempotency_key: idempotencyKey
|
|
3636
3361
|
},
|
|
3637
3362
|
args.ctx.signal
|
|
3638
3363
|
);
|
|
@@ -3684,9 +3409,6 @@ async function ingestValue(value, ctx, declaredKind) {
|
|
|
3684
3409
|
}
|
|
3685
3410
|
if (isRawAsset(value)) {
|
|
3686
3411
|
const kind = value.kind ?? declaredKind ?? "json";
|
|
3687
|
-
if (ctx.downloadOutputs === false) {
|
|
3688
|
-
return buildRef({ kind, sha: value.sha256, mime: value.mime, url: value.url, metadata: value.metadata });
|
|
3689
|
-
}
|
|
3690
3412
|
return ctx.assets.ingestRemote({
|
|
3691
3413
|
kind,
|
|
3692
3414
|
url: value.url,
|
|
@@ -3801,7 +3523,7 @@ function safePathname(rawUrl) {
|
|
|
3801
3523
|
}
|
|
3802
3524
|
var ingestNode = defineNode({
|
|
3803
3525
|
id: "ingest",
|
|
3804
|
-
version: "1.
|
|
3526
|
+
version: "1.1.0",
|
|
3805
3527
|
category: "io",
|
|
3806
3528
|
location: "local",
|
|
3807
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.",
|
|
@@ -3844,9 +3566,6 @@ function runStrategy(strategy, params, ctx) {
|
|
|
3844
3566
|
}
|
|
3845
3567
|
}
|
|
3846
3568
|
async function execDirectFetch(params, ctx) {
|
|
3847
|
-
if (params.expect === "image") {
|
|
3848
|
-
return ingestImageUrl(params.url, ctx);
|
|
3849
|
-
}
|
|
3850
3569
|
const result = await callBackendExec({
|
|
3851
3570
|
nodeType: "ingest",
|
|
3852
3571
|
nodeVersion: ingestNode.version,
|
|
@@ -3857,37 +3576,6 @@ async function execDirectFetch(params, ctx) {
|
|
|
3857
3576
|
});
|
|
3858
3577
|
return assertAssetOutput(result, params.expect);
|
|
3859
3578
|
}
|
|
3860
|
-
async function ingestImageUrl(url, ctx) {
|
|
3861
|
-
const res = await fetch(url);
|
|
3862
|
-
if (!res.ok) {
|
|
3863
|
-
throw localExecError(ctx, `fetch ${url} \u2192 ${res.status}`);
|
|
3864
|
-
}
|
|
3865
|
-
const ab = await res.arrayBuffer();
|
|
3866
|
-
if (ab.byteLength > MAX_ASSET_BYTES) {
|
|
3867
|
-
throw localExecError(ctx, `file_too_large: ${url} is ${ab.byteLength} bytes (limit ${MAX_ASSET_BYTES})`);
|
|
3868
|
-
}
|
|
3869
|
-
let normalized;
|
|
3870
|
-
try {
|
|
3871
|
-
normalized = await toModelSafeImage(Buffer.from(ab));
|
|
3872
|
-
} catch (e) {
|
|
3873
|
-
throw localExecError(ctx, `${url}: ${e.message}`);
|
|
3874
|
-
}
|
|
3875
|
-
if (normalized.rasterizedFrom) {
|
|
3876
|
-
ctx.log(`ingest: normalized ${normalized.rasterizedFrom} URL -> PNG (${normalized.bytes.length}B)`);
|
|
3877
|
-
}
|
|
3878
|
-
return uploadAndIngest({
|
|
3879
|
-
bytes: normalized.bytes,
|
|
3880
|
-
kind: "image",
|
|
3881
|
-
mime: normalized.mime,
|
|
3882
|
-
metadata: {
|
|
3883
|
-
source_url: url,
|
|
3884
|
-
strategy: "direct_fetch",
|
|
3885
|
-
ingested_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3886
|
-
...normalized.rasterizedFrom ? { rasterized_from: normalized.rasterizedFrom } : {}
|
|
3887
|
-
},
|
|
3888
|
-
ctx
|
|
3889
|
-
});
|
|
3890
|
-
}
|
|
3891
3579
|
async function execHandinger(params, ctx) {
|
|
3892
3580
|
const result = await callBackendExec({
|
|
3893
3581
|
nodeType: "ingest",
|
|
@@ -3924,15 +3612,7 @@ var EXT_TO_MIME = {
|
|
|
3924
3612
|
jpeg: "image/jpeg",
|
|
3925
3613
|
webp: "image/webp",
|
|
3926
3614
|
gif: "image/gif",
|
|
3927
|
-
// Non-model-safe rasters `toModelSafeImage` transcodes to PNG at ingest — they
|
|
3928
|
-
// must resolve to an image mime here or the kind-check rejects the local file
|
|
3929
|
-
// before normalization ever runs.
|
|
3930
3615
|
avif: "image/avif",
|
|
3931
|
-
heic: "image/heic",
|
|
3932
|
-
heif: "image/heif",
|
|
3933
|
-
tif: "image/tiff",
|
|
3934
|
-
tiff: "image/tiff",
|
|
3935
|
-
bmp: "image/bmp",
|
|
3936
3616
|
mp4: "video/mp4",
|
|
3937
3617
|
webm: "video/webm",
|
|
3938
3618
|
mov: "video/quicktime",
|
|
@@ -3983,45 +3663,15 @@ async function rasterizeSvgToPng(bytes) {
|
|
|
3983
3663
|
}
|
|
3984
3664
|
return await sharp(bytes, { density }).png({ force: true, palette: false }).toBuffer();
|
|
3985
3665
|
}
|
|
3986
|
-
var MODEL_SAFE_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
|
|
3987
|
-
async function toModelSafeImage(bytes) {
|
|
3988
|
-
const safe = sniffImageMime(bytes);
|
|
3989
|
-
if (safe && MODEL_SAFE_IMAGE_MIMES.has(safe)) {
|
|
3990
|
-
return { bytes, mime: safe };
|
|
3991
|
-
}
|
|
3992
|
-
if (sniffSvg(bytes)) {
|
|
3993
|
-
return { bytes: await rasterizeSvgToPng(bytes), mime: "image/png", rasterizedFrom: "svg" };
|
|
3994
|
-
}
|
|
3995
|
-
const { default: sharp } = await import("sharp");
|
|
3996
|
-
try {
|
|
3997
|
-
const img = sharp(bytes);
|
|
3998
|
-
const format = (await img.metadata()).format;
|
|
3999
|
-
const png = await img.png({ force: true }).toBuffer();
|
|
4000
|
-
return { bytes: png, mime: "image/png", rasterizedFrom: format ?? "unknown" };
|
|
4001
|
-
} catch (e) {
|
|
4002
|
-
throw new Error(`bytes are not a decodable image (${e.message})`);
|
|
4003
|
-
}
|
|
4004
|
-
}
|
|
4005
|
-
function hasAscii(buf, offset, sig) {
|
|
4006
|
-
return buf.length >= offset + sig.length && buf.toString("ascii", offset, offset + sig.length) === sig;
|
|
4007
|
-
}
|
|
4008
|
-
var HEIC_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "heim", "heis", "hevc", "hevx", "mif1", "msf1", "heif"]);
|
|
4009
|
-
function sniffIsoBmff(buf) {
|
|
4010
|
-
if (!hasAscii(buf, 4, "ftyp")) return null;
|
|
4011
|
-
const brand = buf.subarray(8, 12).toString("ascii");
|
|
4012
|
-
if (brand === "avif" || brand === "avis") return "image/avif";
|
|
4013
|
-
if (HEIC_BRANDS.has(brand)) return "image/heic";
|
|
4014
|
-
return null;
|
|
4015
|
-
}
|
|
4016
3666
|
function sniffImageMime(buf) {
|
|
4017
3667
|
if (buf.length < 4) return null;
|
|
4018
|
-
if (buf[0] === 137 &&
|
|
3668
|
+
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
4019
3669
|
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
4020
|
-
if (
|
|
4021
|
-
if (
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
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;
|
|
4025
3675
|
}
|
|
4026
3676
|
function findBoxPayload(buf, start, end, type) {
|
|
4027
3677
|
let offset = start;
|
|
@@ -4074,10 +3724,10 @@ function inferKindFromMime(mime) {
|
|
|
4074
3724
|
if (mime.startsWith("font/")) return "font";
|
|
4075
3725
|
return null;
|
|
4076
3726
|
}
|
|
4077
|
-
function localExecError(ctx,
|
|
3727
|
+
function localExecError(ctx, message) {
|
|
4078
3728
|
return new NodeExecutionError(ctx.nodeId, ctx.nodeType, {
|
|
4079
3729
|
kind: "local",
|
|
4080
|
-
cause: new Error(`ingest: ${
|
|
3730
|
+
cause: new Error(`ingest: ${message}`)
|
|
4081
3731
|
});
|
|
4082
3732
|
}
|
|
4083
3733
|
async function execLocalFile(params, ctx) {
|
|
@@ -4124,20 +3774,17 @@ async function execLocalFile(params, ctx) {
|
|
|
4124
3774
|
ctx.log(`ingest: local file ${stats.size}B mime=${mime}`);
|
|
4125
3775
|
let outBytes = bytes;
|
|
4126
3776
|
let outMime = mime;
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
outMime = normalized.mime;
|
|
4132
|
-
rasterizedFrom = normalized.rasterizedFrom;
|
|
4133
|
-
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)`);
|
|
4134
3781
|
}
|
|
4135
3782
|
const durationMs = probeVideoDurationMs(params.expect, outBytes, ctx);
|
|
4136
3783
|
const ref = await uploadAndIngest({
|
|
4137
3784
|
bytes: outBytes,
|
|
4138
3785
|
kind: params.expect,
|
|
4139
3786
|
mime: outMime,
|
|
4140
|
-
metadata: localFileMetadata({ absPath, fileSize: stats.size, mime, durationMs
|
|
3787
|
+
metadata: localFileMetadata({ absPath, fileSize: stats.size, mime, durationMs }),
|
|
4141
3788
|
ctx
|
|
4142
3789
|
});
|
|
4143
3790
|
return withProbedDuration(ref, durationMs);
|
|
@@ -4155,7 +3802,7 @@ function localFileMetadata(args) {
|
|
|
4155
3802
|
ingested_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4156
3803
|
file_size: args.fileSize,
|
|
4157
3804
|
original_filename: path3.basename(args.absPath),
|
|
4158
|
-
...args.
|
|
3805
|
+
...args.mime === SVG_MIME ? { rasterized_from: "svg" } : {},
|
|
4159
3806
|
...args.durationMs !== void 0 ? { duration_ms: args.durationMs } : {}
|
|
4160
3807
|
};
|
|
4161
3808
|
}
|
|
@@ -4747,7 +4394,7 @@ async function refToUrl(ref) {
|
|
|
4747
4394
|
return `data:${ref.mime};base64,${bytes.toString("base64")}`;
|
|
4748
4395
|
}
|
|
4749
4396
|
var ASSET_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "json", "text", "font"]);
|
|
4750
|
-
function
|
|
4397
|
+
function isAssetRefLike(value) {
|
|
4751
4398
|
if (!value || typeof value !== "object") return false;
|
|
4752
4399
|
const v = value;
|
|
4753
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");
|
|
@@ -5119,8 +4766,8 @@ var NEVER_BLOCK = [
|
|
|
5119
4766
|
/text[_-]?occluded/i
|
|
5120
4767
|
];
|
|
5121
4768
|
var UNAVAILABLE = /unknown command|command not found|not found|Did you mean|Unknown argument|ENOENT/i;
|
|
5122
|
-
function isAdvisory(code,
|
|
5123
|
-
const hay = `${code} ${
|
|
4769
|
+
function isAdvisory(code, message) {
|
|
4770
|
+
const hay = `${code} ${message}`;
|
|
5124
4771
|
return NEVER_BLOCK.some((re) => re.test(hay));
|
|
5125
4772
|
}
|
|
5126
4773
|
function parseCheckJson(raw) {
|
|
@@ -5148,10 +4795,10 @@ function classifyLint(json) {
|
|
|
5148
4795
|
for (const f of findings) {
|
|
5149
4796
|
const rec = f;
|
|
5150
4797
|
const code = String(rec?.code ?? "");
|
|
5151
|
-
const
|
|
4798
|
+
const message = String(rec?.message ?? "");
|
|
5152
4799
|
const severity = String(rec?.severity ?? "info");
|
|
5153
|
-
const blocking = severity === "error" && !isAdvisory(code,
|
|
5154
|
-
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" });
|
|
5155
4802
|
}
|
|
5156
4803
|
return out;
|
|
5157
4804
|
}
|
|
@@ -5163,9 +4810,9 @@ function classifyInspect(json) {
|
|
|
5163
4810
|
for (const iss of issues) {
|
|
5164
4811
|
const rec = iss;
|
|
5165
4812
|
const code = String(rec?.code ?? rec?.type ?? "overflow");
|
|
5166
|
-
const
|
|
4813
|
+
const message = String(rec?.message ?? rec?.detail ?? JSON.stringify(iss));
|
|
5167
4814
|
const severity = rec?.severity ? String(rec.severity) : obj?.ok === false ? "error" : "warning";
|
|
5168
|
-
out.push({ source: "inspect", code, message
|
|
4815
|
+
out.push({ source: "inspect", code, message, severity: severity === "error" ? "blocking" : "warning" });
|
|
5169
4816
|
}
|
|
5170
4817
|
return out;
|
|
5171
4818
|
}
|
|
@@ -5610,7 +5257,7 @@ async function buildSubstitutionValues(compositionParams, meta, duration) {
|
|
|
5610
5257
|
}
|
|
5611
5258
|
function coerceImageParam(value) {
|
|
5612
5259
|
if (typeof value === "string") return Promise.resolve(value);
|
|
5613
|
-
if (
|
|
5260
|
+
if (isAssetRefLike(value)) return refToUrl(value);
|
|
5614
5261
|
throw new Error("hyperframe_render: image param must be a URL string or AssetRef");
|
|
5615
5262
|
}
|
|
5616
5263
|
async function substituteCompositionFiles(tmp, values) {
|
|
@@ -5840,7 +5487,7 @@ async function buildSubstitutionValues2(compositionParams, meta) {
|
|
|
5840
5487
|
}
|
|
5841
5488
|
function coerceImageParam2(value) {
|
|
5842
5489
|
if (typeof value === "string") return Promise.resolve(value);
|
|
5843
|
-
if (
|
|
5490
|
+
if (isAssetRefLike(value)) return refToUrl(value);
|
|
5844
5491
|
throw new Error("hyperframe_snapshot: image param must be a URL string or AssetRef");
|
|
5845
5492
|
}
|
|
5846
5493
|
async function substituteCompositionFiles2(tmp, values) {
|
|
@@ -6866,29 +6513,17 @@ function createEngineFromEnv(opts = {}) {
|
|
|
6866
6513
|
const cacheDir = opts.cacheDir ?? path15.join(cwd, "canvas", ".cache");
|
|
6867
6514
|
const outputsDir = opts.outputsDir ?? path15.join(cwd, "canvas");
|
|
6868
6515
|
const creds = requireCredentialsFromEnv();
|
|
6869
|
-
const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
|
|
6870
|
-
const assets = new LocalAssetStore(path15.join(cacheDir, "assets"));
|
|
6871
|
-
const localCache = new LocalCacheStore(path15.join(cacheDir, "index"));
|
|
6872
|
-
const remoteCacheEnabled = opts.remoteCache ?? remoteCacheEnabledFromEnv();
|
|
6873
|
-
const cache = remoteCacheEnabled ? new LayeredCacheStore({
|
|
6874
|
-
local: localCache,
|
|
6875
|
-
remote: new RemoteCacheStore(client, opts.log),
|
|
6876
|
-
assets,
|
|
6877
|
-
log: opts.log
|
|
6878
|
-
}) : localCache;
|
|
6879
6516
|
return new Engine({
|
|
6880
6517
|
registry: defaultRegistry(),
|
|
6881
|
-
client,
|
|
6882
|
-
assets,
|
|
6883
|
-
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")),
|
|
6884
6521
|
outputsDir,
|
|
6885
|
-
log: opts.log
|
|
6886
|
-
persistAssets: remoteCacheEnabled
|
|
6522
|
+
log: opts.log
|
|
6887
6523
|
});
|
|
6888
6524
|
}
|
|
6889
6525
|
|
|
6890
6526
|
export {
|
|
6891
|
-
requireCredentialsFromEnv,
|
|
6892
6527
|
LayerExecutionError,
|
|
6893
6528
|
describeFailureReason,
|
|
6894
6529
|
SEEDANCE_DURATIONS,
|
|
@@ -6896,14 +6531,7 @@ export {
|
|
|
6896
6531
|
IMAGE_GENERATE_MODELS,
|
|
6897
6532
|
MODEL_REGISTRY,
|
|
6898
6533
|
resolveConcurrency,
|
|
6899
|
-
ulid,
|
|
6900
|
-
isPersistedAssetRef,
|
|
6901
|
-
collectAssetRefLikes,
|
|
6902
|
-
REF_PREFIX,
|
|
6903
|
-
parseRefExpr,
|
|
6904
|
-
sha256Hex,
|
|
6905
6534
|
elementMentionKeywords,
|
|
6906
|
-
toModelSafeImage,
|
|
6907
6535
|
BackendClient2 as BackendClient,
|
|
6908
6536
|
Engine2 as Engine,
|
|
6909
6537
|
LocalAssetStore2 as LocalAssetStore,
|
|
@@ -6914,4 +6542,4 @@ export {
|
|
|
6914
6542
|
defaultRegistry,
|
|
6915
6543
|
createEngineFromEnv
|
|
6916
6544
|
};
|
|
6917
|
-
//# sourceMappingURL=chunk-
|
|
6545
|
+
//# sourceMappingURL=chunk-MWFJ5NOP.js.map
|