@koda-sl/baker-cli 0.114.0-dev.249eaa8ed → 0.114.1
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 +22 -44
- package/dist/{chunk-5AWO4BHJ.js → chunk-ZWMMCLJ4.js} +168 -20
- package/dist/chunk-ZWMMCLJ4.js.map +1 -0
- package/dist/cli.js +536 -2194
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +14 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5AWO4BHJ.js.map +0 -1
package/README.md
CHANGED
|
@@ -653,50 +653,6 @@ baker ads google library search-competitors "crm software" --location uk
|
|
|
653
653
|
|
|
654
654
|
---
|
|
655
655
|
|
|
656
|
-
### Staged writes (`baker ads google budgets|campaigns|...`)
|
|
657
|
-
|
|
658
|
-
Write commands **never touch the Google Ads API at stage time**. Each command stages a create/update/pause/resume/remove op against the current chat's draft (`BAKER_CHAT_ID`); the dashboard shows it as a pending "Google Ads" change, and the whole draft applies as one atomic `GoogleAdsService.Mutate` when the chat is published. Feature-flagged per company (`companies.googleAdsWriteEnabled`) — off by default = a fully simulated publish with zero real API calls.
|
|
659
|
-
|
|
660
|
-
Reference not-yet-created resources with `g_temp_*` refs returned by earlier `create` commands (they map to Google's temporary negative-ID resource names inside one atomic batch).
|
|
661
|
-
|
|
662
|
-
```bash
|
|
663
|
-
# Build a Search campaign bottom-up, chaining temp refs
|
|
664
|
-
baker ads google budgets create --customer-id 1234567890 --name "Search budget" --amount 50
|
|
665
|
-
# → { ref: "g_temp_ab12", ... }
|
|
666
|
-
baker ads google campaigns create --customer-id 1234567890 --name "Brand — Search" \
|
|
667
|
-
--channel-type SEARCH --budget-ref g_temp_ab12 --bidding-strategy MANUAL_CPC
|
|
668
|
-
baker ads google ad-groups create --customer-id 1234567890 --name "Brand terms" --campaign-ref g_temp_<campaign>
|
|
669
|
-
baker ads google keywords add --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> --text "brand name" --match-type EXACT
|
|
670
|
-
# Shared negative keyword list → attach to the campaign
|
|
671
|
-
baker ads google keyword-lists create --customer-id 1234567890 --name "Brand exclusions"
|
|
672
|
-
baker ads google keyword-lists add --customer-id 1234567890 --list-ref g_temp_<list> --text "free" --match-type BROAD
|
|
673
|
-
baker ads google keyword-lists attach --customer-id 1234567890 --campaign-ref g_temp_<campaign> --list-ref g_temp_<list>
|
|
674
|
-
|
|
675
|
-
# Batch keywords — one command stages the whole set as ONE request (up to 500)
|
|
676
|
-
baker ads google keywords add --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> \
|
|
677
|
-
--text "brand name, brand shop:PHRASE, brand store" --match-type EXACT
|
|
678
|
-
baker ads google keyword-lists add --customer-id 1234567890 --list-ref <list-id> --file negatives.txt
|
|
679
|
-
# negatives.txt: one keyword[:MATCH_TYPE] per line; blank lines and "# comments" are skipped
|
|
680
|
-
# Responsive search ad
|
|
681
|
-
baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> \
|
|
682
|
-
--headlines "Fast Widgets,Buy Online,Free Shipping" --descriptions "Best widgets around.,Ships tomorrow." \
|
|
683
|
-
--final-url https://example.com
|
|
684
|
-
|
|
685
|
-
# Review / undo staged changes
|
|
686
|
-
baker ads google draft list # readable campaign ▸ ad group ▸ ad tree + completeness advisories
|
|
687
|
-
baker ads google draft list --json # raw JSON envelope for scripting
|
|
688
|
-
baker ads google draft remove g_temp_ab12 # cascades to dependents
|
|
689
|
-
baker ads google draft clear
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remove), `negative-keywords`, `keyword-lists`, `ads`, `assets`, `audiences`, `conversions`, `bidding-strategies`, `labels`, `campaign-criteria`, and `draft`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa` take major units; `--target-roas` a ratio. Less-common ops accept a `--file <payload.json>` (flags override file keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
|
|
693
|
-
|
|
694
|
-
**Reviewing the draft** — `baker ads google draft list` renders everything you've staged as a grouped campaign ▸ ad group ▸ ad tree (with the simulated/live mode banner and non-blocking completeness advisories), the CLI counterpart to the dashboard's Google Ads tab. Pass `--json` for the raw envelope. Aim for a fully built campaign — 2–4 ad groups, ≥5 keywords each, 2–4 RSAs with 8–12 headlines, ≥4 sitelinks / ≥3 callouts / ≥1 structured snippet, and ≥1 shared negative list; the advisories flag what's still thin.
|
|
695
|
-
|
|
696
|
-
**Batch keyword adds** — `keywords add`, `negative-keywords add`, and `keyword-lists add` take a whole batch in one command: comma-separate `--text` entries and/or pass `--file <list.txt>` (one keyword per line). A `:EXACT`/`:PHRASE`/`:BROAD` suffix per entry overrides the `--match-type` default. Batches stage all-or-nothing as one request (limit 500); each keyword still lands as its own draft op, so it stays individually removable/amendable.
|
|
697
|
-
|
|
698
|
-
---
|
|
699
|
-
|
|
700
656
|
### Caching
|
|
701
657
|
|
|
702
658
|
Google Ads data is cached at two levels:
|
|
@@ -2574,12 +2530,25 @@ Auth: same `BAKER_API_KEY` + `BAKER_API_URL` as the rest of the CLI.
|
|
|
2574
2530
|
```bash
|
|
2575
2531
|
# 1. Validate (free, never calls the API)
|
|
2576
2532
|
baker canvas validate my-canvas.json
|
|
2533
|
+
# Blocking issues exit non-zero. Advisory `warnings[]` (exit 0) flag likely-wrong-but-
|
|
2534
|
+
# legal config to review before billing — a frame that describes a recurring element
|
|
2535
|
+
# (a pet/product) but doesn't wire its reference (VIDEO_REFERENCE_MISSING), or a scene
|
|
2536
|
+
# span that exceeds the assigned model's max clip duration (VIDEO_SPAN_EXCEEDS_MODEL).
|
|
2577
2537
|
|
|
2578
2538
|
# 2. Run (executes nodes, writes files to ./canvas/<run_id>/)
|
|
2579
2539
|
baker canvas run my-canvas.json
|
|
2580
2540
|
# Only nodes reachable from the declared output execute (orphaned nodes are skipped,
|
|
2581
2541
|
# not billed). Add --keep-runs N to prune old r_* run dirs, keeping the N newest.
|
|
2582
2542
|
|
|
2543
|
+
# 2b. Independent nodes ALREADY fan out in parallel — every node in the same
|
|
2544
|
+
# topological layer (e.g. all the per-clip video_generate nodes) runs concurrently
|
|
2545
|
+
# up to a bound. Raise it with --parallel N (alias: --concurrency N; default 5;
|
|
2546
|
+
# env BAKER_CANVAS_CONCURRENCY). A single clip failing NO LONGER strands its
|
|
2547
|
+
# siblings: the whole layer settles, cached siblings survive, and a re-run resumes
|
|
2548
|
+
# from cache. There is no need to render clips one at a time or pin `output` to a
|
|
2549
|
+
# single node — that old serial workaround is obsolete.
|
|
2550
|
+
baker canvas run my-canvas.json --parallel 8
|
|
2551
|
+
|
|
2583
2552
|
# 3. Inspect a finished run (per-node timing, file list, optional video thumbs)
|
|
2584
2553
|
baker canvas inspect <run_id>
|
|
2585
2554
|
|
|
@@ -3323,6 +3292,15 @@ Ref-image MIMEs: `image/png`, `image/jpeg`, `image/webp`, `image/gif` (via OpenR
|
|
|
3323
3292
|
"duration": 6, "resolution": "1080p", "aspect_ratio": "16:9", "enhance_prompt": true } }
|
|
3324
3293
|
```
|
|
3325
3294
|
|
|
3295
|
+
> **Switching a clip's model has cross-node constraints** — validate after every model edit. `validate` gates all of these before any billed run:
|
|
3296
|
+
> - **One aspect_ratio for the whole canvas.** Every `video_generate` node must agree (`VIDEO_ASPECT_MISMATCH`), or the composite silently crops/letterboxes. Switching one clip to Veo (which only offers `16:9`/`9:16`) forces every other clip to a shared ratio too.
|
|
3297
|
+
> - **`duration` is a per-model hard enum.** Seedance `4,5,6,8,10,12,15`; Veo `4,6,8` only. A value outside the model's set is rejected; a scene whose natural span exceeds the model's max is flagged (`VIDEO_SPAN_EXCEEDS_MODEL`, advisory) — a 9.2s scene can't fit Veo's 8s cap and would truncate; split the scene or keep it on a longer-max model.
|
|
3298
|
+
> - **`person_generation` differs.** Veo accepts only `allow_all`.
|
|
3299
|
+
>
|
|
3300
|
+
> A scaffolded canvas carries this table inline at `metadata.todo.model_constraints`.
|
|
3301
|
+
|
|
3302
|
+
> **Content-policy blocks are deterministic, not flaky.** fal.ai/Seedance rejects any first/last frame that reads as a real-person likeness (even an AI-generated face) — surfaced as `content_policy_blocked` (HTTP 422, **non-retryable**), even when fal's proxy chain masks it as a 5xx. Retrying **never** succeeds and wastes credits. Fix the cause: switch the clip to the other curated model (Veo routes around fal's filter), or make the source frame less photorealistic.
|
|
3303
|
+
|
|
3326
3304
|
---
|
|
3327
3305
|
|
|
3328
3306
|
##### `tts`
|
|
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
mod
|
|
25
25
|
));
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
|
|
28
28
|
var require_safe_stable_stringify = __commonJS({
|
|
29
|
-
"
|
|
29
|
+
"../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
|
|
30
30
|
"use strict";
|
|
31
31
|
var { hasOwnProperty } = Object.prototype;
|
|
32
32
|
var stringify = configure2();
|
|
@@ -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":
|
|
@@ -736,6 +739,9 @@ async function parseErrorBody(res) {
|
|
|
736
739
|
return classifyHttpError(res.status, errObj, errObj.message ?? `HTTP ${res.status}`);
|
|
737
740
|
}
|
|
738
741
|
function classifyHttpError(status, errObj, message) {
|
|
742
|
+
if (errObj.code === CONTENT_POLICY_CODE) {
|
|
743
|
+
return { kind: "content_policy", status, provider: errObj.provider, message };
|
|
744
|
+
}
|
|
739
745
|
if (status === 401 || status === 403) {
|
|
740
746
|
return { kind: "unauthorized", status, message };
|
|
741
747
|
}
|
|
@@ -774,6 +780,24 @@ function isAsyncJob(res) {
|
|
|
774
780
|
return typeof res.job_id === "string";
|
|
775
781
|
}
|
|
776
782
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
783
|
+
function failedJobError(error) {
|
|
784
|
+
if (error.code === CONTENT_POLICY_CODE) {
|
|
785
|
+
return new BackendHttpError({
|
|
786
|
+
kind: "content_policy",
|
|
787
|
+
status: error.status ?? 422,
|
|
788
|
+
provider: error.provider,
|
|
789
|
+
message: error.message ?? "content policy blocked"
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
return new BackendHttpError({
|
|
793
|
+
kind: "provider",
|
|
794
|
+
status: error.status ?? 502,
|
|
795
|
+
provider: error.provider,
|
|
796
|
+
code: error.code ?? "provider_error",
|
|
797
|
+
message: error.message ?? "deconstruct failed",
|
|
798
|
+
retryable: error.retryable ?? false
|
|
799
|
+
});
|
|
800
|
+
}
|
|
777
801
|
var JOB_POLL_INTERVAL_MS = 3e3;
|
|
778
802
|
var JOB_POLL_MAX_MS = 20 * 60 * 1e3;
|
|
779
803
|
var BackendClient = class {
|
|
@@ -797,16 +821,7 @@ var BackendClient = class {
|
|
|
797
821
|
}
|
|
798
822
|
const job = await this.http.getJson(path16, signal);
|
|
799
823
|
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
|
-
}
|
|
824
|
+
if (job.status === "failed") throw failedJobError(job.error);
|
|
810
825
|
if (Date.now() > deadline) {
|
|
811
826
|
throw new BackendHttpError({ kind: "timeout", message: `job ${jobId} did not finish in time` });
|
|
812
827
|
}
|
|
@@ -845,6 +860,9 @@ function requireCredentialsFromEnv(env = process.env) {
|
|
|
845
860
|
}
|
|
846
861
|
|
|
847
862
|
// src/engine/engine/errors.ts
|
|
863
|
+
function isBlocking(issue) {
|
|
864
|
+
return issue.severity !== "warning";
|
|
865
|
+
}
|
|
848
866
|
var CanvasError = class extends Error {
|
|
849
867
|
constructor(message) {
|
|
850
868
|
super(message);
|
|
@@ -895,6 +913,8 @@ function describeCause(c) {
|
|
|
895
913
|
return `timeout${c.provider ? ` (${c.provider})` : ""}`;
|
|
896
914
|
case "network":
|
|
897
915
|
return c.cause instanceof Error ? `network: ${c.cause.message}` : "network error";
|
|
916
|
+
case "content_policy":
|
|
917
|
+
return `CONTENT_POLICY_BLOCKED${c.provider ? ` (${c.provider})` : ""}: ${c.message}`;
|
|
898
918
|
default: {
|
|
899
919
|
const _exhaustive = c;
|
|
900
920
|
return String(_exhaustive);
|
|
@@ -902,7 +922,7 @@ function describeCause(c) {
|
|
|
902
922
|
}
|
|
903
923
|
}
|
|
904
924
|
|
|
905
|
-
//
|
|
925
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
906
926
|
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
907
927
|
var configure = import__.default.configure;
|
|
908
928
|
var wrapper_default = import__.default;
|
|
@@ -1638,7 +1658,23 @@ var VideoMeta = z.object({
|
|
|
1638
1658
|
// on which spoken beat" map emitted by scaffold-video (per-scene window,
|
|
1639
1659
|
// spoken line, storyboard frames, scheduled graphics). Free-form rows so the
|
|
1640
1660
|
// schema stays decoupled from the scaffold's exact shape.
|
|
1641
|
-
motion_board: z.array(z.unknown()).optional()
|
|
1661
|
+
motion_board: z.array(z.unknown()).optional(),
|
|
1662
|
+
// The recurring identity elements the scaffold wired (one shared reference slot
|
|
1663
|
+
// per cast member / pet / product / logo). The reference-completeness check reads
|
|
1664
|
+
// this to warn when a frame's description mentions an element whose ref isn't
|
|
1665
|
+
// wired on that frame node (the wrong-identity-animal failure).
|
|
1666
|
+
elements: z.array(
|
|
1667
|
+
z.object({
|
|
1668
|
+
ref: z.string(),
|
|
1669
|
+
label: z.string(),
|
|
1670
|
+
type: z.string(),
|
|
1671
|
+
description: z.string().optional()
|
|
1672
|
+
})
|
|
1673
|
+
).optional(),
|
|
1674
|
+
// Per video_generate node: the scene's natural visual span. The validator warns
|
|
1675
|
+
// when a clip's span exceeds the assigned model's max clip duration (e.g. a 9.2s
|
|
1676
|
+
// scene on Veo, which caps at 8s) — the clip would truncate.
|
|
1677
|
+
clip_spans: z.array(z.object({ node: z.string(), span_s: z.number() })).optional()
|
|
1642
1678
|
}).strict().optional();
|
|
1643
1679
|
var CanvasMetadata = z.object({
|
|
1644
1680
|
name: z.string().optional(),
|
|
@@ -2136,8 +2172,11 @@ var STAGE_CODES = {
|
|
|
2136
2172
|
AUDIO_DURATION: "VIDEO_AUDIO_DURATION",
|
|
2137
2173
|
LIPSYNC_MISSING: "VIDEO_LIPSYNC_MISSING",
|
|
2138
2174
|
SPEECH_OVERRUN: "VIDEO_SPEECH_OVERRUN",
|
|
2139
|
-
ASPECT_MISMATCH: "VIDEO_ASPECT_MISMATCH"
|
|
2175
|
+
ASPECT_MISMATCH: "VIDEO_ASPECT_MISMATCH",
|
|
2176
|
+
REFERENCE_MISSING: "VIDEO_REFERENCE_MISSING",
|
|
2177
|
+
SPAN_EXCEEDS_MODEL: "VIDEO_SPAN_EXCEEDS_MODEL"
|
|
2140
2178
|
};
|
|
2179
|
+
var SPAN_MODEL_SLACK_S = 0.25;
|
|
2141
2180
|
var VIDEO_TIME_SLACK_S = 0.75;
|
|
2142
2181
|
var SPEECH_WORDS_PER_SECOND = 2.5;
|
|
2143
2182
|
var SPEECH_OVERRUN_RATIO = 1.6;
|
|
@@ -2168,8 +2207,10 @@ function validateCanvas(input, registry) {
|
|
|
2168
2207
|
const estimatedCredits = estimateCredits(ctx);
|
|
2169
2208
|
checkOutputRef(ctx);
|
|
2170
2209
|
checkVideoInvariants(ctx);
|
|
2171
|
-
|
|
2172
|
-
return { ok:
|
|
2210
|
+
const hasBlocking = issues.some(isBlocking);
|
|
2211
|
+
if (hasBlocking) return { ok: false, issues };
|
|
2212
|
+
const warnings = issues.filter((i) => !isBlocking(i));
|
|
2213
|
+
return { ok: true, canvas, estimatedCredits, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2173
2214
|
}
|
|
2174
2215
|
async function validateCanvasDeep(input, registry) {
|
|
2175
2216
|
const shallow = validateCanvas(input, registry);
|
|
@@ -2204,7 +2245,9 @@ async function validateCanvasDeep(input, registry) {
|
|
|
2204
2245
|
});
|
|
2205
2246
|
}
|
|
2206
2247
|
}
|
|
2207
|
-
|
|
2248
|
+
const hasBlocking = issues.some(isBlocking);
|
|
2249
|
+
if (hasBlocking) return { ok: false, issues };
|
|
2250
|
+
const warnings = [...shallow.warnings ?? [], ...issues.filter((i) => !isBlocking(i))];
|
|
2208
2251
|
const perNodeCredits = canvas.nodes.map((n) => {
|
|
2209
2252
|
const def = registry.get(n.type);
|
|
2210
2253
|
let credits = 0;
|
|
@@ -2217,7 +2260,13 @@ async function validateCanvasDeep(input, registry) {
|
|
|
2217
2260
|
}
|
|
2218
2261
|
return { node_id: n.id, node_type: n.type, credits };
|
|
2219
2262
|
});
|
|
2220
|
-
return {
|
|
2263
|
+
return {
|
|
2264
|
+
ok: true,
|
|
2265
|
+
canvas,
|
|
2266
|
+
estimatedCredits: shallow.estimatedCredits,
|
|
2267
|
+
perNodeCredits,
|
|
2268
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
2269
|
+
};
|
|
2221
2270
|
}
|
|
2222
2271
|
function buildIdToIndex(canvas) {
|
|
2223
2272
|
const m = /* @__PURE__ */ new Map();
|
|
@@ -2504,6 +2553,95 @@ function checkVideoInvariants(ctx) {
|
|
|
2504
2553
|
}
|
|
2505
2554
|
checkSpeechOverrun(ctx, meta.talking_scenes);
|
|
2506
2555
|
checkAspectConsistency(ctx);
|
|
2556
|
+
checkReferenceCompleteness(ctx, meta);
|
|
2557
|
+
checkClipSpanFitsModel(ctx, meta);
|
|
2558
|
+
}
|
|
2559
|
+
var ELEMENT_TYPE_KEYWORDS = {
|
|
2560
|
+
animal: ["dog", "puppy", "pup", "cat", "kitten", "kitty", "pet", "canine", "feline"],
|
|
2561
|
+
logo: ["logo", "wordmark"],
|
|
2562
|
+
badge: ["badge", "seal"],
|
|
2563
|
+
product: []
|
|
2564
|
+
};
|
|
2565
|
+
var CHECKED_ELEMENT_TYPES = new Set(Object.keys(ELEMENT_TYPE_KEYWORDS));
|
|
2566
|
+
var KEYWORD_STOPWORDS = /* @__PURE__ */ new Set([
|
|
2567
|
+
"the",
|
|
2568
|
+
"and",
|
|
2569
|
+
"with",
|
|
2570
|
+
"for",
|
|
2571
|
+
"brand",
|
|
2572
|
+
"logo",
|
|
2573
|
+
"image",
|
|
2574
|
+
"shot",
|
|
2575
|
+
"main",
|
|
2576
|
+
"hero",
|
|
2577
|
+
"element",
|
|
2578
|
+
"product",
|
|
2579
|
+
"reference"
|
|
2580
|
+
]);
|
|
2581
|
+
function keywordTokens(text) {
|
|
2582
|
+
if (!text) return [];
|
|
2583
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !KEYWORD_STOPWORDS.has(t));
|
|
2584
|
+
}
|
|
2585
|
+
function keywordsForElement(el) {
|
|
2586
|
+
const type = el.type.toLowerCase();
|
|
2587
|
+
const typeWords = ELEMENT_TYPE_KEYWORDS[type] ?? [];
|
|
2588
|
+
return [.../* @__PURE__ */ new Set([...typeWords, ...keywordTokens(el.label), ...keywordTokens(el.description)])];
|
|
2589
|
+
}
|
|
2590
|
+
function containsWord(text, word) {
|
|
2591
|
+
const esc = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2592
|
+
return new RegExp(`\\b${esc}\\b`, "i").test(text);
|
|
2593
|
+
}
|
|
2594
|
+
function checkFrameReferences(ctx, node, index, keyworded) {
|
|
2595
|
+
const prompt = node.params?.prompt;
|
|
2596
|
+
if (typeof prompt !== "string" || prompt.length === 0) return;
|
|
2597
|
+
const inputsBlob = JSON.stringify(node.inputs ?? {});
|
|
2598
|
+
for (const { el, keywords } of keyworded) {
|
|
2599
|
+
if (inputsBlob.includes(el.ref)) continue;
|
|
2600
|
+
const hit = keywords.find((kw) => containsWord(prompt, kw));
|
|
2601
|
+
if (!hit) continue;
|
|
2602
|
+
ctx.issues.push({
|
|
2603
|
+
path: `nodes[${index}].inputs.reference`,
|
|
2604
|
+
code: STAGE_CODES.REFERENCE_MISSING,
|
|
2605
|
+
severity: "warning",
|
|
2606
|
+
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`,
|
|
2607
|
+
node_id: node.id,
|
|
2608
|
+
node_type: node.type
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
function checkReferenceCompleteness(ctx, meta) {
|
|
2613
|
+
const elements = (meta.elements ?? []).filter((el) => CHECKED_ELEMENT_TYPES.has(el.type.toLowerCase()));
|
|
2614
|
+
const keyworded = elements.map((el) => ({ el, keywords: keywordsForElement(el) })).filter((e) => e.keywords.length > 0);
|
|
2615
|
+
if (keyworded.length === 0) return;
|
|
2616
|
+
for (let i = 0; i < ctx.canvas.nodes.length; i++) {
|
|
2617
|
+
const n = ctx.canvas.nodes[i];
|
|
2618
|
+
if (n?.type === "image_generate") checkFrameReferences(ctx, n, i, keyworded);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
function videoModelMaxDuration(model) {
|
|
2622
|
+
const spec = MODEL_REGISTRY.video_generate[model];
|
|
2623
|
+
const durations = spec?.params?.duration?.enum;
|
|
2624
|
+
if (!durations) return void 0;
|
|
2625
|
+
const nums = durations.filter((d) => typeof d === "number");
|
|
2626
|
+
return nums.length > 0 ? Math.max(...nums) : void 0;
|
|
2627
|
+
}
|
|
2628
|
+
function checkClipSpanFitsModel(ctx, meta) {
|
|
2629
|
+
for (const { node, span_s } of meta.clip_spans ?? []) {
|
|
2630
|
+
const target = ctx.canvas.nodes.find((n) => n.id === node && n.type === "video_generate");
|
|
2631
|
+
if (!target) continue;
|
|
2632
|
+
const model = target.params?.model;
|
|
2633
|
+
if (typeof model !== "string") continue;
|
|
2634
|
+
const max = videoModelMaxDuration(model);
|
|
2635
|
+
if (max === void 0 || span_s <= max + SPAN_MODEL_SLACK_S) continue;
|
|
2636
|
+
ctx.issues.push({
|
|
2637
|
+
path: `nodes[${ctx.idToIndex.get(node) ?? -1}].params.duration`,
|
|
2638
|
+
code: STAGE_CODES.SPAN_EXCEEDS_MODEL,
|
|
2639
|
+
severity: "warning",
|
|
2640
|
+
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`,
|
|
2641
|
+
node_id: node,
|
|
2642
|
+
node_type: "video_generate"
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2507
2645
|
}
|
|
2508
2646
|
function speechOverrunOf(node) {
|
|
2509
2647
|
const params = node.params;
|
|
@@ -2684,6 +2822,9 @@ var Engine = class {
|
|
|
2684
2822
|
const writer = new OutputWriter({ outputsDir: this.outputsDir, runId });
|
|
2685
2823
|
await writer.ensure();
|
|
2686
2824
|
this.log(`[validate] ok (${canvas.nodes.length} nodes, est. ${validation.estimatedCredits} credits)`);
|
|
2825
|
+
for (const w of validation.warnings ?? []) {
|
|
2826
|
+
this.log(`[warn ] ${w.code}${w.node_id ? ` (${w.node_id})` : ""}: ${w.message}`);
|
|
2827
|
+
}
|
|
2687
2828
|
const t0 = Date.now();
|
|
2688
2829
|
const outputs = {};
|
|
2689
2830
|
const counters = { cachedNodes: 0, totalCredits: 0 };
|
|
@@ -3253,6 +3394,13 @@ function mapClientError(ctx, e) {
|
|
|
3253
3394
|
provider: d.provider
|
|
3254
3395
|
});
|
|
3255
3396
|
}
|
|
3397
|
+
if (d.kind === "content_policy") {
|
|
3398
|
+
return new NodeExecutionError(ctx.nodeId, ctx.nodeType, {
|
|
3399
|
+
kind: "content_policy",
|
|
3400
|
+
provider: d.provider,
|
|
3401
|
+
message: d.message
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3256
3404
|
if (d.kind === "timeout") {
|
|
3257
3405
|
return new NodeExecutionError(ctx.nodeId, ctx.nodeType, { kind: "timeout", provider: d.provider });
|
|
3258
3406
|
}
|
|
@@ -6351,4 +6499,4 @@ export {
|
|
|
6351
6499
|
defaultRegistry,
|
|
6352
6500
|
createEngineFromEnv
|
|
6353
6501
|
};
|
|
6354
|
-
//# sourceMappingURL=chunk-
|
|
6502
|
+
//# sourceMappingURL=chunk-ZWMMCLJ4.js.map
|