@koda-sl/baker-cli 0.121.0-dev.6fedecad2 → 0.121.0-dev.775026ddd

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.
@@ -1,7 +1,28 @@
1
- import {
2
- __commonJS,
3
- __toESM
4
- } from "./chunk-5WRI5ZAA.js";
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
5
26
 
6
27
  // ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js
7
28
  var require_safe_stable_stringify = __commonJS({
@@ -138,9 +159,9 @@ var require_safe_stable_stringify = __commonJS({
138
159
  }
139
160
  if (value) {
140
161
  return (value2) => {
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);
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);
144
165
  };
145
166
  }
146
167
  }
@@ -652,6 +673,9 @@ var HttpClient = class {
652
673
  async postJson(path16, body, signal) {
653
674
  return await this.requestJson("POST", path16, body, signal);
654
675
  }
676
+ async putJson(path16, body, signal) {
677
+ return await this.requestJson("PUT", path16, body, signal);
678
+ }
655
679
  async getJson(path16, signal) {
656
680
  return await this.requestJson("GET", path16, void 0, signal);
657
681
  }
@@ -679,8 +703,8 @@ var HttpClient = class {
679
703
  try {
680
704
  const res = await this.fetchFn(url, {
681
705
  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,
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),
684
708
  signal: controller.signal
685
709
  });
686
710
  if (res.ok) return { kind: "value", value: await res.json() };
@@ -717,33 +741,33 @@ async function parseErrorBody(res) {
717
741
  const errObj = body.error ?? {};
718
742
  return classifyHttpError(res.status, errObj, errObj.message ?? `HTTP ${res.status}`);
719
743
  }
720
- function classifyHttpError(status, errObj, message) {
744
+ function classifyHttpError(status, errObj, message2) {
721
745
  if (errObj.code === CONTENT_POLICY_CODE) {
722
- return { kind: "content_policy", status, provider: errObj.provider, message };
746
+ return { kind: "content_policy", status, provider: errObj.provider, message: message2 };
723
747
  }
724
748
  if (status === 401 || status === 403) {
725
- return { kind: "unauthorized", status, message };
749
+ return { kind: "unauthorized", status, message: message2 };
726
750
  }
727
751
  if (status === 400 || status === 422) {
728
- return { kind: "validation", status, message, details: errObj.details };
752
+ return { kind: "validation", status, message: message2, details: errObj.details };
729
753
  }
730
754
  if (status === 502 || status === 504) {
731
755
  if (errObj.code === "provider_timeout" || status === 504) {
732
- return { kind: "timeout", provider: errObj.provider, message };
756
+ return { kind: "timeout", provider: errObj.provider, message: message2 };
733
757
  }
734
758
  return {
735
759
  kind: "provider",
736
760
  status,
737
761
  provider: errObj.provider,
738
762
  code: errObj.code ?? "provider_error",
739
- message,
763
+ message: message2,
740
764
  retryable: errObj.retryable ?? true
741
765
  };
742
766
  }
743
767
  if (status >= 500 || status === 429) {
744
- return { kind: "server", status, message };
768
+ return { kind: "server", status, message: message2 };
745
769
  }
746
- return { kind: "validation", status, message, details: errObj.details };
770
+ return { kind: "validation", status, message: message2, details: errObj.details };
747
771
  }
748
772
  function backoffMs(attempt) {
749
773
  return 1e3 * 2 ** attempt;
@@ -814,6 +838,27 @@ var BackendClient = class {
814
838
  signal
815
839
  );
816
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
+ }
817
862
  getArtifact(kind, name, version, signal) {
818
863
  const path16 = version ? `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}` : `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`;
819
864
  return this.http.getJson(path16, signal);
@@ -837,14 +882,17 @@ function requireCredentialsFromEnv(env = process.env) {
837
882
  }
838
883
  return c;
839
884
  }
885
+ function remoteCacheEnabledFromEnv(env = process.env) {
886
+ return env.BAKER_CANVAS_REMOTE_CACHE !== "off";
887
+ }
840
888
 
841
889
  // src/engine/engine/errors.ts
842
890
  function isBlocking(issue) {
843
891
  return issue.severity !== "warning";
844
892
  }
845
893
  var CanvasError = class extends Error {
846
- constructor(message) {
847
- super(message);
894
+ constructor(message2) {
895
+ super(message2);
848
896
  this.name = "CanvasError";
849
897
  }
850
898
  };
@@ -1562,6 +1610,160 @@ function encodeRandom() {
1562
1610
  return out;
1563
1611
  }
1564
1612
 
1613
+ // src/engine/storage/remote-cache-store.ts
1614
+ var CANVAS_ASSETS_URL_SEGMENT = "/canvas-assets/";
1615
+ function isPersistedAssetRef(ref) {
1616
+ const { url, sha256 } = ref;
1617
+ if (typeof url !== "string" || typeof sha256 !== "string") return false;
1618
+ return url.includes(`${CANVAS_ASSETS_URL_SEGMENT}${sha256.slice(0, 2)}/${sha256}`) || url.includes(`${CANVAS_ASSETS_URL_SEGMENT}${sha256}`);
1619
+ }
1620
+ function isAssetRefLike(value) {
1621
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.sha256 === "string" && typeof value.mime === "string";
1622
+ }
1623
+ function collectAssetRefLikes(value, out = []) {
1624
+ if (Array.isArray(value)) {
1625
+ for (const item of value) collectAssetRefLikes(item, out);
1626
+ return out;
1627
+ }
1628
+ if (typeof value !== "object" || value === null) return out;
1629
+ if (isAssetRefLike(value)) {
1630
+ out.push(value);
1631
+ }
1632
+ for (const item of Object.values(value)) collectAssetRefLikes(item, out);
1633
+ return out;
1634
+ }
1635
+ function entryFullyPersisted(entry) {
1636
+ return collectAssetRefLikes(entry.outputs).every((ref) => isPersistedAssetRef(ref));
1637
+ }
1638
+ function stripLocalFields(entry) {
1639
+ const clone = JSON.parse(JSON.stringify(entry));
1640
+ for (const ref of collectAssetRefLikes(clone.outputs)) {
1641
+ delete ref.path;
1642
+ delete ref.bytes;
1643
+ }
1644
+ return clone;
1645
+ }
1646
+ var RemoteCacheStore = class {
1647
+ client;
1648
+ log;
1649
+ constructor(client, log) {
1650
+ this.client = client;
1651
+ this.log = log ?? (() => void 0);
1652
+ }
1653
+ async get(cacheKey) {
1654
+ return await this.client.getCacheEntry(cacheKey);
1655
+ }
1656
+ async put(entry) {
1657
+ if (!entryFullyPersisted(entry)) {
1658
+ this.log(`[cache ] ${entry.cacheKey.slice(0, 12)}\u2026 has local-only assets, kept local`);
1659
+ return;
1660
+ }
1661
+ const stripped = stripLocalFields(entry);
1662
+ if (stripped.refs.length > MAX_REMOTE_REFS) {
1663
+ stripped.refs = stripped.refs.slice(0, MAX_REMOTE_REFS);
1664
+ }
1665
+ await this.client.putCacheEntry(stripped);
1666
+ }
1667
+ };
1668
+ var MAX_REMOTE_REFS = 512;
1669
+ var LayeredCacheStore = class {
1670
+ rootDir;
1671
+ local;
1672
+ remote;
1673
+ assets;
1674
+ log;
1675
+ constructor(opts) {
1676
+ this.local = opts.local;
1677
+ this.remote = opts.remote;
1678
+ this.assets = opts.assets;
1679
+ this.rootDir = opts.local.rootDir;
1680
+ this.log = opts.log ?? (() => void 0);
1681
+ }
1682
+ async get(cacheKey) {
1683
+ const localHit = await this.local.get(cacheKey);
1684
+ if (localHit) return localHit;
1685
+ let remoteEntry;
1686
+ try {
1687
+ remoteEntry = await this.remote.get(cacheKey);
1688
+ } catch (e) {
1689
+ this.log(`[cache ] remote lookup failed (${message(e)}) \u2014 treating as miss`);
1690
+ return null;
1691
+ }
1692
+ if (!remoteEntry) return null;
1693
+ let rehydrated;
1694
+ try {
1695
+ rehydrated = await this.rehydrate(remoteEntry);
1696
+ } catch (e) {
1697
+ this.log(`[cache ] ${cacheKey.slice(0, 12)}\u2026 rehydration failed (${message(e)}) \u2014 treating as miss`);
1698
+ return null;
1699
+ }
1700
+ await this.local.put(rehydrated);
1701
+ return rehydrated;
1702
+ }
1703
+ async put(entry) {
1704
+ await this.local.put(entry);
1705
+ try {
1706
+ await this.remote.put(entry);
1707
+ } catch (e) {
1708
+ this.log(`[cache ] remote write failed (${message(e)}) \u2014 entry kept local`);
1709
+ }
1710
+ }
1711
+ /**
1712
+ * Download every referenced asset into the local content-addressed store
1713
+ * (sha-verified) and stamp fresh local paths. Any ref that cannot be
1714
+ * rehydrated fails the WHOLE entry — a partially-hydrated cache hit would
1715
+ * crash materialization later with a far less actionable error.
1716
+ */
1717
+ async rehydrate(entry) {
1718
+ const clone = JSON.parse(JSON.stringify(entry));
1719
+ for (const ref of collectAssetRefLikes(clone.outputs)) {
1720
+ if (!isPersistedAssetRef(ref)) {
1721
+ throw new Error(`ref ${ref.sha256.slice(0, 12)}\u2026 has no persisted url`);
1722
+ }
1723
+ const ingested = await this.assets.ingestRemote({
1724
+ kind: typeof ref.kind === "string" ? ref.kind : "json",
1725
+ url: ref.url,
1726
+ sha256: ref.sha256,
1727
+ mime: ref.mime,
1728
+ metadata: ref.metadata ?? void 0
1729
+ });
1730
+ ref.path = ingested.path;
1731
+ }
1732
+ return clone;
1733
+ }
1734
+ };
1735
+ function message(e) {
1736
+ return e instanceof Error ? e.message : String(e);
1737
+ }
1738
+
1739
+ // src/engine/nodes/remote/upload.ts
1740
+ async function presignAndPut(args) {
1741
+ const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
1742
+ const putRes = await fetch(putUrl, {
1743
+ method: "PUT",
1744
+ body: new Uint8Array(args.bytes),
1745
+ headers: { "Content-Type": args.mime },
1746
+ signal: args.ctx.signal
1747
+ });
1748
+ if (!putRes.ok) {
1749
+ throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
1750
+ }
1751
+ return publicUrl;
1752
+ }
1753
+ async function ensureUploaded(ref, ctx) {
1754
+ if (ref.url) return ref;
1755
+ const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
1756
+ const url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
1757
+ return { ...ref, url };
1758
+ }
1759
+ async function persistOutputAssetUrls(outputs, ctx) {
1760
+ for (const ref of collectAssetRefLikes(outputs)) {
1761
+ if (isPersistedAssetRef(ref)) continue;
1762
+ const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
1763
+ ref.url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
1764
+ }
1765
+ }
1766
+
1565
1767
  // src/engine/schema/canvas.ts
1566
1768
  import { z } from "zod";
1567
1769
  var REF_PREFIX = "$ref:";
@@ -1623,11 +1825,7 @@ var VideoMeta = z.object({
1623
1825
  // Advisory: the scene's visual length vs the estimated spoken length, so
1624
1826
  // a reviewer can see a native line that may run past its cut. Not gated.
1625
1827
  scene_s: z.number().optional(),
1626
- est_speech_s: z.number().optional(),
1627
- // Word count of the line est_speech_s was measured for. Together they carry
1628
- // the speaker's OBSERVED pace (from the deconstruct's word timings), so the
1629
- // overrun check budgets re-authored lines at the real rate, not a wps guess.
1630
- speech_words: z.number().optional()
1828
+ est_speech_s: z.number().optional()
1631
1829
  }),
1632
1830
  z.object({ scene: z.number(), lipsync_node: z.string() })
1633
1831
  ])
@@ -2157,9 +2355,7 @@ var STAGE_CODES = {
2157
2355
  SPEECH_OVERRUN: "VIDEO_SPEECH_OVERRUN",
2158
2356
  ASPECT_MISMATCH: "VIDEO_ASPECT_MISMATCH",
2159
2357
  REFERENCE_MISSING: "VIDEO_REFERENCE_MISSING",
2160
- SPAN_EXCEEDS_MODEL: "VIDEO_SPAN_EXCEEDS_MODEL",
2161
- UI_IN_PROMPT: "VIDEO_UI_IN_PROMPT",
2162
- BRANDMARK_IN_PROMPT: "VIDEO_BRANDMARK_IN_PROMPT"
2358
+ SPAN_EXCEEDS_MODEL: "VIDEO_SPAN_EXCEEDS_MODEL"
2163
2359
  };
2164
2360
  var SPAN_MODEL_SLACK_S = 0.25;
2165
2361
  var VIDEO_TIME_SLACK_S = 0.75;
@@ -2538,8 +2734,6 @@ function checkVideoInvariants(ctx) {
2538
2734
  }
2539
2735
  checkSpeechOverrun(ctx, meta.talking_scenes);
2540
2736
  checkAspectConsistency(ctx);
2541
- checkUiInPrompt(ctx);
2542
- checkBrandmarkInPrompt(ctx);
2543
2737
  checkReferenceCompleteness(ctx, meta);
2544
2738
  checkClipSpanFitsModel(ctx, meta);
2545
2739
  }
@@ -2569,30 +2763,18 @@ function keywordTokens(text) {
2569
2763
  if (!text) return [];
2570
2764
  return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !KEYWORD_STOPWORDS.has(t));
2571
2765
  }
