@koda-sl/baker-cli 0.133.0 → 0.133.3

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 CHANGED
@@ -3361,7 +3361,7 @@ Ref-image MIMEs: `image/png`, `image/jpeg`, `image/webp`, `image/gif` (via OpenR
3361
3361
  > - **gpt-image** (default frames): clean-plate exclusions placed **LAST** (OpenAI weights early text as composition), an explicit `photorealistic` cue, identity-critical reference **first**, and `quality=high` (gpt-image-2 forces high input fidelity automatically — the `input_fidelity` param is deliberately **not** sent). Gemini keeps exclusions inline; Recraft drops the photoreal cue.
3362
3362
  > - New advisories: `VIDEO_RAW_FACE_KEYFRAME` (a Seedance clip animating a RAW ingested image — real-person 422 risk), `IMAGE_PROMPT_PROFILE_MISSING`, `VIDEO_PROMPT_PROFILE_MISSING`.
3363
3363
 
3364
- > **Content-policy blocks are deterministic, not flaky.** ByteDance's 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**). This is ByteDance's upstream filter, so it fires on **any** reseller (Replicate or otherwise) — a provider swap does **not** route around it. Retrying **never** succeeds and wastes credits. Fix the cause: use a **synthetic/AI-generated** (non-identifiable) presenter face, or route a real face to **Veo** (`person_generation: allow_adult`), or make the source frame less photorealistic.
3364
+ > **Content-policy blocks are deterministic, not flaky — and now auto-recover.** ByteDance's 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/`E005` "flagged as sensitive", **non-retryable on the same model**). This is ByteDance's upstream filter, so it fires on **any** reseller (Replicate or otherwise) — a provider swap does **not** route around it. **When a Seedance `video_generate` clip is blocked, the backend automatically re-renders that same clip on `google/veo-3.1-fast` (`person_generation: allow_adult`) the one curated model that clears the filter — so the run still completes.** The Seedance attempt is never billed (its output was never finalized); only the Veo render is charged. To skip that wasted first attempt entirely (and control look/cost), scaffold a photoreal-cast ad directly on Veo up front (`--real-face`, which also covers AI-generated photoreal faces), or make the source frame less photorealistic.
3365
3365
 
3366
3366
  ---
3367
3367
 
@@ -3966,7 +3966,7 @@ baker canvas run ./reference-ad.video.canvas.json
3966
3966
  | `--select-model <id>` | `~google/gemini-flash-latest` | Override the element-selection `text_generate` model. |
3967
3967
  | `--image-model <id>` | `openai/gpt-5.4-image-2` | Override the per-frame `image_generate` model (defaults to the strongest, matching `scaffold-static-ad`). |
3968
3968
  | `--video-model <id>` | scored router | Override the `video_generate` model **and skip the router**. Curated roster: `bytedance/seedance-2.0` (workhorse), `google/veo-3.1` (cine ceiling + real-face), `google/veo-3.1-fast` (cheap Veo), `kwaivgi/kling-v3.0-pro` (motion-transfer). |
3969
- | `--real-face` | off | Brief needs a real human likeness the router picks Veo (dodges the ByteDance real-person 422). |
3969
+ | `--real-face` | auto-detected | Force the Veo route for a photoreal human presenter (dodges the ByteDance real-person filter). **Usually unnecessary** — the scaffold auto-detects a photoreal on-camera person/animal cast from the deconstruct and routes it to Veo on its own; set this only to force it when the cast isn't detected. Covers AI-generated photoreal faces too, not just real ones. |
3970
3970
  | `--motion-transfer` | off | Motion driven from a reference / hyper-dynamic → the router picks Kling. |
3971
3971
  | `--identity` | off | Same character/product across clips → the router picks the Seedance workhorse. |
3972
3972
  | `--budget <tier>` | `standard` | Router cost posture: `economy` (cheap Veo-fast tier) / `standard` / `premium`. |
@@ -3975,7 +3975,10 @@ baker canvas run ./reference-ad.video.canvas.json
3975
3975
  **Scored model router.** When `--video-model` is omitted, a small weighted scorer picks from the curated
3976
3976
  roster and prints the choice as `models.video_route_reason` in the run report (mirroring Higgsfield's
3977
3977
  `models_explore`). Real-face → Veo, motion-transfer → Kling, else the Seedance workhorse; `--budget economy`
3978
- shifts a Veo pick to the fast tier. The scaffolder also bakes **per-intent param recipes** so the hero/reveal
3978
+ shifts a Veo pick to the fast tier. **The router runs after the deconstruct, so a photoreal on-camera
3979
+ person/animal cast is auto-detected and routed to Veo up front** — that cast would be blocked on Seedance by
3980
+ ByteDance's real-person filter, so picking Seedance would only burn a wasted attempt before the runtime
3981
+ auto-fallback re-renders it on Veo anyway. The scaffolder also bakes **per-intent param recipes** so the hero/reveal
3979
3982
  beat claims the 1080p cine ceiling automatically and a Kling hook beat maxes prompt adherence.
3980
3983
 
3981
3984
  Each scene is captured in a **shoot mode** — `ugc_selfie` (talking heads, the default look), `ugc_broll`, `studio_product` (pack shot), `lifestyle_cinematic`, or `screen_ui`. The scaffold derives one per scene (UGC by default; the cinematic and screen lanes are opt-in) and bakes its capture block into the frame and a camera default into the clip; override per scene with a `shoot_mode` field in that scene's `scenes/sNN.json`. Capture aesthetic + depth-of-field follow the mode (UGC stays flat; studio/lifestyle allow shallow DoF). Clips also carry **diegetic native audio** — the scene's own ambience described in the Seedance prompt, never music (the music bed is a separate, ducked track); set a scene's `ambient` field to steer it.
@@ -2488,6 +2488,11 @@ import { readFile as readFile2 } from "fs/promises";
2488
2488
  import path3 from "path";
2489
2489
  import { z as z2 } from "zod";
2490
2490
 
2491
+ // src/engine/lib/url.ts
2492
+ function looksLikeHttpUrl(value) {
2493
+ return typeof value === "string" && /^https?:\/\//i.test(value.trim());
2494
+ }
2495
+
2491
2496
  // src/engine/scaffold/lib/prompt-profiles.ts
2492
2497
  var VEO_PERSON_GENERATION = "allow_adult";
2493
2498
  var VEO_NEGATIVE_PROMPT = "subtitles, captions, on-screen text, watermark, logo, warped face, distorted hands, extra fingers, low quality";
@@ -2720,7 +2725,8 @@ var STAGE_CODES = {
2720
2725
  ODD_DIMENSIONS: "VIDEO_ODD_DIMENSIONS",
2721
2726
  REGION_DROPPED: "VIDEO_REGION_DROPPED",
2722
2727
  OVERLAY_OUT_OF_BOUNDS: "VIDEO_OVERLAY_OUT_OF_BOUNDS",
2723
- ORPHAN_NODE: "ORPHAN_NODE"
2728
+ ORPHAN_NODE: "ORPHAN_NODE",
2729
+ INGEST_PATH_URL_HEALED: "INGEST_PATH_URL_HEALED"
2724
2730
  };
2725
2731
  var SPAN_MODEL_SLACK_S = 0.25;
2726
2732
  var VIDEO_TIME_SLACK_S = 0.75;
@@ -2777,6 +2783,7 @@ function validateCanvas(input, registry) {
2777
2783
  checkUnknownNodeTypes(ctx);
2778
2784
  if (issues.some((i) => i.code === STAGE_CODES.UNKNOWN_NODE)) return { ok: false, issues };
2779
2785
  checkAllParams(ctx);
2786
+ checkIngestPathUrlHeals(ctx);
2780
2787
  checkAllModelParams(ctx);
2781
2788
  checkAllRefs(ctx);
2782
2789
  checkAllModelInputs(ctx);
@@ -2899,6 +2906,22 @@ function checkAllParams(ctx) {
2899
2906
  pushZodIssues(ctx.issues, parsed.error, `nodes[${i}].params`, STAGE_CODES.PARAMS, n.id, n.type);
2900
2907
  }
2901
2908
  }
2909
+ function checkIngestPathUrlHeals(ctx) {
2910
+ for (let i = 0; i < ctx.canvas.nodes.length; i++) {
2911
+ const n = ctx.canvas.nodes[i];
2912
+ if (!n || n.type !== "ingest") continue;
2913
+ const params = n.params ?? {};
2914
+ if (params.source !== "path" || !looksLikeHttpUrl(params.path)) continue;
2915
+ ctx.issues.push({
2916
+ path: `nodes[${i}].params.source`,
2917
+ code: STAGE_CODES.INGEST_PATH_URL_HEALED,
2918
+ message: `ingest "${n.id}" declares source:"path" but its value is a URL \u2014 running it as source:"url". Update the canvas JSON to source:"url" so it matches what baker runs.`,
2919
+ severity: "warning",
2920
+ node_id: n.id,
2921
+ node_type: n.type
2922
+ });
2923
+ }
2924
+ }
2902
2925
  function checkAllModelParams(ctx) {
2903
2926
  for (let i = 0; i < ctx.canvas.nodes.length; i++) {
2904
2927
  const n = ctx.canvas.nodes[i];
@@ -4534,18 +4557,30 @@ function mapClientError(ctx, e) {
4534
4557
  // src/engine/nodes/ingest.ts
4535
4558
  var execFile = promisify(execFileCb);
4536
4559
  var ExpectEnum = z5.enum(["image", "video", "audio", "text", "json", "font"]);
4537
- var IngestParams = z5.discriminatedUnion("source", [
4538
- z5.object({
4539
- source: z5.literal("url"),
4540
- url: z5.string().url(),
4541
- expect: ExpectEnum
4542
- }).strict(),
4543
- z5.object({
4544
- source: z5.literal("path"),
4545
- path: z5.string().min(1),
4546
- expect: ExpectEnum
4547
- }).strict()
4548
- ]);
4560
+ function coercePathUrlToUrl(input) {
4561
+ if (!input || typeof input !== "object") return input;
4562
+ const obj = input;
4563
+ if (obj.source === "path" && looksLikeHttpUrl(obj.path)) {
4564
+ const { path: _path, ...rest } = obj;
4565
+ return { ...rest, source: "url", url: obj.path.trim() };
4566
+ }
4567
+ return input;
4568
+ }
4569
+ var IngestParams = z5.preprocess(
4570
+ coercePathUrlToUrl,
4571
+ z5.discriminatedUnion("source", [
4572
+ z5.object({
4573
+ source: z5.literal("url"),
4574
+ url: z5.string().url(),
4575
+ expect: ExpectEnum
4576
+ }).strict(),
4577
+ z5.object({
4578
+ source: z5.literal("path"),
4579
+ path: z5.string().min(1),
4580
+ expect: ExpectEnum
4581
+ }).strict()
4582
+ ])
4583
+ );
4549
4584
  var IngestInputs = z5.object({}).strict();
4550
4585
  var IngestOutputs = z5.object({ asset: AssetRef }).strict();
4551
4586
  var RAW_TEXT_EXTENSIONS = [".txt", ".md"];
@@ -7272,8 +7307,8 @@ var videoGenerateNode = delegated({
7272
7307
  id: "video_generate",
7273
7308
  version: "2.0.0",
7274
7309
  category: "video",
7275
- summary: "Generate video for ad creatives. Curated roster: `bytedance/seedance-2.0` (identity/product workhorse), `google/veo-3.1` (photoreal cine ceiling + real-face fallback), `google/veo-3.1-fast` (cheap Veo iteration), `kwaivgi/kling-3.0` (motion-transfer/dynamic). Async with polling.",
7276
- when_to_use: "Use `bytedance/seedance-2.0` for identity/product output. Route real human likenesses to `google/veo-3.1` (dodges the ByteDance real-person filter); use `google/veo-3.1-fast` while iterating to keep cost low; `kwaivgi/kling-3.0` for motion-transfer/hyper-dynamic beats. The scaffolder's scored router picks for you. Each model gates its own durations/resolutions/aspect ratios in the registry \u2014 see the README per-model section.",
7310
+ summary: "Generate video for ad creatives. Curated roster: `bytedance/seedance-2.0` (identity/product workhorse, NO photoreal human faces), `google/veo-3.1` (photoreal cine ceiling + the human-face model), `google/veo-3.1-fast` (cheap Veo iteration), `kwaivgi/kling-3.0` (motion-transfer/dynamic). Async with polling.",
7311
+ when_to_use: "Use `bytedance/seedance-2.0` for identity/product output WITHOUT a photoreal human face on camera. Any clip whose frame shows a photoreal human face \u2014 a UGC creator, presenter, or testimonial speaker, REAL OR AI-GENERATED \u2014 must use `google/veo-3.1`: ByteDance's filter rejects photoreal human faces on Seedance (content_policy_blocked/E005), and a synthetic AI face does NOT clear it. Use `google/veo-3.1-fast` while iterating to keep cost low; `kwaivgi/kling-3.0` for motion-transfer/hyper-dynamic beats. `baker canvas scaffold-video` auto-routes a photoreal cast to Veo; a Seedance clip blocked at runtime also auto-recovers on Veo. Each model gates its own durations/resolutions/aspect ratios in the registry \u2014 see the README per-model section.",
7277
7312
  inputs: z28.object({
7278
7313
  first_frame: ImageRef.optional(),
7279
7314
  last_frame: ImageRef.optional(),
@@ -7744,6 +7779,7 @@ export {
7744
7779
  REF_PREFIX,
7745
7780
  parseRefExpr,
7746
7781
  sha256Hex,
7782
+ looksLikeHttpUrl,
7747
7783
  SEEDANCE_PROFILE,
7748
7784
  clipProfileFor,
7749
7785
  clipParamRecipe,
@@ -7762,4 +7798,4 @@ export {
7762
7798
  defaultRegistry,
7763
7799
  createEngineFromEnv
7764
7800
  };
7765
- //# sourceMappingURL=chunk-UBFU3E7L.js.map
7801
+ //# sourceMappingURL=chunk-CIF62V7L.js.map