@koda-sl/baker-cli 0.180.0 → 0.181.1-dev.3fc14023b
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 +55 -3
- package/dist/{chunk-P2T3IZRE.js → chunk-UZ37VVP4.js} +175 -23
- package/dist/chunk-UZ37VVP4.js.map +1 -0
- package/dist/cli.js +57 -23
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-P2T3IZRE.js.map +0 -1
package/README.md
CHANGED
|
@@ -1937,6 +1937,7 @@ baker images generate "flat geometric mascot, brand palette" \
|
|
|
1937
1937
|
| Model | Best for | Aspect ratios | Sizes |
|
|
1938
1938
|
|---|---|---|---|
|
|
1939
1939
|
| `google/gemini-3.1-flash-image-preview` **(default)** | Fast iteration, reference editing, extreme aspect ratios (Nano Banana flash) | standard **+** `1:4` `4:1` `1:8` `8:1` | `0.5K`–`4K` |
|
|
1940
|
+
| `google/gemini-3.1-flash-lite-image` | Cheapest tier (Nano Banana 2 Lite) — half the flash price per output token. High-volume variants and any 1K-sufficient frame, portrait ad formats included | standard **+** `1:4` `4:1` `1:8` `8:1` | `1K` **only** |
|
|
1940
1941
|
| `google/gemini-3-pro-image-preview` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
|
|
1941
1942
|
| `openai/gpt-image-2` | Photoreal + cleanest in-image text — ad/landing reproduction | standard set **minus** `4:5` `5:4` | *(none — derived from the ratio)* |
|
|
1942
1943
|
| `openai/gpt-5.4-image-2` | **Legacy** — accepted so canvases authored before the gpt-image-2 switch still re-run. Don't pick it for new work. | standard set | `1K` `2K` `4K` (ignored upstream) |
|
|
@@ -1946,13 +1947,15 @@ Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9`
|
|
|
1946
1947
|
|
|
1947
1948
|
`openai/gpt-image-2` accepts neither `image_size` nor the `4:5` / `5:4` portrait ratios — OpenAI picks the pixel dimensions from the ratio. To ship a 4:5 Meta feed ad from it, render the hero at `3:4` and adapt with `image_aspect_adapt`; the scaffolders do this for you.
|
|
1948
1949
|
|
|
1950
|
+
`google/gemini-3.1-flash-lite-image` renders at `1K` and nothing else — `2K` / `4K` is a provider 400 (`resolution: not supported. Accepted: 1K`), not a soft clamp. It costs half of `google/gemini-3.1-flash-image-preview` per output image token ($0.00003 vs $0.00006), so it is the cheap-iteration tier for images. Keep hero stills and reference sheets that need `2K`+ on the flash or pro tier.
|
|
1951
|
+
|
|
1949
1952
|
**Flags:**
|
|
1950
1953
|
|
|
1951
1954
|
| Flag | Description |
|
|
1952
1955
|
|---|---|
|
|
1953
1956
|
| `--model` | Model id (default `google/gemini-3.1-flash-image-preview`) |
|
|
1954
1957
|
| `--aspect-ratio` | Output aspect ratio (default `1:1`) |
|
|
1955
|
-
| `--image-size` | Resolution: `1K` (default) `2K` `4K` (Gemini flash also `0.5K`) |
|
|
1958
|
+
| `--image-size` | Resolution: `1K` (default) `2K` `4K` (Gemini flash also `0.5K`; Gemini flash-lite is `1K` only) |
|
|
1956
1959
|
| `--reference` | Comma-separated visual references, each either a **public image URL** (Pinterest / stock / library `imageUrl`) **or a local file path** (a sandbox image — brand logo, product shot, cropped photo, screenshot). Local files are downscaled (≤1536px) and inlined automatically — no manual upload. Applied in order; the biggest quality lever for photographed, on-brand output. Split is on `,`, so a URL containing a literal comma in its query string would be torn in two (rare for image CDNs — pass it alone if it occurs); a single `data:` URL is taken whole. |
|
|
1957
1960
|
| `--strength` | Recraft only: vectorization strength 0–1 |
|
|
1958
1961
|
| `--rgb-colors` | Recraft only: JSON palette `[[r,g,b],…]` |
|
|
@@ -3361,6 +3364,24 @@ Gemini flash image model — the default. Fast iteration, reference editing, ext
|
|
|
3361
3364
|
| `aspect_ratio` | enum | no | EXTREME AR |
|
|
3362
3365
|
| `image_size` | enum | no | `0.5K \| 1K \| 2K \| 4K` |
|
|
3363
3366
|
|
|
3367
|
+
###### Model: `google/gemini-3.1-flash-lite-image`
|
|
3368
|
+
|
|
3369
|
+
"Nano Banana 2 Lite" — the cheap-iteration tier. Same Gemini image family as the flash model (same extreme ratios, same reference editing, up to 14 reference images) at **half the output-image-token price** ($0.00003 vs $0.00006), so a 1K render lands around 5 credits. What it gives up is resolution: `1K` is the only size it renders.
|
|
3370
|
+
|
|
3371
|
+
| Name | Type | Required | Notes |
|
|
3372
|
+
|---|---|---|---|
|
|
3373
|
+
| `model` | literal | yes | `"google/gemini-3.1-flash-lite-image"` |
|
|
3374
|
+
| `prompt` | string | yes | non-empty |
|
|
3375
|
+
| `aspect_ratio` | enum | no | EXTREME AR — keeps `4:5` / `5:4`, so Meta/Instagram portrait formats come straight out of it |
|
|
3376
|
+
| `image_size` | enum | no | `1K` only — `2K` / `4K` is a hard provider 400 (`resolution: not supported. Accepted: 1K`), so `validate` rejects it before the call costs anything |
|
|
3377
|
+
|
|
3378
|
+
Reach for it for high-volume variants and any frame `1K` covers. Send hero stills and the `image_reference_sheet` subject sheet — anything that needs `2K`+ — to `google/gemini-3.1-flash-image-preview` or `google/gemini-3-pro-image-preview`. Registered for `image_generate` only; `image_reference_sheet` and `image_aspect_adapt` do not accept it.
|
|
3379
|
+
|
|
3380
|
+
```json
|
|
3381
|
+
{ "id": "variant_a", "type": "image_generate",
|
|
3382
|
+
"params": { "model": "google/gemini-3.1-flash-lite-image", "prompt": "Portrait feed ad for a citrus energy drink …", "aspect_ratio": "4:5", "image_size": "1K" } }
|
|
3383
|
+
```
|
|
3384
|
+
|
|
3364
3385
|
###### Model: `google/gemini-3-pro-image-preview`
|
|
3365
3386
|
|
|
3366
3387
|
"Nano Banana Pro" — heavier, higher-quality Gemini.
|
|
@@ -3665,7 +3686,7 @@ Fail-fast: an invalid model selection (wrong count, duplicate or out-of-range pi
|
|
|
3665
3686
|
|
|
3666
3687
|
##### `video_generate`
|
|
3667
3688
|
|
|
3668
|
-
Generate video. Async with polling
|
|
3689
|
+
Generate video. Curated model roster (per-model params below). Async with polling, except `google/gemini-omni-flash`, which returns its clip inline.
|
|
3669
3690
|
|
|
3670
3691
|
**Inputs**
|
|
3671
3692
|
|
|
@@ -3688,6 +3709,36 @@ Accepted ref-image MIMEs vary by model — see per-model sections below.
|
|
|
3688
3709
|
|
|
3689
3710
|
---
|
|
3690
3711
|
|
|
3712
|
+
###### Model: `google/gemini-omni-flash` (default)
|
|
3713
|
+
|
|
3714
|
+
The default `video_generate` model (`DEFAULT_VIDEO_GENERATE_MODEL`). Natively multimodal — one call returns picture **and** synchronized audio. **Not on OpenRouter** (its `GET /api/v1/videos/models` catalogue of 17 video models does not include it): routed direct to Google's **Interactions API**, `POST https://generativelanguage.googleapis.com/v1beta/interactions`, wire model `gemini-omni-flash-preview`. Its own `generateContent` endpoint refuses the model ("This model only supports Interactions API"), and it does not use Veo's `predictLongRunning` shape. The call is synchronous (~30–40s) and returns the MP4 inline as base64 — no polling.
|
|
3715
|
+
|
|
3716
|
+
Ref-image MIMEs: `image/png`, `image/jpeg`, `image/webp`. Takes **one** conditioning image — there is no `last_frame`, and wiring one raises `VIDEO_MODEL_IGNORES_INPUT`. Tasks: `text_to_video`, `image_to_video`, `reference_to_video`, `edit`, `extend` — the only curated model besides Seedance that accepts `references`.
|
|
3717
|
+
|
|
3718
|
+
| Name | Type | Required | Notes |
|
|
3719
|
+
|---|---|---|---|
|
|
3720
|
+
| `model` | literal | yes | `"google/gemini-omni-flash"` |
|
|
3721
|
+
| `prompt` | string | yes | non-empty |
|
|
3722
|
+
| `aspect_ratio` | enum | no | `16:9 \| 9:16` (`1:1` is rejected) |
|
|
3723
|
+
| `duration` | enum | no | `3, 4, 5, 6, 7, 8, 9, 10` seconds. Omitted ⇒ **10s** — the model's own default *and* its ceiling. |
|
|
3724
|
+
|
|
3725
|
+
> Those are the **only** accepted params. `resolution`, `seed`, `negative_prompt`, `generate_audio`, `person_generation`, `enhance_prompt` and `conditioning_scale` each fail pre-dispatch with a 400 `Unknown parameter`. Output is fixed: **720p** (1280×720 or 720×1280), 24fps, h264 + native stereo AAC — audio is always on.
|
|
3726
|
+
|
|
3727
|
+
> `duration` is the exception. Omni has **no duration field** — every spelling at every nesting level (`duration`, `duration_seconds`, `seconds`, `length`, `video_length`, `num_frames`, at top level, inside `response_format`, inside `generation_config.video_config`) comes back `Unknown parameter`. But it **honours a length asked for in the prompt**, so the backend adapter (`convex/lib/geminiOmni.ts`) translates the canonical `duration` param into a prompt directive. Measured: "exactly 3 seconds" → 3.008s, "exactly 6 seconds" → 6.016s, no length named → 10.005s.
|
|
3728
|
+
|
|
3729
|
+
> **The 3–10s range is ours to enforce.** Outside it the model does not error — it silently snaps (2s came back 3.008s, 15s came back 10.005s) and bills the length it actually rendered. That is why `baker canvas validate` gates the enum, for free, before the render.
|
|
3730
|
+
|
|
3731
|
+
```json
|
|
3732
|
+
{ "id": "clip", "type": "video_generate",
|
|
3733
|
+
"inputs": { "first_frame": "$ref:start.images#0" },
|
|
3734
|
+
"params": { "model": "google/gemini-omni-flash", "prompt": "Same person, talking head, …",
|
|
3735
|
+
"aspect_ratio": "9:16", "duration": 6 } }
|
|
3736
|
+
```
|
|
3737
|
+
|
|
3738
|
+
> **Billed per token, which resolves to a clean per-second rate** — $17.50 per 1M video output tokens ($1.50 per 1M input, noise), and 720p video is a flat **5,792 tokens per second at every length** (3.008s → 17,376 tokens; 6.016s → 34,752; 10.005s → 57,920). So **$0.10136 per second**: a 10s clip ~$1.01 (102 credits), a 4s clip ~$0.41 (41 credits), a 3s clip ~$0.30 (31 credits). Cutting a clip short is a real saving. A 5s 720p Seedance clip is $2.50.
|
|
3739
|
+
|
|
3740
|
+
> **Move off the default** when a beat needs 1080p, a length outside 3–10s, a `1:1` frame, or a photoreal human face on camera (that still routes to Veo, unchanged).
|
|
3741
|
+
|
|
3691
3742
|
###### Model: `bytedance/seedance-2.0`
|
|
3692
3743
|
|
|
3693
3744
|
Production-quality ad-creative model. Routed via **Replicate** (`bytedance/seedance-2.0`). NOTE: ByteDance's upstream "real person" likeness filter still blocks photorealistic human reference frames on **any** reseller — the escape is a synthetic/AI presenter face or routing real faces to Veo, not the provider.
|
|
@@ -4356,7 +4407,7 @@ baker canvas run ./reference-ad.video.canvas.json
|
|
|
4356
4407
|
| `--deconstruct-model <id>` | `~google/gemini-pro-latest` | Override the `video_deconstruct` model. |
|
|
4357
4408
|
| `--select-model <id>` | `~google/gemini-flash-latest` | Override the element-selection `text_generate` model. |
|
|
4358
4409
|
| `--image-model <id>` | `openai/gpt-image-2` | Override the per-frame `image_generate` model (defaults to the strongest, matching `scaffold-static-ad`). |
|
|
4359
|
-
| `--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). |
|
|
4410
|
+
| `--video-model <id>` | scored router | Override the `video_generate` model **and skip the router**. Curated roster: `google/gemini-omni-flash` (**the default video model** — native audio + reference images, ~$0.101 per second of clip, 720p/`16:9`\|`9:16`, `duration` 3–10s and 10s when unset), `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). |
|
|
4360
4411
|
| `--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. |
|
|
4361
4412
|
| `--motion-transfer` | off | Motion driven from a reference / hyper-dynamic → the router picks Kling. |
|
|
4362
4413
|
| `--identity` | off | Same character/product across clips → the router picks the Seedance workhorse. |
|
|
@@ -5022,6 +5073,7 @@ This CLI is designed for AI agent consumption. Key patterns:
|
|
|
5022
5073
|
- **0.179.0**: `baker ads google keywords update --final-url` / `--clear-final-url` — changing where one keyword sends its clicks no longer costs the keyword its history. The write contract has always accepted `finalUrls` on a keyword update (`baker capabilities google-ads` reported it as a field of `google.keyword.update`), but no CLI flag reached it, so an agent that followed capabilities got "update needs at least one field" and the only route left was remove + re-add — a new criterion to Google, learning from zero, for a change that was only ever about the destination. `--clear-final-url` stages the empty set Google reads as "inherit the ad's landing page", which is the undo for the misuse `--final-url` already warns about. Same nudge as `add` on the way in.
|
|
5023
5074
|
|
|
5024
5075
|
A keyword's final URL now also shows on its row when the keyword is being **removed**. 0.178.0 read it from the stage-time snapshot, but the snapshot never asked Google for it, so the removal half of that change silently did nothing — the card said which keyword was going away but not which landing page it had been sending its clicks to. The batch hint says outright that `--final-url` has no per-keyword form rather than implying destinations could differ within one call, and the tool doc now lists each `keywords` subcommand's own flags instead of one shared set (`--ad-group-ref`, `--text` and `--match-type` are `add`-only; `remove` takes a target and nothing else).
|
|
5076
|
+
- **0.180.1**: `baker tag-manager --help` said the container version Baker cuts at publish is **NOT** published, and told the agent to have the user publish it in Tag Manager. The executor publishes it — a clean run takes the client's container live — so the help understated an irreversible change to a production site. Corrected, and the stale `versionPendingMessage` helper (no callers, same wrong claim) removed from `@baker/api/tag-manager`. `draft list` already reported both outcomes correctly and is unchanged.
|
|
5025
5077
|
|
|
5026
5078
|
## Publishing
|
|
5027
5079
|
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
__toESM
|
|
8
8
|
} from "./chunk-YL3HDEIJ.js";
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js
|
|
11
11
|
var require_safe_stable_stringify = __commonJS({
|
|
12
|
-
"
|
|
12
|
+
"../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js"(exports, module) {
|
|
13
13
|
"use strict";
|
|
14
14
|
var { hasOwnProperty } = Object.prototype;
|
|
15
15
|
var stringify = configure2();
|
|
@@ -1075,7 +1075,7 @@ function resolveAdaptFormats(params) {
|
|
|
1075
1075
|
return params.formats ?? [];
|
|
1076
1076
|
}
|
|
1077
1077
|
|
|
1078
|
-
//
|
|
1078
|
+
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
1079
1079
|
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
1080
1080
|
var configure = import__.default.configure;
|
|
1081
1081
|
var wrapper_default = import__.default;
|
|
@@ -1122,9 +1122,14 @@ var OPENROUTER_IMAGE_AR_EXTREME = [...OPENROUTER_IMAGE_AR, "1:4", "4:1", "1:8",
|
|
|
1122
1122
|
var OPENROUTER_IMAGE_AR_GPT = ["1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9", "21:9"];
|
|
1123
1123
|
var OPENROUTER_IMAGE_SIZE = ["1K", "2K", "4K"];
|
|
1124
1124
|
var OPENROUTER_IMAGE_SIZE_EXTENDED = ["0.5K", ...OPENROUTER_IMAGE_SIZE];
|
|
1125
|
+
var GEMINI_LITE_IMAGE_SIZE = ["1K"];
|
|
1125
1126
|
var OPENROUTER_IMAGE_QUALITY = ["auto", "low", "medium", "high"];
|
|
1126
1127
|
var SEEDANCE_DURATIONS = [4, 5, 6, 8, 10, 12, 15];
|
|
1127
1128
|
var KLING_DURATIONS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
|
1129
|
+
var GEMINI_OMNI_DURATIONS = [3, 4, 5, 6, 7, 8, 9, 10];
|
|
1130
|
+
var GEMINI_OMNI_DEFAULT_SECONDS = 10;
|
|
1131
|
+
var GEMINI_OMNI_ASPECT_RATIOS = ["16:9", "9:16"];
|
|
1132
|
+
var GEMINI_OMNI_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp"];
|
|
1128
1133
|
var ELEVENLABS_OUTPUT_FORMATS = [
|
|
1129
1134
|
"mp3_22050_32",
|
|
1130
1135
|
"mp3_44100_32",
|
|
@@ -1144,16 +1149,30 @@ var IMAGE_GENERATE_MODELS = [
|
|
|
1144
1149
|
"openai/gpt-image-2",
|
|
1145
1150
|
"openai/gpt-5.4-image-2",
|
|
1146
1151
|
"google/gemini-3.1-flash-image-preview",
|
|
1152
|
+
"google/gemini-3.1-flash-lite-image",
|
|
1147
1153
|
"google/gemini-3-pro-image-preview",
|
|
1148
1154
|
"recraft/recraft-v4.1-pro-vector"
|
|
1149
1155
|
];
|
|
1150
1156
|
var VIDEO_GENERATE_MODELS = [
|
|
1151
1157
|
"bytedance/seedance-2.0",
|
|
1158
|
+
"google/gemini-omni-flash",
|
|
1152
1159
|
"google/veo-3.1",
|
|
1153
1160
|
"google/veo-3.1-fast",
|
|
1154
1161
|
"kwaivgi/kling-v3.0-pro"
|
|
1155
1162
|
];
|
|
1156
|
-
var DEFAULT_VIDEO_GENERATE_MODEL = "
|
|
1163
|
+
var DEFAULT_VIDEO_GENERATE_MODEL = "google/gemini-omni-flash";
|
|
1164
|
+
function isGeminiOmniModel(model) {
|
|
1165
|
+
return model.startsWith("google/gemini-omni");
|
|
1166
|
+
}
|
|
1167
|
+
var LAST_FRAME_TO_VIDEO_MODELS = [
|
|
1168
|
+
"bytedance/seedance-2.0",
|
|
1169
|
+
"google/veo-3.1",
|
|
1170
|
+
"google/veo-3.1-fast",
|
|
1171
|
+
"kwaivgi/kling-v3.0-pro"
|
|
1172
|
+
];
|
|
1173
|
+
function supportsLastFrame(model) {
|
|
1174
|
+
return LAST_FRAME_TO_VIDEO_MODELS.includes(model);
|
|
1175
|
+
}
|
|
1157
1176
|
var MODEL_REGISTRY = {
|
|
1158
1177
|
text_generate: {
|
|
1159
1178
|
"~google/gemini-flash-latest": {
|
|
@@ -1286,6 +1305,31 @@ var MODEL_REGISTRY = {
|
|
|
1286
1305
|
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE_EXTENDED }
|
|
1287
1306
|
}
|
|
1288
1307
|
},
|
|
1308
|
+
"google/gemini-3.1-flash-lite-image": {
|
|
1309
|
+
// Nano Banana 2 Lite. Same Gemini image family as the flash tier — same
|
|
1310
|
+
// extreme aspect-ratio set, same reference editing — at HALF the output-token
|
|
1311
|
+
// price ($0.00003 vs $0.00006 per output image token), which makes it the
|
|
1312
|
+
// cheap-iteration tier for image work the way `veo-3.1-fast` is for video.
|
|
1313
|
+
//
|
|
1314
|
+
// The one thing it gives up is resolution: it renders 1K and nothing else.
|
|
1315
|
+
// That is a hard provider 400, not a soft clamp, so it is gated here rather
|
|
1316
|
+
// than discovered at dispatch. A 2K/4K hero frame belongs on the flash or pro
|
|
1317
|
+
// tier. Unlike gpt-image it keeps 4:5 and 5:4, so Meta/Instagram portrait
|
|
1318
|
+
// formats still come straight out of it.
|
|
1319
|
+
//
|
|
1320
|
+
// Reached through the normal OpenRouter image gateway (Google Vertex and
|
|
1321
|
+
// Google AI Studio both serve it) — no direct-provider exception needed.
|
|
1322
|
+
// Source: GET /api/v1/images/models/google/gemini-3.1-flash-lite-image/endpoints.
|
|
1323
|
+
label: "Google Gemini 3.1 Flash Lite Image",
|
|
1324
|
+
inputs: [],
|
|
1325
|
+
optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
1326
|
+
required: ["prompt"],
|
|
1327
|
+
params: {
|
|
1328
|
+
prompt: { kind: "string" },
|
|
1329
|
+
aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR_EXTREME },
|
|
1330
|
+
image_size: { kind: "string", enum: GEMINI_LITE_IMAGE_SIZE }
|
|
1331
|
+
}
|
|
1332
|
+
},
|
|
1289
1333
|
"google/gemini-3-pro-image-preview": {
|
|
1290
1334
|
label: "Google Gemini 3 Pro Image (Nano Banana Pro)",
|
|
1291
1335
|
inputs: [],
|
|
@@ -1294,8 +1338,10 @@ var MODEL_REGISTRY = {
|
|
|
1294
1338
|
params: {
|
|
1295
1339
|
prompt: { kind: "string" },
|
|
1296
1340
|
aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR },
|
|
1297
|
-
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE }
|
|
1298
|
-
quality
|
|
1341
|
+
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE }
|
|
1342
|
+
// No `quality`: OpenRouter's per-endpoint schema lists it for gpt-image and
|
|
1343
|
+
// for NO Gemini image model, pro included. It is accepted-then-dropped, so
|
|
1344
|
+
// declaring it invites the scaffolder to emit a param that does nothing.
|
|
1299
1345
|
}
|
|
1300
1346
|
},
|
|
1301
1347
|
"recraft/recraft-v4.1-pro-vector": {
|
|
@@ -1407,6 +1453,41 @@ var MODEL_REGISTRY = {
|
|
|
1407
1453
|
generate_audio: { kind: "boolean" }
|
|
1408
1454
|
}
|
|
1409
1455
|
},
|
|
1456
|
+
"google/gemini-omni-flash": {
|
|
1457
|
+
// Google Gemini Omni Flash — the DEFAULT video model. Natively multimodal:
|
|
1458
|
+
// it reasons over text + images and renders video WITH synchronized audio in
|
|
1459
|
+
// one call, and it is the only curated model that takes reference images
|
|
1460
|
+
// through its own first-party API (task `reference_to_video`).
|
|
1461
|
+
//
|
|
1462
|
+
// NOT on OpenRouter. `GET /api/v1/videos/models` lists 17 video models and
|
|
1463
|
+
// Omni is not among them, so this model is the sanctioned direct-provider
|
|
1464
|
+
// exception (see `convex/lib/geminiOmni.ts`): Google's Interactions API,
|
|
1465
|
+
// `POST /v1beta/interactions`. Its own `generateContent` endpoint refuses it
|
|
1466
|
+
// ("This model only supports Interactions API"), so the Veo `predictLongRunning`
|
|
1467
|
+
// shape does not apply either.
|
|
1468
|
+
//
|
|
1469
|
+
// The param set below is DELIBERATELY tiny and was read off the live API, not
|
|
1470
|
+
// off the docs — every knob the other video models take is rejected with a
|
|
1471
|
+
// pre-dispatch 400 (`Unknown parameter '<name>'`): no `resolution`, no `seed`,
|
|
1472
|
+
// no `negative_prompt`, no `generate_audio`, no `person_generation` /
|
|
1473
|
+
// `enhance_prompt` / `conditioning_scale`. Resolution is fixed at 720p/24fps
|
|
1474
|
+
// and audio is always on. Widening this list does not widen the model; it just
|
|
1475
|
+
// moves a free validation error into a billed one.
|
|
1476
|
+
//
|
|
1477
|
+
// `duration` is the exception, and the reason it looks odd: Omni has no
|
|
1478
|
+
// duration FIELD, but it honours a length named in the prompt (3-10s), so the
|
|
1479
|
+
// adapter turns this param into a prompt directive. It is a real, billable
|
|
1480
|
+
// knob — cost scales exactly with the seconds rendered.
|
|
1481
|
+
label: "Google Gemini Omni Flash",
|
|
1482
|
+
inputs: [],
|
|
1483
|
+
optional_inputs: [{ kind: "image", mimes: GEMINI_OMNI_IMAGE_MIMES }],
|
|
1484
|
+
required: ["prompt"],
|
|
1485
|
+
params: {
|
|
1486
|
+
prompt: { kind: "string" },
|
|
1487
|
+
aspect_ratio: { kind: "string", enum: GEMINI_OMNI_ASPECT_RATIOS },
|
|
1488
|
+
duration: { kind: "number", enum: GEMINI_OMNI_DURATIONS }
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1410
1491
|
"google/veo-3.1": {
|
|
1411
1492
|
// Photoreal CINE CEILING + the real-face fallback (Veo generates adult
|
|
1412
1493
|
// humans from a keyframe, dodging ByteDance's real-person filter). Same
|
|
@@ -1661,6 +1742,21 @@ function nearestSupportedAspectRatio(kind, model, target) {
|
|
|
1661
1742
|
}
|
|
1662
1743
|
return best;
|
|
1663
1744
|
}
|
|
1745
|
+
function supportedImageSizes(kind, model) {
|
|
1746
|
+
const schema = MODEL_REGISTRY[kind]?.[model]?.params.image_size;
|
|
1747
|
+
return schema?.kind === "string" ? schema.enum : void 0;
|
|
1748
|
+
}
|
|
1749
|
+
var IMAGE_SIZE_ORDER = ["0.5K", "1K", "2K", "4K"];
|
|
1750
|
+
function nearestSupportedImageSize(kind, model, target) {
|
|
1751
|
+
const supported = supportedImageSizes(kind, model);
|
|
1752
|
+
if (!supported || supported.includes(target)) return target;
|
|
1753
|
+
const wanted = IMAGE_SIZE_ORDER.indexOf(target);
|
|
1754
|
+
if (wanted < 0) return target;
|
|
1755
|
+
const ranked = supported.filter((s) => IMAGE_SIZE_ORDER.includes(s)).sort((a, b) => IMAGE_SIZE_ORDER.indexOf(a) - IMAGE_SIZE_ORDER.indexOf(b));
|
|
1756
|
+
if (ranked.length === 0) return target;
|
|
1757
|
+
const belowOrEqual = ranked.filter((s) => IMAGE_SIZE_ORDER.indexOf(s) <= wanted);
|
|
1758
|
+
return belowOrEqual.length > 0 ? belowOrEqual[belowOrEqual.length - 1] : ranked[0];
|
|
1759
|
+
}
|
|
1664
1760
|
|
|
1665
1761
|
// ../canvas-contract/src/validateParams.ts
|
|
1666
1762
|
function isManagedNodeKind(nodeType) {
|
|
@@ -1827,8 +1923,14 @@ var USD_PER_SECOND = {
|
|
|
1827
1923
|
}
|
|
1828
1924
|
}
|
|
1829
1925
|
};
|
|
1926
|
+
var GEMINI_OMNI_USD_PER_1M_VIDEO_TOKENS = 17.5;
|
|
1927
|
+
var GEMINI_OMNI_VIDEO_TOKENS_PER_SECOND = 5792;
|
|
1928
|
+
var GEMINI_OMNI_USD_PER_SECOND = GEMINI_OMNI_VIDEO_TOKENS_PER_SECOND * GEMINI_OMNI_USD_PER_1M_VIDEO_TOKENS / 1e6;
|
|
1830
1929
|
function estimateVideoCostUsd({ model, duration, resolution, generateAudio }) {
|
|
1831
1930
|
const seconds = duration ?? DEFAULT_VIDEO_DURATION_S;
|
|
1931
|
+
if (isGeminiOmniModel(model)) {
|
|
1932
|
+
return (duration ?? GEMINI_OMNI_DEFAULT_SECONDS) * GEMINI_OMNI_USD_PER_SECOND;
|
|
1933
|
+
}
|
|
1832
1934
|
if (isSeedanceModel(model)) return seedanceUsdPerSecond(resolution) * seconds;
|
|
1833
1935
|
const published = USD_PER_SECOND[model];
|
|
1834
1936
|
if (published) {
|
|
@@ -2688,15 +2790,41 @@ var KLING_PROFILE = {
|
|
|
2688
2790
|
durationSet: KLING_DURATIONS2,
|
|
2689
2791
|
paramDefaults: { negative_prompt: KLING_NEGATIVE_PROMPT, cfg_scale: KLING_CFG_SCALE }
|
|
2690
2792
|
};
|
|
2793
|
+
var OMNI_PROFILE = {
|
|
2794
|
+
id: "omni",
|
|
2795
|
+
dialogueDirective: SPEAKS_PROSE,
|
|
2796
|
+
// Omni renders speech and ambience natively in the same pass, so the prompt
|
|
2797
|
+
// carries the audio direction; there is no separate audio toggle to set.
|
|
2798
|
+
extraDirectives: ["No subtitles, no captions, no on-screen text of any kind."],
|
|
2799
|
+
// Omni exposes NO negative_prompt param — the API rejects the field outright —
|
|
2800
|
+
// so the anti-artifact intent has to be carried positively, Seedance-style,
|
|
2801
|
+
// rather than as an "Avoid:" list Veo would have taken as a param.
|
|
2802
|
+
stabilityDirectives: [
|
|
2803
|
+
"hands and fingers crisp and anatomically correct",
|
|
2804
|
+
"one consistent face and identity across every frame",
|
|
2805
|
+
"smooth, temporally coherent motion that holds steady frame to frame"
|
|
2806
|
+
],
|
|
2807
|
+
keyframeInstruction: "Preserve the composition and colors of the first frame; change only the motion described.",
|
|
2808
|
+
// Omni reasons over a 1M-token context and rewards a fuller brief than the
|
|
2809
|
+
// diffusion-style models, which dilute past their sweet spot.
|
|
2810
|
+
wordBudget: 200,
|
|
2811
|
+
// 3-10s. Omni has no duration FIELD, but honours a length asked for in the
|
|
2812
|
+
// prompt, so the scaffolder plans real clip lengths here like any other model.
|
|
2813
|
+
durationSet: GEMINI_OMNI_DURATIONS,
|
|
2814
|
+
// Nothing to set. Every knob the other profiles use (negative_prompt,
|
|
2815
|
+
// person_generation, cfg_scale) is a pre-dispatch 400 on this model.
|
|
2816
|
+
paramDefaults: {}
|
|
2817
|
+
};
|
|
2691
2818
|
function clipProfileFor(modelId) {
|
|
2692
2819
|
if (/^bytedance\/seedance/.test(modelId)) return SEEDANCE_PROFILE;
|
|
2820
|
+
if (/^google\/gemini-omni/.test(modelId)) return OMNI_PROFILE;
|
|
2693
2821
|
if (/^google\/veo/.test(modelId)) return VEO_PROFILE;
|
|
2694
2822
|
if (/^kwaivgi\/kling|^kling\//.test(modelId)) return KLING_PROFILE;
|
|
2695
2823
|
return void 0;
|
|
2696
2824
|
}
|
|
2697
2825
|
function clipParamRecipe(profile, intent) {
|
|
2698
2826
|
const out = {};
|
|
2699
|
-
if (intent === "hero") {
|
|
2827
|
+
if (intent === "hero" && profile.id !== "omni") {
|
|
2700
2828
|
out.resolution = "1080p";
|
|
2701
2829
|
}
|
|
2702
2830
|
if (intent === "hook" && profile.id === "kling") {
|
|
@@ -2721,7 +2849,12 @@ var GEMINI_IMAGE_PROFILE = {
|
|
|
2721
2849
|
id: "gemini",
|
|
2722
2850
|
constraintPlacement: "inline",
|
|
2723
2851
|
photorealCue: true,
|
|
2724
|
-
|
|
2852
|
+
// NO `quality`. OpenRouter accepts the field on a Gemini image call and returns
|
|
2853
|
+
// 200 — but its per-endpoint schema does not list it and the cost is identical
|
|
2854
|
+
// with and without, so Google drops it: an inert knob. Worse, the registry
|
|
2855
|
+
// (correctly) does not declare it for Gemini, so emitting it fails `baker canvas
|
|
2856
|
+
// validate` with `unknown_param` before the call is ever made.
|
|
2857
|
+
paramDefaults: {}
|
|
2725
2858
|
};
|
|
2726
2859
|
var RECRAFT_IMAGE_PROFILE = {
|
|
2727
2860
|
id: "recraft",
|
|
@@ -2855,6 +2988,7 @@ var STAGE_CODES = {
|
|
|
2855
2988
|
BRANDMARK_IN_PROMPT: "VIDEO_BRANDMARK_IN_PROMPT",
|
|
2856
2989
|
SPEECH_EXCEEDS_EXTRACT: "VIDEO_SPEECH_EXCEEDS_EXTRACT",
|
|
2857
2990
|
PROMPT_PROFILE_MISSING: "VIDEO_PROMPT_PROFILE_MISSING",
|
|
2991
|
+
MODEL_IGNORES_INPUT: "VIDEO_MODEL_IGNORES_INPUT",
|
|
2858
2992
|
PROMPT_DECISION_MISSING: "VIDEO_PROMPT_DECISION_MISSING",
|
|
2859
2993
|
HOOK_LAYER_MISSING: "VIDEO_HOOK_LAYER_MISSING",
|
|
2860
2994
|
IMAGE_PROFILE_MISSING: "IMAGE_PROMPT_PROFILE_MISSING",
|
|
@@ -3281,9 +3415,14 @@ function talkingSceneSatisfied(ctx, entry, scene) {
|
|
|
3281
3415
|
const nodes = ctx.canvas.nodes;
|
|
3282
3416
|
if (typeof entry === "object" && "voice_convert_node" in entry) {
|
|
3283
3417
|
const nativeClipRe = new RegExp(`^s${scene}(_r\\d+)?_clip$`);
|
|
3284
|
-
const clipNativeAudio = nodes.some(
|
|
3285
|
-
(
|
|
3286
|
-
|
|
3418
|
+
const clipNativeAudio = nodes.some((n) => {
|
|
3419
|
+
if (!nativeClipRe.test(n.id) || n.type !== "video_generate") return false;
|
|
3420
|
+
const params = n.params;
|
|
3421
|
+
if (typeof params?.model === "string" && !supportsParam("video_generate", params.model, "generate_audio")) {
|
|
3422
|
+
return true;
|
|
3423
|
+
}
|
|
3424
|
+
return params?.generate_audio === true;
|
|
3425
|
+
});
|
|
3287
3426
|
const converted = nodes.some((n) => n.id === entry.voice_convert_node && n.type === "audio_voice_convert");
|
|
3288
3427
|
return clipNativeAudio && (converted || nativeAudioReachesMix(ctx, scene));
|
|
3289
3428
|
}
|
|
@@ -3344,6 +3483,7 @@ function checkVideoInvariants(ctx) {
|
|
|
3344
3483
|
checkReferenceCompleteness(ctx, meta);
|
|
3345
3484
|
checkClipSpanFitsModel(ctx, meta);
|
|
3346
3485
|
checkTimelineContract(ctx, meta);
|
|
3486
|
+
checkUnusableLastFrame(ctx);
|
|
3347
3487
|
checkNativeSegOverlap(ctx);
|
|
3348
3488
|
checkSpineNormalized(ctx, meta);
|
|
3349
3489
|
checkOddDimensions(ctx);
|
|
@@ -3708,6 +3848,23 @@ function videoModelMaxDuration(model) {
|
|
|
3708
3848
|
const nums = durations.filter((d) => typeof d === "number");
|
|
3709
3849
|
return nums.length > 0 ? Math.max(...nums) : void 0;
|
|
3710
3850
|
}
|
|
3851
|
+
function checkUnusableLastFrame(ctx) {
|
|
3852
|
+
for (const n of ctx.canvas.nodes) {
|
|
3853
|
+
if (n.type !== "video_generate") continue;
|
|
3854
|
+
const model = n.params?.model;
|
|
3855
|
+
if (typeof model !== "string" || supportsLastFrame(model)) continue;
|
|
3856
|
+
const inputs = n.inputs;
|
|
3857
|
+
if (!inputs?.last_frame) continue;
|
|
3858
|
+
ctx.issues.push({
|
|
3859
|
+
path: `nodes[${ctx.idToIndex.get(n.id) ?? -1}].inputs.last_frame`,
|
|
3860
|
+
code: STAGE_CODES.MODEL_IGNORES_INPUT,
|
|
3861
|
+
severity: "warning",
|
|
3862
|
+
node_id: n.id,
|
|
3863
|
+
node_type: "video_generate",
|
|
3864
|
+
message: `"${n.id}" wires a last_frame but runs on ${model}, which takes only ONE conditioning image \u2014 the end keyframe is dropped and the image generated for it is wasted. Drop the last_frame, or move this clip to Veo/Seedance, which honour both frames`
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3711
3868
|
function checkClipSpanFitsModel(ctx, meta) {
|
|
3712
3869
|
for (const { node, span_s } of meta.clip_spans ?? []) {
|
|
3713
3870
|
const target = ctx.canvas.nodes.find((n) => n.id === node && n.type === "video_generate");
|
|
@@ -7096,16 +7253,9 @@ var dialogueNode = delegated({
|
|
|
7096
7253
|
|
|
7097
7254
|
// src/engine/nodes/remote/image.ts
|
|
7098
7255
|
import { z as z17 } from "zod";
|
|
7099
|
-
var
|
|
7100
|
-
"openai/gpt-image-2",
|
|
7101
|
-
// Legacy — see the registry entry; kept so pre-switch canvases still run.
|
|
7102
|
-
"openai/gpt-5.4-image-2",
|
|
7103
|
-
"google/gemini-3.1-flash-image-preview",
|
|
7104
|
-
"google/gemini-3-pro-image-preview",
|
|
7105
|
-
"recraft/recraft-v4.1-pro-vector"
|
|
7106
|
-
];
|
|
7256
|
+
var imageModelEnum = z17.enum(IMAGE_GENERATE_MODELS);
|
|
7107
7257
|
var ImageGenerateParams = z17.object({
|
|
7108
|
-
model:
|
|
7258
|
+
model: imageModelEnum,
|
|
7109
7259
|
prompt: z17.string().min(1),
|
|
7110
7260
|
aspect_ratio: z17.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9", "1:4", "4:1", "1:8", "8:1"]).optional(),
|
|
7111
7261
|
image_size: z17.enum(["0.5K", "1K", "2K", "4K"]).optional(),
|
|
@@ -7514,8 +7664,8 @@ var videoGenerateNode = delegated({
|
|
|
7514
7664
|
id: "video_generate",
|
|
7515
7665
|
version: "2.0.0",
|
|
7516
7666
|
category: "video",
|
|
7517
|
-
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.",
|
|
7518
|
-
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.",
|
|
7667
|
+
summary: "Generate video for ad creatives. Curated roster: `google/gemini-omni-flash` (DEFAULT \u2014 native audio + reference images in one call, 720p, 3-10s), `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.",
|
|
7668
|
+
when_to_use: "`google/gemini-omni-flash` is the DEFAULT: it renders picture and synchronized audio in one call, and is the only model here that takes reference images alongside a prompt. SET `duration` (3-10s) to the length the beat actually needs \u2014 it bills ~$0.101 PER SECOND, so leaving it out renders the 10s default and charges ~$1.01 for a beat that may need 3s (~$0.30). Its resolution is fixed at 720p and it accepts only `16:9` or `9:16`; it takes NO `resolution`, `seed`, `negative_prompt` or `generate_audio` param \u2014 sending one fails validation. Keep the default for ordinary beats; move off it when a beat needs 1080p, a length outside 3-10s, or a 1:1 frame. 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.",
|
|
7519
7669
|
inputs: z28.object({
|
|
7520
7670
|
first_frame: ImageRef.optional(),
|
|
7521
7671
|
last_frame: ImageRef.optional(),
|
|
@@ -7999,9 +8149,11 @@ export {
|
|
|
7999
8149
|
ELEVENLABS_MAX_MUSIC_LENGTH_MS,
|
|
8000
8150
|
IMAGE_GENERATE_MODELS,
|
|
8001
8151
|
DEFAULT_VIDEO_GENERATE_MODEL,
|
|
8152
|
+
supportsLastFrame,
|
|
8002
8153
|
MODEL_REGISTRY,
|
|
8003
8154
|
supportsParam,
|
|
8004
8155
|
nearestSupportedAspectRatio,
|
|
8156
|
+
nearestSupportedImageSize,
|
|
8005
8157
|
resolveConcurrency,
|
|
8006
8158
|
ulid,
|
|
8007
8159
|
isPersistedAssetRef,
|
|
@@ -8028,4 +8180,4 @@ export {
|
|
|
8028
8180
|
defaultRegistry,
|
|
8029
8181
|
createEngineFromEnv
|
|
8030
8182
|
};
|
|
8031
|
-
//# sourceMappingURL=chunk-
|
|
8183
|
+
//# sourceMappingURL=chunk-UZ37VVP4.js.map
|