2572
- function elementMentionKeywords(el) {
2573
- const typeWords = ELEMENT_TYPE_KEYWORDS[el.type.toLowerCase()] ?? [];
2574
- return [.../* @__PURE__ */ new Set([...typeWords, ...keywordTokens(el.label), ...keywordTokens(el.description)])];
2575
- }
2576
2766
  function keywordsForElement(el) {
2577
- return elementMentionKeywords(el);
2767
+ const type = el.type.toLowerCase();
2768
+ const typeWords = ELEMENT_TYPE_KEYWORDS[type] ?? [];
2769
+ return [.../* @__PURE__ */ new Set([...typeWords, ...keywordTokens(el.label), ...keywordTokens(el.description)])];
2578
2770
  }
2579
2771
  function containsWord(text, word) {
2580
2772
  const esc = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2581
2773
  return new RegExp(`\\b${esc}\\b`, "i").test(text);
2582
2774
  }
2583
- var FRAME_DESCRIPTION_START = "FRAME DESCRIPTION (this frame's editable prompt):";
2584
- var FRAME_DESCRIPTION_END = "Render exactly what the FRAME DESCRIPTION";
2585
- function frameDescriptionOf(prompt) {
2586
- const i = prompt.indexOf(FRAME_DESCRIPTION_START);
2587
- if (i < 0) return prompt;
2588
- const rest = prompt.slice(i + FRAME_DESCRIPTION_START.length);
2589
- const j = rest.indexOf(FRAME_DESCRIPTION_END);
2590
- return j < 0 ? rest : rest.slice(0, j);
2591
- }
2592
2775
  function checkFrameReferences(ctx, node, index, keyworded) {
2593
- const rawPrompt = node.params?.prompt;
2594
- if (typeof rawPrompt !== "string" || rawPrompt.length === 0) return;
2595
- const prompt = frameDescriptionOf(rawPrompt);
2776
+ const prompt = node.params?.prompt;
2777
+ if (typeof prompt !== "string" || prompt.length === 0) return;
2596
2778
  const inputsBlob = JSON.stringify(node.inputs ?? {});
2597
2779
  for (const { el, keywords } of keyworded) {
2598
2780
  if (inputsBlob.includes(el.ref)) continue;
@@ -2642,24 +2824,13 @@ function checkClipSpanFitsModel(ctx, meta) {
2642
2824
  });
2643
2825
  }
