@koda-sl/baker-cli 0.114.0-dev.249eaa8ed → 0.115.0-dev.3bcc79f9c
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 +61 -0
- package/dist/{chunk-5AWO4BHJ.js → chunk-OCMOQOIJ.js} +401 -69
- package/dist/chunk-OCMOQOIJ.js.map +1 -0
- package/dist/cli.js +551 -108
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +44 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5AWO4BHJ.js.map +0 -1
|
@@ -159,9 +159,9 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
159
159
|
}
|
|
160
160
|
if (value) {
|
|
161
161
|
return (value2) => {
|
|
162
|
-
let
|
|
163
|
-
if (typeof value2 !== "function")
|
|
164
|
-
throw new Error(
|
|
162
|
+
let message2 = `Object can not safely be stringified. Received type ${typeof value2}`;
|
|
163
|
+
if (typeof value2 !== "function") message2 += ` (${value2.toString()})`;
|
|
164
|
+
throw new Error(message2);
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
}
|
|
@@ -624,6 +624,7 @@ ${originalIndentation}`;
|
|
|
624
624
|
import path15 from "path";
|
|
625
625
|
|
|
626
626
|
// src/engine/client/http.ts
|
|
627
|
+
var CONTENT_POLICY_CODE = "content_policy_blocked";
|
|
627
628
|
var BackendHttpError = class extends Error {
|
|
628
629
|
detail;
|
|
629
630
|
constructor(detail) {
|
|
@@ -640,6 +641,8 @@ function describe(d) {
|
|
|
640
641
|
return `invalid request: ${d.message}`;
|
|
641
642
|
case "provider":
|
|
642
643
|
return `provider error${d.provider ? ` (${d.provider})` : ""}: ${d.message}`;
|
|
644
|
+
case "content_policy":
|
|
645
|
+
return `content policy blocked${d.provider ? ` (${d.provider})` : ""}: ${d.message}`;
|
|
643
646
|
case "timeout":
|
|
644
647
|
return `provider timeout${d.provider ? ` (${d.provider})` : ""}: ${d.message}`;
|
|
645
648
|
case "server":
|
|
@@ -670,6 +673,9 @@ var HttpClient = class {
|
|
|
670
673
|
async postJson(path16, body, signal) {
|
|
671
674
|
return await this.requestJson("POST", path16, body, signal);
|
|
672
675
|
}
|
|
676
|
+
async putJson(path16, body, signal) {
|
|
677
|
+
return await this.requestJson("PUT", path16, body, signal);
|
|
678
|
+
}
|
|
673
679
|
async getJson(path16, signal) {
|
|
674
680
|
return await this.requestJson("GET", path16, void 0, signal);
|
|
675
681
|
}
|
|
@@ -697,8 +703,8 @@ var HttpClient = class {
|
|
|
697
703
|
try {
|
|
698
704
|
const res = await this.fetchFn(url, {
|
|
699
705
|
method,
|
|
700
|
-
headers: method === "
|
|
701
|
-
body: method === "
|
|
706
|
+
headers: method === "GET" ? { Authorization: `Bearer ${this.apiKey}` } : { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` },
|
|
707
|
+
body: method === "GET" ? void 0 : JSON.stringify(body),
|
|
702
708
|
signal: controller.signal
|
|
703
709
|
});
|
|
704
710
|
if (res.ok) return { kind: "value", value: await res.json() };
|
|
@@ -735,30 +741,33 @@ async function parseErrorBody(res) {
|
|
|
735
741
|
const errObj = body.error ?? {};
|
|
736
742
|
return classifyHttpError(res.status, errObj, errObj.message ?? `HTTP ${res.status}`);
|
|
737
743
|
}
|
|
738
|
-
function classifyHttpError(status, errObj,
|
|
744
|
+
function classifyHttpError(status, errObj, message2) {
|
|
745
|
+
if (errObj.code === CONTENT_POLICY_CODE) {
|
|
746
|
+
return { kind: "content_policy", status, provider: errObj.provider, message: message2 };
|
|
747
|
+
}
|
|
739
748
|
if (status === 401 || status === 403) {
|
|
740
|
-
return { kind: "unauthorized", status, message };
|
|
749
|
+
return { kind: "unauthorized", status, message: message2 };
|
|
741
750
|
}
|
|
742
751
|
if (status === 400 || status === 422) {
|
|
743
|
-
return { kind: "validation", status, message, details: errObj.details };
|
|
752
|
+
return { kind: "validation", status, message: message2, details: errObj.details };
|
|
744
753
|
}
|
|
745
754
|
if (status === 502 || status === 504) {
|
|
746
755
|
if (errObj.code === "provider_timeout" || status === 504) {
|
|
747
|
-
return { kind: "timeout", provider: errObj.provider, message };
|
|
756
|
+
return { kind: "timeout", provider: errObj.provider, message: message2 };
|
|
748
757
|
}
|
|
749
758
|
return {
|
|
750
759
|
kind: "provider",
|
|
751
760
|
status,
|
|
752
761
|
provider: errObj.provider,
|
|
753
762
|
code: errObj.code ?? "provider_error",
|
|
754
|
-
message,
|
|
763
|
+
message: message2,
|
|
755
764
|
retryable: errObj.retryable ?? true
|
|
756
765
|
};
|
|
757
766
|
}
|
|
758
767
|
if (status >= 500 || status === 429) {
|
|
759
|
-
return { kind: "server", status, message };
|
|
768
|
+
return { kind: "server", status, message: message2 };
|
|
760
769
|
}
|
|
761
|
-
return { kind: "validation", status, message, details: errObj.details };
|
|
770
|
+
return { kind: "validation", status, message: message2, details: errObj.details };
|
|
762
771
|
}
|
|
763
772
|
function backoffMs(attempt) {
|
|
764
773
|
return 1e3 * 2 ** attempt;
|
|
@@ -774,6 +783,24 @@ function isAsyncJob(res) {
|
|
|
774
783
|
return typeof res.job_id === "string";
|
|
775
784
|
}
|
|
776
785
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
786
|
+
function failedJobError(error) {
|
|
787
|
+
if (error.code === CONTENT_POLICY_CODE) {
|
|
788
|
+
return new BackendHttpError({
|
|
789
|
+
kind: "content_policy",
|
|
790
|
+
status: error.status ?? 422,
|
|
791
|
+
provider: error.provider,
|
|
792
|
+
message: error.message ?? "content policy blocked"
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
return new BackendHttpError({
|
|
796
|
+
kind: "provider",
|
|
797
|
+
status: error.status ?? 502,
|
|
798
|
+
provider: error.provider,
|
|
799
|
+
code: error.code ?? "provider_error",
|
|
800
|
+
message: error.message ?? "deconstruct failed",
|
|
801
|
+
retryable: error.retryable ?? false
|
|
802
|
+
});
|
|
803
|
+
}
|
|
777
804
|
var JOB_POLL_INTERVAL_MS = 3e3;
|
|
778
805
|
var JOB_POLL_MAX_MS = 20 * 60 * 1e3;
|
|
779
806
|
var BackendClient = class {
|
|
@@ -797,16 +824,7 @@ var BackendClient = class {
|
|
|
797
824
|
}
|
|
798
825
|
const job = await this.http.getJson(path16, signal);
|
|
799
826
|
if (job.status === "completed") return job.result;
|
|
800
|
-
if (job.status === "failed")
|
|
801
|
-
throw new BackendHttpError({
|
|
802
|
-
kind: "provider",
|
|
803
|
-
status: job.error.status ?? 502,
|
|
804
|
-
provider: job.error.provider,
|
|
805
|
-
code: job.error.code ?? "provider_error",
|
|
806
|
-
message: job.error.message ?? "deconstruct failed",
|
|
807
|
-
retryable: job.error.retryable ?? false
|
|
808
|
-
});
|
|
809
|
-
}
|
|
827
|
+
if (job.status === "failed") throw failedJobError(job.error);
|
|
810
828
|
if (Date.now() > deadline) {
|
|
811
829
|
throw new BackendHttpError({ kind: "timeout", message: `job ${jobId} did not finish in time` });
|
|
812
830
|
}
|
|
@@ -820,6 +838,27 @@ var BackendClient = class {
|
|
|
820
838
|
signal
|
|
821
839
|
);
|
|
822
840
|
}
|
|
841
|
+
/** Remote cache lookup. A miss (404) — or an old backend without the route — returns null. */
|
|
842
|
+
async getCacheEntry(cacheKey, signal) {
|
|
843
|
+
try {
|
|
844
|
+
const res = await this.http.getJson(`/api/canvas/cache/${encodeURIComponent(cacheKey)}`, signal);
|
|
845
|
+
return res.entry;
|
|
846
|
+
} catch (e) {
|
|
847
|
+
if (e instanceof BackendHttpError && "status" in e.detail && e.detail.status === 404) return null;
|
|
848
|
+
throw e;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
async putCacheEntry(entry, signal) {
|
|
852
|
+
await this.http.putJson(
|
|
853
|
+
`/api/canvas/cache/${encodeURIComponent(entry.cacheKey)}`,
|
|
854
|
+
entry,
|
|
855
|
+
signal
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
/** Durable run-history record — POST /api/canvas/runs (idempotent server-side on runId). */
|
|
859
|
+
async recordRun(payload, signal) {
|
|
860
|
+
await this.http.postJson("/api/canvas/runs", payload, signal);
|
|
861
|
+
}
|
|
823
862
|
getArtifact(kind, name, version, signal) {
|
|
824
863
|
const path16 = version ? `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}` : `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`;
|
|
825
864
|
return this.http.getJson(path16, signal);
|
|
@@ -843,11 +882,17 @@ function requireCredentialsFromEnv(env = process.env) {
|
|
|
843
882
|
}
|
|
844
883
|
return c;
|
|
845
884
|
}
|
|
885
|
+
function remoteCacheEnabledFromEnv(env = process.env) {
|
|
886
|
+
return env.BAKER_CANVAS_REMOTE_CACHE !== "off";
|
|
887
|
+
}
|
|
846
888
|
|
|
847
889
|
// src/engine/engine/errors.ts
|
|
890
|
+
function isBlocking(issue) {
|
|
891
|
+
return issue.severity !== "warning";
|
|
892
|
+
}
|
|
848
893
|
var CanvasError = class extends Error {
|
|
849
|
-
constructor(
|
|
850
|
-
super(
|
|
894
|
+
constructor(message2) {
|
|
895
|
+
super(message2);
|
|
851
896
|
this.name = "CanvasError";
|
|
852
897
|
}
|
|
853
898
|
};
|
|
@@ -895,6 +940,8 @@ function describeCause(c) {
|
|
|
895
940
|
return `timeout${c.provider ? ` (${c.provider})` : ""}`;
|
|
896
941
|
case "network":
|
|
897
942
|
return c.cause instanceof Error ? `network: ${c.cause.message}` : "network error";
|
|
943
|
+
case "content_policy":
|
|
944
|
+
return `CONTENT_POLICY_BLOCKED${c.provider ? ` (${c.provider})` : ""}: ${c.message}`;
|
|
898
945
|
default: {
|
|
899
946
|
const _exhaustive = c;
|
|
900
947
|
return String(_exhaustive);
|
|
@@ -1563,6 +1610,158 @@ function encodeRandom() {
|
|
|
1563
1610
|
return out;
|
|
1564
1611
|
}
|
|
1565
1612
|
|
|
1613
|
+
// src/engine/storage/remote-cache-store.ts
|
|
1614
|
+
var CANVAS_ASSETS_URL_SEGMENT = "/canvas-assets/";
|
|
1615
|
+
function isPersistedAssetRef(ref) {
|
|
1616
|
+
return typeof ref.url === "string" && ref.url.includes(`${CANVAS_ASSETS_URL_SEGMENT}${ref.sha256}`);
|
|
1617
|
+
}
|
|
1618
|
+
function isAssetRefLike(value) {
|
|
1619
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.sha256 === "string" && typeof value.mime === "string";
|
|
1620
|
+
}
|
|
1621
|
+
function collectAssetRefLikes(value, out = []) {
|
|
1622
|
+
if (Array.isArray(value)) {
|
|
1623
|
+
for (const item of value) collectAssetRefLikes(item, out);
|
|
1624
|
+
return out;
|
|
1625
|
+
}
|
|
1626
|
+
if (typeof value !== "object" || value === null) return out;
|
|
1627
|
+
if (isAssetRefLike(value)) {
|
|
1628
|
+
out.push(value);
|
|
1629
|
+
}
|
|
1630
|
+
for (const item of Object.values(value)) collectAssetRefLikes(item, out);
|
|
1631
|
+
return out;
|
|
1632
|
+
}
|
|
1633
|
+
function entryFullyPersisted(entry) {
|
|
1634
|
+
return collectAssetRefLikes(entry.outputs).every((ref) => isPersistedAssetRef(ref));
|
|
1635
|
+
}
|
|
1636
|
+
function stripLocalFields(entry) {
|
|
1637
|
+
const clone = JSON.parse(JSON.stringify(entry));
|
|
1638
|
+
for (const ref of collectAssetRefLikes(clone.outputs)) {
|
|
1639
|
+
delete ref.path;
|
|
1640
|
+
delete ref.bytes;
|
|
1641
|
+
}
|
|
1642
|
+
return clone;
|
|
1643
|
+
}
|
|
1644
|
+
var RemoteCacheStore = class {
|
|
1645
|
+
client;
|
|
1646
|
+
log;
|
|
1647
|
+
constructor(client, log) {
|
|
1648
|
+
this.client = client;
|
|
1649
|
+
this.log = log ?? (() => void 0);
|
|
1650
|
+
}
|
|
1651
|
+
async get(cacheKey) {
|
|
1652
|
+
return await this.client.getCacheEntry(cacheKey);
|
|
1653
|
+
}
|
|
1654
|
+
async put(entry) {
|
|
1655
|
+
if (!entryFullyPersisted(entry)) {
|
|
1656
|
+
this.log(`[cache ] ${entry.cacheKey.slice(0, 12)}\u2026 has local-only assets, kept local`);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
const stripped = stripLocalFields(entry);
|
|
1660
|
+
if (stripped.refs.length > MAX_REMOTE_REFS) {
|
|
1661
|
+
stripped.refs = stripped.refs.slice(0, MAX_REMOTE_REFS);
|
|
1662
|
+
}
|
|
1663
|
+
await this.client.putCacheEntry(stripped);
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
var MAX_REMOTE_REFS = 512;
|
|
1667
|
+
var LayeredCacheStore = class {
|
|
1668
|
+
rootDir;
|
|
1669
|
+
local;
|
|
1670
|
+
remote;
|
|
1671
|
+
assets;
|
|
1672
|
+
log;
|
|
1673
|
+
constructor(opts) {
|
|
1674
|
+
this.local = opts.local;
|
|
1675
|
+
this.remote = opts.remote;
|
|
1676
|
+
this.assets = opts.assets;
|
|
1677
|
+
this.rootDir = opts.local.rootDir;
|
|
1678
|
+
this.log = opts.log ?? (() => void 0);
|
|
1679
|
+
}
|
|
1680
|
+
async get(cacheKey) {
|
|
1681
|
+
const localHit = await this.local.get(cacheKey);
|
|
1682
|
+
if (localHit) return localHit;
|
|
1683
|
+
let remoteEntry;
|
|
1684
|
+
try {
|
|
1685
|
+
remoteEntry = await this.remote.get(cacheKey);
|
|
1686
|
+
} catch (e) {
|
|
1687
|
+
this.log(`[cache ] remote lookup failed (${message(e)}) \u2014 treating as miss`);
|
|
1688
|
+
return null;
|
|
1689
|
+
}
|
|
1690
|
+
if (!remoteEntry) return null;
|
|
1691
|
+
let rehydrated;
|
|
1692
|
+
try {
|
|
1693
|
+
rehydrated = await this.rehydrate(remoteEntry);
|
|
1694
|
+
} catch (e) {
|
|
1695
|
+
this.log(`[cache ] ${cacheKey.slice(0, 12)}\u2026 rehydration failed (${message(e)}) \u2014 treating as miss`);
|
|
1696
|
+
return null;
|
|
1697
|
+
}
|
|
1698
|
+
await this.local.put(rehydrated);
|
|
1699
|
+
return rehydrated;
|
|
1700
|
+
}
|
|
1701
|
+
async put(entry) {
|
|
1702
|
+
await this.local.put(entry);
|
|
1703
|
+
try {
|
|
1704
|
+
await this.remote.put(entry);
|
|
1705
|
+
} catch (e) {
|
|
1706
|
+
this.log(`[cache ] remote write failed (${message(e)}) \u2014 entry kept local`);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Download every referenced asset into the local content-addressed store
|
|
1711
|
+
* (sha-verified) and stamp fresh local paths. Any ref that cannot be
|
|
1712
|
+
* rehydrated fails the WHOLE entry — a partially-hydrated cache hit would
|
|
1713
|
+
* crash materialization later with a far less actionable error.
|
|
1714
|
+
*/
|
|
1715
|
+
async rehydrate(entry) {
|
|
1716
|
+
const clone = JSON.parse(JSON.stringify(entry));
|
|
1717
|
+
for (const ref of collectAssetRefLikes(clone.outputs)) {
|
|
1718
|
+
if (!isPersistedAssetRef(ref)) {
|
|
1719
|
+
throw new Error(`ref ${ref.sha256.slice(0, 12)}\u2026 has no persisted url`);
|
|
1720
|
+
}
|
|
1721
|
+
const ingested = await this.assets.ingestRemote({
|
|
1722
|
+
kind: typeof ref.kind === "string" ? ref.kind : "json",
|
|
1723
|
+
url: ref.url,
|
|
1724
|
+
sha256: ref.sha256,
|
|
1725
|
+
mime: ref.mime,
|
|
1726
|
+
metadata: ref.metadata ?? void 0
|
|
1727
|
+
});
|
|
1728
|
+
ref.path = ingested.path;
|
|
1729
|
+
}
|
|
1730
|
+
return clone;
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
function message(e) {
|
|
1734
|
+
return e instanceof Error ? e.message : String(e);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// src/engine/nodes/remote/upload.ts
|
|
1738
|
+
async function presignAndPut(args) {
|
|
1739
|
+
const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
|
|
1740
|
+
const putRes = await fetch(putUrl, {
|
|
1741
|
+
method: "PUT",
|
|
1742
|
+
body: new Uint8Array(args.bytes),
|
|
1743
|
+
headers: { "Content-Type": args.mime },
|
|
1744
|
+
signal: args.ctx.signal
|
|
1745
|
+
});
|
|
1746
|
+
if (!putRes.ok) {
|
|
1747
|
+
throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
|
|
1748
|
+
}
|
|
1749
|
+
return publicUrl;
|
|
1750
|
+
}
|
|
1751
|
+
async function ensureUploaded(ref, ctx) {
|
|
1752
|
+
if (ref.url) return ref;
|
|
1753
|
+
const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
|
|
1754
|
+
const url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
|
|
1755
|
+
return { ...ref, url };
|
|
1756
|
+
}
|
|
1757
|
+
async function persistOutputAssetUrls(outputs, ctx) {
|
|
1758
|
+
for (const ref of collectAssetRefLikes(outputs)) {
|
|
1759
|
+
if (isPersistedAssetRef(ref)) continue;
|
|
1760
|
+
const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
|
|
1761
|
+
ref.url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1566
1765
|
// src/engine/schema/canvas.ts
|
|
1567
1766
|
import { z } from "zod";
|
|
1568
1767
|
var REF_PREFIX = "$ref:";
|
|
@@ -1638,7 +1837,23 @@ var VideoMeta = z.object({
|
|
|
1638
1837
|
// on which spoken beat" map emitted by scaffold-video (per-scene window,
|
|
1639
1838
|
// spoken line, storyboard frames, scheduled graphics). Free-form rows so the
|
|
1640
1839
|
// schema stays decoupled from the scaffold's exact shape.
|
|
1641
|
-
motion_board: z.array(z.unknown()).optional()
|
|
1840
|
+
motion_board: z.array(z.unknown()).optional(),
|
|
1841
|
+
// The recurring identity elements the scaffold wired (one shared reference slot
|
|
1842
|
+
// per cast member / pet / product / logo). The reference-completeness check reads
|
|
1843
|
+
// this to warn when a frame's description mentions an element whose ref isn't
|
|
1844
|
+
// wired on that frame node (the wrong-identity-animal failure).
|
|
1845
|
+
elements: z.array(
|
|
1846
|
+
z.object({
|
|
1847
|
+
ref: z.string(),
|
|
1848
|
+
label: z.string(),
|
|
1849
|
+
type: z.string(),
|
|
1850
|
+
description: z.string().optional()
|
|
1851
|
+
})
|
|
1852
|
+
).optional(),
|
|
1853
|
+
// Per video_generate node: the scene's natural visual span. The validator warns
|
|
1854
|
+
// when a clip's span exceeds the assigned model's max clip duration (e.g. a 9.2s
|
|
1855
|
+
// scene on Veo, which caps at 8s) — the clip would truncate.
|
|
1856
|
+
clip_spans: z.array(z.object({ node: z.string(), span_s: z.number() })).optional()
|
|
1642
1857
|
}).strict().optional();
|
|
1643
1858
|
var CanvasMetadata = z.object({
|
|
1644
1859
|
name: z.string().optional(),
|
|
@@ -2136,8 +2351,11 @@ var STAGE_CODES = {
|
|
|
2136
2351
|
AUDIO_DURATION: "VIDEO_AUDIO_DURATION",
|
|
2137
2352
|
LIPSYNC_MISSING: "VIDEO_LIPSYNC_MISSING",
|
|
2138
2353
|
SPEECH_OVERRUN: "VIDEO_SPEECH_OVERRUN",
|
|
2139
|
-
ASPECT_MISMATCH: "VIDEO_ASPECT_MISMATCH"
|
|
2354
|
+
ASPECT_MISMATCH: "VIDEO_ASPECT_MISMATCH",
|
|
2355
|
+
REFERENCE_MISSING: "VIDEO_REFERENCE_MISSING",
|
|
2356
|
+
SPAN_EXCEEDS_MODEL: "VIDEO_SPAN_EXCEEDS_MODEL"
|
|
2140
2357
|
};
|
|
2358
|
+
var SPAN_MODEL_SLACK_S = 0.25;
|
|
2141
2359
|
var VIDEO_TIME_SLACK_S = 0.75;
|
|
2142
2360
|
var SPEECH_WORDS_PER_SECOND = 2.5;
|
|
2143
2361
|
var SPEECH_OVERRUN_RATIO = 1.6;
|
|
@@ -2168,8 +2386,10 @@ function validateCanvas(input, registry) {
|
|
|
2168
2386
|
const estimatedCredits = estimateCredits(ctx);
|
|
2169
2387
|
checkOutputRef(ctx);
|
|
2170
2388
|
checkVideoInvariants(ctx);
|
|
2171
|
-
|
|
2172
|
-
return { ok:
|
|
2389
|
+
const hasBlocking = issues.some(isBlocking);
|
|
2390
|
+
if (hasBlocking) return { ok: false, issues };
|
|
2391
|
+
const warnings = issues.filter((i) => !isBlocking(i));
|
|
2392
|
+
return { ok: true, canvas, estimatedCredits, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2173
2393
|
}
|
|
2174
2394
|
async function validateCanvasDeep(input, registry) {
|
|
2175
2395
|
const shallow = validateCanvas(input, registry);
|
|
@@ -2204,7 +2424,9 @@ async function validateCanvasDeep(input, registry) {
|
|
|
2204
2424
|
});
|
|
2205
2425
|
}
|
|
2206
2426
|
}
|
|
2207
|
-
|
|
2427
|
+
const hasBlocking = issues.some(isBlocking);
|
|
2428
|
+
if (hasBlocking) return { ok: false, issues };
|
|
2429
|
+
const warnings = [...shallow.warnings ?? [], ...issues.filter((i) => !isBlocking(i))];
|
|
2208
2430
|
const perNodeCredits = canvas.nodes.map((n) => {
|
|
2209
2431
|
const def = registry.get(n.type);
|
|
2210
2432
|
let credits = 0;
|
|
@@ -2217,7 +2439,13 @@ async function validateCanvasDeep(input, registry) {
|
|
|
2217
2439
|
}
|
|
2218
2440
|
return { node_id: n.id, node_type: n.type, credits };
|
|
2219
2441
|
});
|
|
2220
|
-
return {
|
|
2442
|
+
return {
|
|
2443
|
+
ok: true,
|
|
2444
|
+
canvas,
|
|
2445
|
+
estimatedCredits: shallow.estimatedCredits,
|
|
2446
|
+
perNodeCredits,
|
|
2447
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
2448
|
+
};
|
|
2221
2449
|
}
|
|
2222
2450
|
function buildIdToIndex(canvas) {
|
|
2223
2451
|
const m = /* @__PURE__ */ new Map();
|
|
@@ -2504,6 +2732,95 @@ function checkVideoInvariants(ctx) {
|
|
|
2504
2732
|
}
|
|
2505
2733
|
checkSpeechOverrun(ctx, meta.talking_scenes);
|
|
2506
2734
|
checkAspectConsistency(ctx);
|
|
2735
|
+
checkReferenceCompleteness(ctx, meta);
|
|
2736
|
+
checkClipSpanFitsModel(ctx, meta);
|
|
2737
|
+
}
|
|
2738
|
+
var ELEMENT_TYPE_KEYWORDS = {
|
|
2739
|
+
animal: ["dog", "puppy", "pup", "cat", "kitten", "kitty", "pet", "canine", "feline"],
|
|
2740
|
+
logo: ["logo", "wordmark"],
|
|
2741
|
+
badge: ["badge", "seal"],
|
|
2742
|
+
product: []
|
|
2743
|
+
};
|
|
2744
|
+
var CHECKED_ELEMENT_TYPES = new Set(Object.keys(ELEMENT_TYPE_KEYWORDS));
|
|
2745
|
+
var KEYWORD_STOPWORDS = /* @__PURE__ */ new Set([
|
|
2746
|
+
"the",
|
|
2747
|
+
"and",
|
|
2748
|
+
"with",
|
|
2749
|
+
"for",
|
|
2750
|
+
"brand",
|
|
2751
|
+
"logo",
|
|
2752
|
+
"image",
|
|
2753
|
+
"shot",
|
|
2754
|
+
"main",
|
|
2755
|
+
"hero",
|
|
2756
|
+
"element",
|
|
2757
|
+
"product",
|
|
2758
|
+
"reference"
|
|
2759
|
+
]);
|
|
2760
|
+
function keywordTokens(text) {
|
|
2761
|
+
if (!text) return [];
|
|
2762
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !KEYWORD_STOPWORDS.has(t));
|
|
2763
|
+
}
|
|
2764
|
+
function keywordsForElement(el) {
|
|
2765
|
+
const type = el.type.toLowerCase();
|
|
2766
|
+
const typeWords = ELEMENT_TYPE_KEYWORDS[type] ?? [];
|
|
2767
|
+
return [.../* @__PURE__ */ new Set([...typeWords, ...keywordTokens(el.label), ...keywordTokens(el.description)])];
|
|
2768
|
+
}
|
|
2769
|
+
function containsWord(text, word) {
|
|
2770
|
+
const esc = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2771
|
+
return new RegExp(`\\b${esc}\\b`, "i").test(text);
|
|
2772
|
+
}
|
|
2773
|
+
function checkFrameReferences(ctx, node, index, keyworded) {
|
|
2774
|
+
const prompt = node.params?.prompt;
|
|
2775
|
+
if (typeof prompt !== "string" || prompt.length === 0) return;
|
|
2776
|
+
const inputsBlob = JSON.stringify(node.inputs ?? {});
|
|
2777
|
+
for (const { el, keywords } of keyworded) {
|
|
2778
|
+
if (inputsBlob.includes(el.ref)) continue;
|
|
2779
|
+
const hit = keywords.find((kw) => containsWord(prompt, kw));
|
|
2780
|
+
if (!hit) continue;
|
|
2781
|
+
ctx.issues.push({
|
|
2782
|
+
path: `nodes[${index}].inputs.reference`,
|
|
2783
|
+
code: STAGE_CODES.REFERENCE_MISSING,
|
|
2784
|
+
severity: "warning",
|
|
2785
|
+
message: `frame "${node.id}" describes a "${hit}" but the "${el.label}" reference (${el.ref}) isn't wired on this node \u2014 the generator will invent a wrong-identity ${el.type} from prose. Add ${el.ref} to inputs.reference`,
|
|
2786
|
+
node_id: node.id,
|
|
2787
|
+
node_type: node.type
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
function checkReferenceCompleteness(ctx, meta) {
|
|
2792
|
+
const elements = (meta.elements ?? []).filter((el) => CHECKED_ELEMENT_TYPES.has(el.type.toLowerCase()));
|
|
2793
|
+
const keyworded = elements.map((el) => ({ el, keywords: keywordsForElement(el) })).filter((e) => e.keywords.length > 0);
|
|
2794
|
+
if (keyworded.length === 0) return;
|
|
2795
|
+
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
2796
|
+
const n = ctx.canvas.nodes[i];
|
|
2797
|
+
if (n?.type === "image_generate") checkFrameReferences(ctx, n, i, keyworded);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
function videoModelMaxDuration(model) {
|
|
2801
|
+
const spec = MODEL_REGISTRY.video_generate[model];
|
|
2802
|
+
const durations = spec?.params?.duration?.enum;
|
|
2803
|
+
if (!durations) return void 0;
|
|
2804
|
+
const nums = durations.filter((d) => typeof d === "number");
|
|
2805
|
+
return nums.length > 0 ? Math.max(...nums) : void 0;
|
|
2806
|
+
}
|
|
2807
|
+
function checkClipSpanFitsModel(ctx, meta) {
|
|
2808
|
+
for (const { node, span_s } of meta.clip_spans ?? []) {
|
|
2809
|
+
const target = ctx.canvas.nodes.find((n) => n.id === node && n.type === "video_generate");
|
|
2810
|
+
if (!target) continue;
|
|
2811
|
+
const model = target.params?.model;
|
|
2812
|
+
if (typeof model !== "string") continue;
|
|
2813
|
+
const max = videoModelMaxDuration(model);
|
|
2814
|
+
if (max === void 0 || span_s <= max + SPAN_MODEL_SLACK_S) continue;
|
|
2815
|
+
ctx.issues.push({
|
|
2816
|
+
path: `nodes[${ctx.idToIndex.get(node) ?? -1}].params.duration`,
|
|
2817
|
+
code: STAGE_CODES.SPAN_EXCEEDS_MODEL,
|
|
2818
|
+
severity: "warning",
|
|
2819
|
+
message: `scene for clip "${node}" needs ~${span_s}s but ${model} caps clips at ${max}s \u2014 the clip will truncate. Split the scene, or keep it on a model whose max duration covers the span`,
|
|
2820
|
+
node_id: node,
|
|
2821
|
+
node_type: "video_generate"
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2507
2824
|
}
|
|
2508
2825
|
function speechOverrunOf(node) {
|
|
2509
2826
|
const params = node.params;
|
|
@@ -2661,6 +2978,7 @@ var Engine = class {
|
|
|
2661
2978
|
cache;
|
|
2662
2979
|
outputsDir;
|
|
2663
2980
|
log;
|
|
2981
|
+
persistAssets;
|
|
2664
2982
|
constructor(opts) {
|
|
2665
2983
|
this.registry = opts.registry;
|
|
2666
2984
|
this.client = opts.client;
|
|
@@ -2668,6 +2986,7 @@ var Engine = class {
|
|
|
2668
2986
|
this.cache = opts.cache;
|
|
2669
2987
|
this.outputsDir = opts.outputsDir;
|
|
2670
2988
|
this.log = opts.log ?? (() => void 0);
|
|
2989
|
+
this.persistAssets = opts.persistAssets ?? false;
|
|
2671
2990
|
}
|
|
2672
2991
|
validate(canvas) {
|
|
2673
2992
|
return validateCanvas(canvas, this.registry);
|
|
@@ -2684,6 +3003,9 @@ var Engine = class {
|
|
|
2684
3003
|
const writer = new OutputWriter({ outputsDir: this.outputsDir, runId });
|
|
2685
3004
|
await writer.ensure();
|
|
2686
3005
|
this.log(`[validate] ok (${canvas.nodes.length} nodes, est. ${validation.estimatedCredits} credits)`);
|
|
3006
|
+
for (const w of validation.warnings ?? []) {
|
|
3007
|
+
this.log(`[warn ] ${w.code}${w.node_id ? ` (${w.node_id})` : ""}: ${w.message}`);
|
|
3008
|
+
}
|
|
2687
3009
|
const t0 = Date.now();
|
|
2688
3010
|
const outputs = {};
|
|
2689
3011
|
const counters = { cachedNodes: 0, totalCredits: 0 };
|
|
@@ -2711,7 +3033,7 @@ var Engine = class {
|
|
|
2711
3033
|
`[done ] ${stats.cached_nodes}/${stats.total_nodes} cached, ${stats.total_credits} credits, ${stats.duration_ms}ms`
|
|
2712
3034
|
);
|
|
2713
3035
|
this.log(`outputs in: ${writer.runDir}`);
|
|
2714
|
-
return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir };
|
|
3036
|
+
return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir, node_runs: nodeRuns };
|
|
2715
3037
|
}
|
|
2716
3038
|
async runLayers(canvas, outputs, runId, writer, opts, counters, nodeRuns) {
|
|
2717
3039
|
const layers = topologicalLayers(this.pruneToOutput(canvas, buildGraph(canvas)));
|
|
@@ -2808,6 +3130,14 @@ var Engine = class {
|
|
|
2808
3130
|
const credits = def.cost ? def.cost({ params: parsedParams }).credits : 0;
|
|
2809
3131
|
const outputsObj = result;
|
|
2810
3132
|
outputs[node.id] = outputsObj;
|
|
3133
|
+
if (this.persistAssets) {
|
|
3134
|
+
try {
|
|
3135
|
+
await persistOutputAssetUrls(outputsObj, ctx);
|
|
3136
|
+
} catch (e) {
|
|
3137
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
3138
|
+
this.log(`[warn ] ${node.id}: asset persistence failed (${msg}) \u2014 outputs stay local-only`);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
2811
3141
|
if (policy === "read_write") {
|
|
2812
3142
|
await this.cache.put({
|
|
2813
3143
|
cacheKey: prepared.cacheKey,
|
|
@@ -3126,27 +3456,6 @@ var FontRef = BaseAssetRef.extend({
|
|
|
3126
3456
|
});
|
|
3127
3457
|
var AssetRef = z4.discriminatedUnion("kind", [ImageRef, VideoRef, AudioRef, JsonRef, TextRef, FontRef]);
|
|
3128
3458
|
|
|
3129
|
-
// src/engine/nodes/remote/upload.ts
|
|
3130
|
-
async function presignAndPut(args) {
|
|
3131
|
-
const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
|
|
3132
|
-
const putRes = await fetch(putUrl, {
|
|
3133
|
-
method: "PUT",
|
|
3134
|
-
body: new Uint8Array(args.bytes),
|
|
3135
|
-
headers: { "Content-Type": args.mime },
|
|
3136
|
-
signal: args.ctx.signal
|
|
3137
|
-
});
|
|
3138
|
-
if (!putRes.ok) {
|
|
3139
|
-
throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
|
|
3140
|
-
}
|
|
3141
|
-
return publicUrl;
|
|
3142
|
-
}
|
|
3143
|
-
async function ensureUploaded(ref, ctx) {
|
|
3144
|
-
if (ref.url) return ref;
|
|
3145
|
-
const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
|
|
3146
|
-
const url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
|
|
3147
|
-
return { ...ref, url };
|
|
3148
|
-
}
|
|
3149
|
-
|
|
3150
3459
|
// src/engine/nodes/remote/delegate.ts
|
|
3151
3460
|
function delegated(spec) {
|
|
3152
3461
|
return {
|
|
@@ -3253,6 +3562,13 @@ function mapClientError(ctx, e) {
|
|
|
3253
3562
|
provider: d.provider
|
|
3254
3563
|
});
|
|
3255
3564
|
}
|
|
3565
|
+
if (d.kind === "content_policy") {
|
|
3566
|
+
return new NodeExecutionError(ctx.nodeId, ctx.nodeType, {
|
|
3567
|
+
kind: "content_policy",
|
|
3568
|
+
provider: d.provider,
|
|
3569
|
+
message: d.message
|
|
3570
|
+
});
|
|
3571
|
+
}
|
|
3256
3572
|
if (d.kind === "timeout") {
|
|
3257
3573
|
return new NodeExecutionError(ctx.nodeId, ctx.nodeType, { kind: "timeout", provider: d.provider });
|
|
3258
3574
|
}
|
|
@@ -3534,10 +3850,10 @@ function inferKindFromMime(mime) {
|
|
|
3534
3850
|
if (mime.startsWith("font/")) return "font";
|
|
3535
3851
|
return null;
|
|
3536
3852
|
}
|
|
3537
|
-
function localExecError(ctx,
|
|
3853
|
+
function localExecError(ctx, message2) {
|
|
3538
3854
|
return new NodeExecutionError(ctx.nodeId, ctx.nodeType, {
|
|
3539
3855
|
kind: "local",
|
|
3540
|
-
cause: new Error(`ingest: ${
|
|
3856
|
+
cause: new Error(`ingest: ${message2}`)
|
|
3541
3857
|
});
|
|
3542
3858
|
}
|
|
3543
3859
|
async function execLocalFile(params, ctx) {
|
|
@@ -4204,7 +4520,7 @@ async function refToUrl(ref) {
|
|
|
4204
4520
|
return `data:${ref.mime};base64,${bytes.toString("base64")}`;
|
|
4205
4521
|
}
|
|
4206
4522
|
var ASSET_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "json", "text", "font"]);
|
|
4207
|
-
function
|
|
4523
|
+
function isAssetRefLike2(value) {
|
|
4208
4524
|
if (!value || typeof value !== "object") return false;
|
|
4209
4525
|
const v = value;
|
|
4210
4526
|
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");
|
|
@@ -4576,8 +4892,8 @@ var NEVER_BLOCK = [
|
|
|
4576
4892
|
/text[_-]?occluded/i
|
|
4577
4893
|
];
|
|
4578
4894
|
var UNAVAILABLE = /unknown command|command not found|not found|Did you mean|Unknown argument|ENOENT/i;
|
|
4579
|
-
function isAdvisory(code,
|
|
4580
|
-
const hay = `${code} ${
|
|
4895
|
+
function isAdvisory(code, message2) {
|
|
4896
|
+
const hay = `${code} ${message2}`;
|
|
4581
4897
|
return NEVER_BLOCK.some((re) => re.test(hay));
|
|
4582
4898
|
}
|
|
4583
4899
|
function parseCheckJson(raw) {
|
|
@@ -4605,10 +4921,10 @@ function classifyLint(json) {
|
|
|
4605
4921
|
for (const f of findings) {
|
|
4606
4922
|
const rec = f;
|
|
4607
4923
|
const code = String(rec?.code ?? "");
|
|
4608
|
-
const
|
|
4924
|
+
const message2 = String(rec?.message ?? "");
|
|
4609
4925
|
const severity = String(rec?.severity ?? "info");
|
|
4610
|
-
const blocking = severity === "error" && !isAdvisory(code,
|
|
4611
|
-
out.push({ source: "lint", code, message, severity: blocking ? "blocking" : "warning" });
|
|
4926
|
+
const blocking = severity === "error" && !isAdvisory(code, message2);
|
|
4927
|
+
out.push({ source: "lint", code, message: message2, severity: blocking ? "blocking" : "warning" });
|
|
4612
4928
|
}
|
|
4613
4929
|
return out;
|
|
4614
4930
|
}
|
|
@@ -4620,9 +4936,9 @@ function classifyInspect(json) {
|
|
|
4620
4936
|
for (const iss of issues) {
|
|
4621
4937
|
const rec = iss;
|
|
4622
4938
|
const code = String(rec?.code ?? rec?.type ?? "overflow");
|
|
4623
|
-
const
|
|
4939
|
+
const message2 = String(rec?.message ?? rec?.detail ?? JSON.stringify(iss));
|
|
4624
4940
|
const severity = rec?.severity ? String(rec.severity) : obj?.ok === false ? "error" : "warning";
|
|
4625
|
-
out.push({ source: "inspect", code, message, severity: severity === "error" ? "blocking" : "warning" });
|
|
4941
|
+
out.push({ source: "inspect", code, message: message2, severity: severity === "error" ? "blocking" : "warning" });
|
|
4626
4942
|
}
|
|
4627
4943
|
return out;
|
|
4628
4944
|
}
|
|
@@ -5067,7 +5383,7 @@ async function buildSubstitutionValues(compositionParams, meta, duration) {
|
|
|
5067
5383
|
}
|
|
5068
5384
|
function coerceImageParam(value) {
|
|
5069
5385
|
if (typeof value === "string") return Promise.resolve(value);
|
|
5070
|
-
if (
|
|
5386
|
+
if (isAssetRefLike2(value)) return refToUrl(value);
|
|
5071
5387
|
throw new Error("hyperframe_render: image param must be a URL string or AssetRef");
|
|
5072
5388
|
}
|
|
5073
5389
|
async function substituteCompositionFiles(tmp, values) {
|
|
@@ -5297,7 +5613,7 @@ async function buildSubstitutionValues2(compositionParams, meta) {
|
|
|
5297
5613
|
}
|
|
5298
5614
|
function coerceImageParam2(value) {
|
|
5299
5615
|
if (typeof value === "string") return Promise.resolve(value);
|
|
5300
|
-
if (
|
|
5616
|
+
if (isAssetRefLike2(value)) return refToUrl(value);
|
|
5301
5617
|
throw new Error("hyperframe_snapshot: image param must be a URL string or AssetRef");
|
|
5302
5618
|
}
|
|
5303
5619
|
async function substituteCompositionFiles2(tmp, values) {
|
|
@@ -6323,17 +6639,29 @@ function createEngineFromEnv(opts = {}) {
|
|
|
6323
6639
|
const cacheDir = opts.cacheDir ?? path15.join(cwd, "canvas", ".cache");
|
|
6324
6640
|
const outputsDir = opts.outputsDir ?? path15.join(cwd, "canvas");
|
|
6325
6641
|
const creds = requireCredentialsFromEnv();
|
|
6642
|
+
const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
|
|
6643
|
+
const assets = new LocalAssetStore(path15.join(cacheDir, "assets"));
|
|
6644
|
+
const localCache = new LocalCacheStore(path15.join(cacheDir, "index"));
|
|
6645
|
+
const remoteCacheEnabled = opts.remoteCache ?? remoteCacheEnabledFromEnv();
|
|
6646
|
+
const cache = remoteCacheEnabled ? new LayeredCacheStore({
|
|
6647
|
+
local: localCache,
|
|
6648
|
+
remote: new RemoteCacheStore(client, opts.log),
|
|
6649
|
+
assets,
|
|
6650
|
+
log: opts.log
|
|
6651
|
+
}) : localCache;
|
|
6326
6652
|
return new Engine({
|
|
6327
6653
|
registry: defaultRegistry(),
|
|
6328
|
-
client
|
|
6329
|
-
assets
|
|
6330
|
-
cache
|
|
6654
|
+
client,
|
|
6655
|
+
assets,
|
|
6656
|
+
cache,
|
|
6331
6657
|
outputsDir,
|
|
6332
|
-
log: opts.log
|
|
6658
|
+
log: opts.log,
|
|
6659
|
+
persistAssets: remoteCacheEnabled
|
|
6333
6660
|
});
|
|
6334
6661
|
}
|
|
6335
6662
|
|
|
6336
6663
|
export {
|
|
6664
|
+
requireCredentialsFromEnv,
|
|
6337
6665
|
LayerExecutionError,
|
|
6338
6666
|
describeFailureReason,
|
|
6339
6667
|
SEEDANCE_DURATIONS,
|
|
@@ -6341,6 +6669,10 @@ export {
|
|
|
6341
6669
|
IMAGE_GENERATE_MODELS,
|
|
6342
6670
|
MODEL_REGISTRY,
|
|
6343
6671
|
resolveConcurrency,
|
|
6672
|
+
ulid,
|
|
6673
|
+
isPersistedAssetRef,
|
|
6674
|
+
collectAssetRefLikes,
|
|
6675
|
+
sha256Hex,
|
|
6344
6676
|
BackendClient2 as BackendClient,
|
|
6345
6677
|
Engine2 as Engine,
|
|
6346
6678
|
LocalAssetStore2 as LocalAssetStore,
|
|
@@ -6351,4 +6683,4 @@ export {
|
|
|
6351
6683
|
defaultRegistry,
|
|
6352
6684
|
createEngineFromEnv
|
|
6353
6685
|
};
|
|
6354
|
-
//# sourceMappingURL=chunk-
|
|
6686
|
+
//# sourceMappingURL=chunk-OCMOQOIJ.js.map
|