2644
2826
  }
2645
- var OBSERVED_WPS_MIN = 1;
2646
- var OBSERVED_WPS_MAX = 6;
2647
- function secondsPerWord(stamped) {
2648
- const est = stamped?.est_speech_s;
2649
- const words = stamped?.speech_words;
2650
- if (est && words && est > 0 && words > 0) {
2651
- const wps = words / est;
2652
- if (wps >= OBSERVED_WPS_MIN && wps <= OBSERVED_WPS_MAX) return est / words;
2653
- }
2654
- return 1 / SPEECH_WORDS_PER_SECOND;
2655
- }
2656
- function speechOverrunOf(node, secPerWord) {
2827
+ function speechOverrunOf(node) {
2657
2828
  const params = node.params;
2658
2829
  if (params?.generate_audio !== true) return null;
2659
2830
  const line = nativeDialogueOf(params.prompt);
2660
2831
  const duration = typeof params.duration === "number" ? params.duration : void 0;
2661
2832
  if (!line || !duration) return null;
2662
- const estSpeechS = line.split(/\s+/).filter(Boolean).length * secPerWord;
2833
+ const estSpeechS = line.split(/\s+/).filter(Boolean).length / SPEECH_WORDS_PER_SECOND;
2663
2834
  return estSpeechS > duration * SPEECH_OVERRUN_RATIO ? { estSpeechS, duration } : null;
2664
2835
  }
2665
2836
  function checkSpeechOverrun(ctx, talkingScenes) {
@@ -2668,7 +2839,7 @@ function checkSpeechOverrun(ctx, talkingScenes) {
2668
2839
  const nativeClipRe = new RegExp(`^s${entry.scene}(_r\\d+)?_clip$`);
2669
2840
  for (const n of ctx.canvas.nodes) {
2670
2841
  if (!nativeClipRe.test(n.id) || n.type !== "video_generate") continue;
2671
- const overrun = speechOverrunOf(n, secondsPerWord(entry));
2842
+ const overrun = speechOverrunOf(n);
2672
2843
  if (!overrun) continue;
2673
2844
  ctx.issues.push({
2674
2845
  path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
@@ -2678,38 +2849,6 @@ function checkSpeechOverrun(ctx, talkingScenes) {
2678
2849
  }
2679
2850
  }
2680
2851
  }
2681
- var UI_IN_PROMPT_RE = /\bscreen[- ]?(?:recording|capture|grab|share)\b|\bapp (?:interface|screen)\b|\bphone screen overlay\b/i;
2682
- function checkUiInPrompt(ctx) {
2683
- for (const n of ctx.canvas.nodes) {
2684
- if (n.type !== "video_generate") continue;
2685
- const prompt = n.params?.prompt;
2686
- if (typeof prompt !== "string" || !UI_IN_PROMPT_RE.test(prompt)) continue;
2687
- ctx.issues.push({
2688
- path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
2689
- code: STAGE_CODES.UI_IN_PROMPT,
2690
- severity: "warning",
2691
- message: `"${n.id}" asks the video model to render a screen/UI surface \u2014 generative video garbles UI text and chrome. Composite the real screen on the overlay layer (screenshot / brand HTML) and keep this prompt to the background plate`,
2692
- node_id: n.id,
2693
- node_type: "video_generate"
2694
- });
2695
- }
2696
- }
2697
- var BRANDMARK_IN_PROMPT_RE = /\b(?:logo|wordmark) (?:overlay|animation|sting|card|reveal)\b|\b(?:google|facebook|instagram|tiktok|youtube|amazon|apple|microsoft|whatsapp|netflix|spotify|excel|trustpilot) (?:logo|wordmark|branding)\b/i;
2698
- function checkBrandmarkInPrompt(ctx) {
2699
- for (const n of ctx.canvas.nodes) {
2700
- if (n.type !== "video_generate" && n.type !== "image_generate") continue;
2701
- const prompt = n.params?.prompt;
2702
- if (typeof prompt !== "string" || !BRANDMARK_IN_PROMPT_RE.test(prompt)) continue;
2703
- ctx.issues.push({
2704
- path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].params.prompt`,
2705
- code: STAGE_CODES.BRANDMARK_IN_PROMPT,
2706
- severity: "warning",
2707
- message: `"${n.id}" asks the model to render a brand logo/wordmark \u2014 generation garbles marks and third-party logos carry IP exposure. Source the real mark (baker images logo <domain>) and composite it on the overlay layer`,
2708
- node_id: n.id,
2709
- node_type: n.type
2710
- });
2711
- }
2712
- }
2713
2852
  function checkAspectConsistency(ctx) {
2714
2853
  const clips = ctx.canvas.nodes.filter((n) => n.type === "video_generate");
2715
2854
  if (clips.length < 2) return;
@@ -2841,6 +2980,7 @@ var Engine = class {
2841
2980
  cache;
2842
2981
  outputsDir;
2843
2982
  log;
2983
+ persistAssets;
2844
2984
  constructor(opts) {
2845
2985
  this.registry = opts.registry;
2846
2986
  this.client = opts.client;
@@ -2848,6 +2988,7 @@ var Engine = class {
2848
2988
  this.cache = opts.cache;
2849
2989
  this.outputsDir = opts.outputsDir;
2850
2990
  this.log = opts.log ?? (() => void 0);
2991
+ this.persistAssets = opts.persistAssets ?? false;
2851
2992
  }
2852
2993
  validate(canvas) {
2853
2994
  return validateCanvas(canvas, this.registry);
@@ -2894,7 +3035,7 @@ var Engine = class {
2894
3035
  `[done ] ${stats.cached_nodes}/${stats.total_nodes} cached, ${stats.total_credits} credits, ${stats.duration_ms}ms`
2895
3036
  );
2896
3037
  this.log(`outputs in: ${writer.runDir}`);
2897
- return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir };
3038
+ return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir, node_runs: nodeRuns };
2898
3039
  }
2899
3040
  async runLayers(canvas, outputs, runId, writer, opts, counters, nodeRuns) {
2900
3041
  const layers = topologicalLayers(this.pruneToOutput(canvas, buildGraph(canvas)));
@@ -2991,6 +3132,14 @@ var Engine = class {
2991
3132
  const credits = def.cost ? def.cost({ params: parsedParams }).credits : 0;
2992
3133
  const outputsObj = result;
2993
3134
  outputs[node.id] = outputsObj;
3135
+ if (this.persistAssets) {
3136
+ try {
3137
+ await persistOutputAssetUrls(outputsObj, ctx);
3138
+ } catch (e) {
3139
+ const msg = e instanceof Error ? e.message : String(e);
3140
+ this.log(`[warn ] ${node.id}: asset persistence failed (${msg}) \u2014 outputs stay local-only`);
3141
+ }
3142
+ }
2994
3143
  if (policy === "read_write") {
2995
3144
  await this.cache.put({
2996
3145
  cacheKey: prepared.cacheKey,
@@ -3309,27 +3458,6 @@ var FontRef = BaseAssetRef.extend({
3309
3458
  });
3310
3459
  var AssetRef = z4.discriminatedUnion("kind", [ImageRef, VideoRef, AudioRef, JsonRef, TextRef, FontRef]);
3311
3460
 
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
-
3333
3461
  // src/engine/nodes/remote/delegate.ts
3334
3462
  function delegated(spec) {
3335
3463
  return {
@@ -3724,10 +3852,10 @@ function inferKindFromMime(mime) {
3724
3852
  if (mime.startsWith("font/")) return "font";
3725
3853
  return null;
3726
3854
  }
3727
- function localExecError(ctx, message) {
3855
+ function localExecError(ctx, message2) {
3728
3856
  return new NodeExecutionError(ctx.nodeId, ctx.nodeType, {
3729
3857
  kind: "local",
3730
- cause: new Error(`ingest: ${message}`)
3858
+ cause: new Error(`ingest: ${message2}`)
3731
3859
  });
3732
3860
  }
3733
3861
  async function execLocalFile(params, ctx) {
@@ -4394,7 +4522,7 @@ async function refToUrl(ref) {
4394
4522
  return `data:${ref.mime};base64,${bytes.toString("base64")}`;
4395
4523
  }
4396
4524
  var ASSET_KINDS = /* @__PURE__ */ new Set(["image", "video", "audio", "json", "text", "font"]);
4397
- function isAssetRefLike(value) {
4525
+ function isAssetRefLike2(value) {
4398
4526
  if (!value || typeof value !== "object") return false;
4399
4527
  const v = value;
4400
4528
  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");
@@ -4766,8 +4894,8 @@ var NEVER_BLOCK = [
4766
4894
  /text[_-]?occluded/i
4767
4895
  ];
4768
4896
  var UNAVAILABLE = /unknown command|command not found|not found|Did you mean|Unknown argument|ENOENT/i;
4769
- function isAdvisory(code, message) {
4770
- const hay = `${code} ${message}`;
4897
+ function isAdvisory(code, message2) {
4898
+ const hay = `${code} ${message2}`;
4771
4899
  return NEVER_BLOCK.some((re) => re.test(hay));
4772
4900
  }
4773
4901
  function parseCheckJson(raw) {
@@ -4795,10 +4923,10 @@ function classifyLint(json) {
4795
4923
  for (const f of findings) {
4796
4924
  const rec = f;
4797
4925
  const code = String(rec?.code ?? "");
4798
- const message = String(rec?.message ?? "");
4926
+ const message2 = String(rec?.message ?? "");
4799
4927
  const severity = String(rec?.severity ?? "info");
4800
- const blocking = severity === "error" && !isAdvisory(code, message);
4801
- out.push({ source: "lint", code, message, severity: blocking ? "blocking" : "warning" });
4928
+ const blocking = severity === "error" && !isAdvisory(code, message2);
4929
+ out.push({ source: "lint", code, message: message2, severity: blocking ? "blocking" : "warning" });
4802
4930
  }
4803
4931
  return out;
4804
4932
  }
@@ -4810,9 +4938,9 @@ function classifyInspect(json) {
4810
4938
  for (const iss of issues) {
4811
4939
  const rec = iss;
4812
4940
  const code = String(rec?.code ?? rec?.type ?? "overflow");
4813
- const message = String(rec?.message ?? rec?.detail ?? JSON.stringify(iss));
4941
+ const message2 = String(rec?.message ?? rec?.detail ?? JSON.stringify(iss));
4814
4942
  const severity = rec?.severity ? String(rec.severity) : obj?.ok === false ? "error" : "warning";
4815
- out.push({ source: "inspect", code, message, severity: severity === "error" ? "blocking" : "warning" });
4943
+ out.push({ source: "inspect", code, message: message2, severity: severity === "error" ? "blocking" : "warning" });
4816
4944
  }
4817
4945
  return out;
4818
4946
  }
@@ -5257,7 +5385,7 @@ async function buildSubstitutionValues(compositionParams, meta, duration) {
5257
5385
  }
5258
5386
  function coerceImageParam(value) {
5259
5387
  if (typeof value === "string") return Promise.resolve(value);
5260
- if (isAssetRefLike(value)) return refToUrl(value);
5388
+ if (isAssetRefLike2(value)) return refToUrl(value);
5261
5389
  throw new Error("hyperframe_render: image param must be a URL string or AssetRef");
5262
5390
  }
5263
5391
  async function substituteCompositionFiles(tmp, values) {
@@ -5487,7 +5615,7 @@ async function buildSubstitutionValues2(compositionParams, meta) {
5487
5615
  }
5488
5616
  function coerceImageParam2(value) {
5489
5617
  if (typeof value === "string") return Promise.resolve(value);
5490
- if (isAssetRefLike(value)) return refToUrl(value);
5618
+ if (isAssetRefLike2(value)) return refToUrl(value);
5491
5619
  throw new Error("hyperframe_snapshot: image param must be a URL string or AssetRef");
5492
5620
  }
5493
5621
  async function substituteCompositionFiles2(tmp, values) {
@@ -6513,17 +6641,29 @@ function createEngineFromEnv(opts = {}) {
6513
6641
  const cacheDir = opts.cacheDir ?? path15.join(cwd, "canvas", ".cache");
6514
6642
  const outputsDir = opts.outputsDir ?? path15.join(cwd, "canvas");
6515
6643
  const creds = requireCredentialsFromEnv();
6644
+ const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
6645
+ const assets = new LocalAssetStore(path15.join(cacheDir, "assets"));
6646
+ const localCache = new LocalCacheStore(path15.join(cacheDir, "index"));
6647
+ const remoteCacheEnabled = opts.remoteCache ?? remoteCacheEnabledFromEnv();
6648
+ const cache = remoteCacheEnabled ? new LayeredCacheStore({
6649
+ local: localCache,
6650
+ remote: new RemoteCacheStore(client, opts.log),
6651
+ assets,
6652
+ log: opts.log
6653
+ }) : localCache;
6516
6654
  return new Engine({
6517
6655
  registry: defaultRegistry(),
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")),
6656
+ client,
6657
+ assets,
6658
+ cache,
6521
6659
  outputsDir,
6522
- log: opts.log
6660
+ log: opts.log,
6661
+ persistAssets: remoteCacheEnabled
6523
6662
  });
6524
6663
  }
6525
6664
 
6526
6665
  export {
6666
+ requireCredentialsFromEnv,
6527
6667
  LayerExecutionError,
6528
6668
  describeFailureReason,
6529
6669
  SEEDANCE_DURATIONS,
@@ -6531,7 +6671,10 @@ export {
6531
6671
  IMAGE_GENERATE_MODELS,
6532
6672
  MODEL_REGISTRY,
6533
6673
  resolveConcurrency,
6534
- elementMentionKeywords,
6674
+ ulid,
6675
+ isPersistedAssetRef,
6676
+ collectAssetRefLikes,
6677
+ sha256Hex,
6535
6678
  BackendClient2 as BackendClient,
6536
6679
  Engine2 as Engine,
6537
6680
  LocalAssetStore2 as LocalAssetStore,
@@ -6542,4 +6685,4 @@ export {
6542
6685
  defaultRegistry,
6543
6686
  createEngineFromEnv
6544
6687
  };
6545
- //# sourceMappingURL=chunk-MWFJ5NOP.js.map
6688
+ //# sourceMappingURL=chunk-GQIOFHVI.js.map