@koda-sl/baker-cli 0.181.1 → 0.184.0-dev.0155730c3

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
@@ -1753,7 +1753,6 @@ Each external source is its own subcommand. Pick the verb that matches the sourc
1753
1753
  | `baker images stock <q> [--type photo\|vector\|psd]` | Magnific (Freepik's dev API) — photos, vectors, illustrations, PSDs (~250M assets) | off |
1754
1754
  | `baker images google <q>` | Google Images via the official Custom Search JSON API | off |
1755
1755
  | `baker images pinterest <q>` | Pinterest reference imagery via ScrapeCreators (photo-real mood boards) | off |
1756
- | `baker images generate <prompt>` | AI image generation via OpenRouter image models (cost-tracked) | **always on** (the bytes are the artifact) |
1757
1756
  | `baker images logo <domain>` | Brand logo via Brandfetch CDN | **on** (top 1) |
1758
1757
  | `baker images icon <name>` | Iconify (200+ icon sets, no API key) | off (CDN URL is stable) |
1759
1758
  | `baker images gif <q>` | Reaction GIFs / memes via Giphy (paid-social creative) | off |
@@ -1854,7 +1853,7 @@ baker images stock "office reaction" --people only
1854
1853
 
1855
1854
  Free tier exists but watermarks previews — pass `--license freemium` to filter to clean free assets explicitly.
1856
1855
 
1857
- An empty result comes back with `hints` naming the filters that narrowed the search plus the fallbacks (`baker images find --sources …`, `baker images generate`); a provider failure returns an `error.fix` pointing at the same exits. `baker images google` does the same, minus the cross-provider fallback (it is the last-resort provider).
1856
+ An empty result comes back with `hints` naming the filters that narrowed the search plus the fallbacks (`baker images find --sources …`, `baker studio generate`); a provider failure returns an `error.fix` pointing at the same exits. `baker images google` does the same, minus the cross-provider fallback (it is the last-resort provider).
1858
1857
 
1859
1858
  **Flags:**
1860
1859
 
@@ -1916,53 +1915,9 @@ baker images pinterest "cozy coffee shop interior" --auto-ingest 2 --context "Mo
1916
1915
 
1917
1916
  Endpoint `api.scrapecreators.com/v1/pinterest/search` (`SCRAPE_CREATORS_API_KEY`), $0.00188/request, 1-day cache.
1918
1917
 
1919
- ### `baker images generate <prompt>`
1918
+ ### AI image generation
1920
1919
 
1921
- Generate an image with AI (OpenRouter image models) and ingest it into the library. The generated bytes **are** the artifact, so the result is always auto-ingested (described + embedded) — the next `baker images library` query finds it. Cost is tracked per request via OpenRouter's reported usage. Models and defaults mirror the Baker canvas.
1922
-
1923
- ```bash
1924
- baker images generate "a friendly golden retriever in a bright modern living room" --aspect-ratio 16:9
1925
- baker images generate "hero shot of a matte black water bottle on wet marble, studio light" \
1926
- --model google/gemini-3-pro-image-preview --image-size 2K --aspect-ratio 4:5
1927
- baker images generate "home office hero, warm minimalist, natural light, 35mm photo" \
1928
- --reference "https://i.pinimg.com/…/pin1.jpg,https://…/brand-product.png"
1929
- baker images generate "put this product on a marble countertop in soft daylight" \
1930
- --reference "./src/brand/logos/product.png,./refs/kitchen-mood.jpg" # local sandbox files
1931
- baker images generate "flat geometric mascot, brand palette" \
1932
- --model recraft/recraft-v4.1-pro-vector --rgb-colors "[[10,10,10],[255,80,0]]" --bg-rgb "[255,255,255]"
1933
- ```
1934
-
1935
- **Models** (`--model`, default `google/gemini-3.1-flash-image-preview`):
1936
-
1937
- | Model | Best for | Aspect ratios | Sizes |
1938
- |---|---|---|---|
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** |
1941
- | `google/gemini-3-pro-image-preview` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
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)* |
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) |
1944
- | `recraft/recraft-v4.1-pro-vector` | Vector/flat/SVG-style with palette control | standard set | `1K` `2K` `4K` |
1945
-
1946
- Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9` `21:9`.
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.
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
-
1952
- **Flags:**
1953
-
1954
- | Flag | Description |
1955
- |---|---|
1956
- | `--model` | Model id (default `google/gemini-3.1-flash-image-preview`) |
1957
- | `--aspect-ratio` | Output aspect ratio (default `1:1`) |
1958
- | `--image-size` | Resolution: `1K` (default) `2K` `4K` (Gemini flash also `0.5K`; Gemini flash-lite is `1K` only) |
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. |
1960
- | `--strength` | Recraft only: vectorization strength 0–1 |
1961
- | `--rgb-colors` | Recraft only: JSON palette `[[r,g,b],…]` |
1962
- | `--bg-rgb` | Recraft only: JSON background `[r,g,b]` |
1963
- | `--context` | Describe-hint override for the ingested row (defaults to the prompt) |
1964
-
1965
- Returns `{ images: [{ imageId, imageUrl, deduped, width, height }], model, costUsd }`. `imageUrl` is library-owned and ready to place. Requires `OPENROUTER_API_KEY` on the Convex deployment. No `seed` (OpenRouter image_config has no seed slot). Identical re-generations dedup by content hash.
1920
+ Its own family see [`baker studio`](#baker-studio--make-new-imagery) below. `baker images` sources what already exists; `baker studio` makes what doesn't.
1966
1921
 
1967
1922
  ### `baker images logo <domain>`
1968
1923
 
@@ -2920,6 +2875,142 @@ baker schema images.search # Get image search schema
2920
2875
 
2921
2876
  Not every command registers a schema. On `NOT_FOUND` the error carries a structured `fix` pointing at `--help` and the family's agent doc (`__tooling__/docs/tools/baker/<family>.md` in sandboxes). Every top-level family's `--help` also ends with a `Full guide:` line pointing at that doc; `packages/cli/__tests__/doc-coverage.test.ts` fails CI if a mounted command is missing from its family doc.
2922
2877
 
2878
+ ## `baker studio` — make new imagery
2879
+
2880
+ The generation lane. `baker images` **sources** what already exists; `baker studio` **makes** what doesn't. Every call opens a *batch* — one brief, 1-8 takes, rendered in parallel — recorded against the calling chat and shown in the dashboard Studio next to the batches the client ran themselves.
2881
+
2882
+ | Command | Purpose |
2883
+ |---|---|
2884
+ | `baker studio generate <prompt>` | Render 1-8 takes of one still. Each take is ingested into the media library as it lands |
2885
+ | `baker studio animate <prompt> --image <…>` | Turn a still into a clip. The prompt describes movement, not the look |
2886
+ | `baker studio get <generationId>` | Read one batch: takes, urls, library state, per-take failure reason |
2887
+ | `baker studio list` | Recent batches for the calling chat (`--all` widens to the company) |
2888
+ | `baker studio keep <generationId> --slot N` | Mark the keeper. Stars an image; **ingests** a clip into the video library |
2889
+ | `baker studio models` | Per model: shapes, resolutions, clip lengths, prompt cap, reference limit, every knob. `--kind image\|video`, `--model <id>` |
2890
+ | `baker studio skills` | Craft directions accepted by `generate --skill <id>` |
2891
+ | `baker studio improve <prompt>` | Sharpen a rough brief — the same rewrite the client's prompt-bar wand performs. Returns the rewrite and the original; generates nothing |
2892
+
2893
+ **Every per-model limit is enforced before anything is billed.** Prompt character caps (Kling 2,500 · Seedance 4,000 · Recraft 10,000 · gpt-image 32,000), reference-image counts (Recraft 1 · Gemini 14 · gpt-image 16 · Seedance 9 in reference mode), the uint32 seed range, RGB channel bounds, and `width:height` shapes are all checked against the chosen model up front — the request is refused with the number to fix rather than opening a batch that fails a take at a time. A knob the model does not have is refused too; only the *shape* snaps to the nearest it renders. `baker studio models` reads all of this from the same contract table the backend validates against, so it cannot drift.
2894
+
2895
+ Two of those checks run in the CLI itself, before the request is even sent. The **reference-image count** is one: `--reference` / `--image` accept file paths and URLs, which are uploaded to the media library first, so checking the count only at the backend means paying to ingest and describe every image in a set that was always going to be refused. The **first-frame rule** is the other — a clip started from one exact frame takes exactly one image whatever the model's reference slot allows, and reference mode is refused outright on Veo and Kling, which cannot read references at all.
2896
+
2897
+ **References must be JPG, PNG or WebP.** The models read nothing else, and the library legitimately holds SVGs because Recraft's vector model writes them — so "render the logo I just made, on a kitchen counter" is a shape the trap fits exactly. It is refused on the way in, naming the format.
2898
+
2899
+ Starting a batch returns 202 + a `generationId`; the CLI polls until the takes settle. `--wait false` / `--max-wait 0` returns the id immediately, and a wait that runs out is **not** a failure — the id comes back with `status: running` and a hint to collect it with `baker studio get`.
2900
+
2901
+ ### `baker studio generate <prompt>`
2902
+
2903
+ ```bash
2904
+ baker studio generate "a friendly golden retriever in a bright modern living room" --aspect-ratio 16:9
2905
+ baker studio generate "hero shot of a matte black water bottle on wet marble, studio light" \
2906
+ --model google/gemini-3-pro-image-preview --image-size 2K --aspect-ratio 4:5 --count 3
2907
+ baker studio generate "home office hero, warm minimalist, natural light, 35mm photo" \
2908
+ --reference "https://i.pinimg.com/…/pin1.jpg,./src/brand/logos/product.png,j57abc123def456ghi789"
2909
+ baker studio generate "founder-style selfie, kitchen background" --skill ugc-selfie-hook
2910
+ baker studio generate "flat geometric mascot, brand palette" \
2911
+ --model recraft/recraft-v4.1-pro-vector --rgb-colors "[[10,10,10],[255,80,0]]" --bg-rgb "[255,255,255]"
2912
+ ```
2913
+
2914
+ **Models** (`--model`, default `google/gemini-3.1-flash-image-preview`):
2915
+
2916
+ | Model | Best for | Aspect ratios | Sizes |
2917
+ |---|---|---|---|
2918
+ | `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` |
2919
+ | `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** |
2920
+ | `google/gemini-3-pro-image-preview` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
2921
+ | `openai/gpt-image-2` | Photoreal + cleanest in-image text — ad/landing reproduction | standard set **minus** `4:5` `5:4` | *(none — derived from the ratio)* |
2922
+ | `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) |
2923
+ | `recraft/recraft-v4.1-pro-vector` | Vector/flat/SVG-style with palette control. Writes **SVG**, so its output cannot be fed back as a reference | `1:1` `4:3` `3:4` `16:9` `9:16` **only** | *(none — refuses `image_size`)* |
2924
+
2925
+ Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9` `21:9`.
2926
+
2927
+ `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.
2928
+
2929
+ `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.
2930
+
2931
+ `recraft/recraft-v4.1-pro-vector` is the narrowest of the roster in three ways, all read off its live endpoint schema: it renders **five** ratios (no `4:5` / `5:4` / `2:3` / `3:2` / `21:9`, so a portrait ad shape snaps to `3:4`), it has **no `image_size`** — the provider accepts one and ignores it, which is why it is refused here — and it takes **exactly one** reference image against 14-16 elsewhere. Its output is an SVG, and the image models read only JPG, PNG and WebP, so a Recraft take cannot be used as a reference for the next render.
2932
+
2933
+ A knob the chosen model does not advertise is **refused, not silently dropped** — the error names the model and what it accepts, and carries an `error.fix` telling the agent to drop the flag rather than abandon the job.
2934
+
2935
+ **Flags:**
2936
+
2937
+ | Flag | Description |
2938
+ |---|---|
2939
+ | `--model` | Model id (default `google/gemini-3.1-flash-image-preview`) |
2940
+ | `--aspect-ratio` | Output aspect ratio (default `1:1`) |
2941
+ | `--count` | Takes to render, 1–8 (default 1) |
2942
+ | `--reference` | Comma-separated visual references, in order: **library image ids**, **local file paths**, and/or **image URLs**. Paths and URLs are added to the media library first and passed as ids — the studio takes library ids only, so the client can see what grounded the render and the reference stays reusable. The biggest quality lever there is. |
2943
+ | `--skill` | Craft direction id (`baker studio skills`) — art direction plus the model/shape/count it wants |
2944
+ | `--brand` | Append the company palette and visual style. **Off by default** for the agent (the dashboard defaults it on) |
2945
+ | `--image-size` | Resolution: `0.5K` (Gemini flash only) `1K` `2K` `4K`; Gemini flash-lite is `1K` only. **No default** — left unset the provider picks the pixels from the ratio. `gpt-image-2` has no resolution setting and **refuses** this flag |
2946
+ | `--quality` | `low` \| `medium` \| `high` \| `auto`. Only on models that honour it — OpenRouter lists it for gpt-image alone, so every Gemini model and Recraft **refuse** it |
2947
+ | `--strength` | Recraft only: vectorization strength 0–1 |
2948
+ | `--rgb-colors` | Recraft only: JSON palette `[[r,g,b],…]` |
2949
+ | `--bg-rgb` | Recraft only: JSON background `[r,g,b]` |
2950
+ | `--wait` / `--max-wait` | Wait for the takes (default on, 240000 ms). `--wait false` / `--max-wait 0` returns the batch id |
2951
+ | `--full` | Include settings, references, chat attribution, `createdAt` |
2952
+
2953
+ Requires `OPENROUTER_API_KEY` on the Convex deployment. Identical re-generations dedup by content hash.
2954
+
2955
+ ### `baker studio improve <prompt>`
2956
+
2957
+ Sharpen a rough brief into directed art direction, using the same model and prompt as the wand in the dashboard's Studio bar.
2958
+
2959
+ ```
2960
+ baker studio improve "make it look premium"
2961
+ baker studio improve "zoom in a bit" --kind video --has-reference
2962
+ ```
2963
+
2964
+ | Flag | Notes |
2965
+ | --- | --- |
2966
+ | `--kind` | `image` (default) sharpens a still brief; `video` sharpens a **motion** brief |
2967
+ | `--skill` | Sharpen it for one craft direction (`baker studio skills`) |
2968
+ | `--has-reference` | Say a reference image is wired, so the rewrite describes the **change** rather than the whole scene |
2969
+
2970
+ Both `prompt` (the rewrite) and `original` come back, so you can show the client what changed instead of silently replacing their words. Nothing is generated — pass the result to `baker studio generate` yourself.
2971
+
2972
+ Reach for it when you are relaying the **client's** own words. When you are writing the art direction yourself, just write it: you will do a better job than a flash model rewriting you.
2973
+
2974
+ ### `baker studio animate <prompt>`
2975
+
2976
+ ```bash
2977
+ baker studio animate "slow push in, model turns to camera and smiles" --image j57abc123def456ghi789
2978
+ baker studio animate "handheld drift right, steam rising from the cup" \
2979
+ --image ./out/hero.png --duration 6 --quality 1080p
2980
+ baker studio animate "product rotates once on a turntable" --image j57abc…,j57def… --from references
2981
+ ```
2982
+
2983
+ The image fixes the look, so the prompt describes **movement**. Models: `bytedance/seedance-2.0` (default), `google/veo-3.1`, `google/veo-3.1-fast`, `kwaivgi/kling-v3.0-pro`.
2984
+
2985
+ | Flag | Description |
2986
+ |---|---|
2987
+ | `--image` | **Required.** Comma-separated library image ids / local paths / image URLs; the first is the opening frame |
2988
+ | `--from` | `frame` (default — animate the first image) \| `references` (use the images as visual reference). Mutually exclusive at every provider |
2989
+ | `--model` | Video model id (default `bytedance/seedance-2.0`) |
2990
+ | `--aspect-ratio` | Default `9:16` |
2991
+ | `--duration` | Seconds; per-model set (default 5, snapped onto what the model renders) |
2992
+ | `--quality` | Resolution, e.g. `720p` (default) \| `1080p` |
2993
+ | `--count` | Takes, 1–3 (default 1). A clip costs ~45x an image |
2994
+ | `--sound` | Let the model generate audio. Off by default (billed on top) |
2995
+ | `--negative` / `--seed` / `--people` / `--enhance-prompt` / `--conditioning-scale` / `--cfg-scale` | Per-model knobs; refused when the chosen model doesn't advertise them |
2996
+ | `--wait` / `--max-wait` | Wait for the takes (default on, 600000 ms) |
2997
+ | `--full` | Include settings, references, chat attribution, `createdAt` |
2998
+
2999
+ **A rendered clip is not usable anywhere until it is kept** — `keep` is what ingests it into the video library. Unkept takes are never ingested, which is what makes a rejected batch cheap.
3000
+
3001
+ ### `baker studio get` / `list` / `keep` / `skills`
3002
+
3003
+ ```bash
3004
+ baker studio get j57abc123def456ghi789 --full
3005
+ baker studio list --kind video --limit 5 # this chat's batches
3006
+ baker studio list --all # every batch for the company
3007
+ baker studio keep j57abc123def456ghi789 --slot 2
3008
+ baker studio keep j57abc123def456ghi789 --slot 2 --undo
3009
+ baker studio skills
3010
+ ```
3011
+
3012
+ Compact output per take is `{ slot, status, imageId | videoId, url, inLibrary, error }` alongside the batch's prompt, model, aspect ratio and status; `--full` adds the settings echo, the reference ids, the chat id and `createdAt`. `keep` stars an image (it was already in the library) and **ingests** a clip; `--undo` reverses either.
3013
+
2923
3014
  ## Creative Canvas
2924
3015
 
2925
3016
  The creative canvas runs declarative JSON pipelines. You author a graph of nodes (text, image, video, audio, composition, control-flow), `baker canvas run` it, and every node's output drops to disk under `canvas/<run_id>/` so you can watch progress and consume the artifacts.
@@ -7,9 +7,9 @@ import {
7
7
  __toESM
8
8
  } from "./chunk-YL3HDEIJ.js";
9
9
 
10
- // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
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
- "../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
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
- // ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
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;
@@ -1120,11 +1120,22 @@ function normalizeForCanonical(value) {
1120
1120
  var OPENROUTER_IMAGE_AR = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
1121
1121
  var OPENROUTER_IMAGE_AR_EXTREME = [...OPENROUTER_IMAGE_AR, "1:4", "4:1", "1:8", "8:1"];
1122
1122
  var OPENROUTER_IMAGE_AR_GPT = ["1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9", "21:9"];
1123
+ var GPT_IMAGE_PROMPT_MAX = 32e3;
1124
+ var RECRAFT_PROMPT_MAX = 1e4;
1125
+ var SEEDANCE_PROMPT_MAX = 4e3;
1126
+ var KLING_PROMPT_MAX = 2500;
1127
+ var VEO_SEED_MAX = 4294967295;
1128
+ var GPT_IMAGE_MAX_REFERENCES = 16;
1129
+ var GEMINI_IMAGE_MAX_REFERENCES = 14;
1130
+ var RECRAFT_MAX_REFERENCES = 1;
1131
+ var SEEDANCE_MAX_REFERENCES = 9;
1123
1132
  var OPENROUTER_IMAGE_SIZE = ["1K", "2K", "4K"];
1124
- var OPENROUTER_IMAGE_SIZE_EXTENDED = ["0.5K", ...OPENROUTER_IMAGE_SIZE];
1133
+ var OPENROUTER_IMAGE_SIZES = ["0.5K", ...OPENROUTER_IMAGE_SIZE];
1134
+ var OPENROUTER_IMAGE_SIZE_EXTENDED = OPENROUTER_IMAGE_SIZES;
1125
1135
  var GEMINI_LITE_IMAGE_SIZE = ["1K"];
1126
1136
  var OPENROUTER_IMAGE_QUALITY = ["auto", "low", "medium", "high"];
1127
- var SEEDANCE_DURATIONS = [4, 5, 6, 8, 10, 12, 15];
1137
+ var RECRAFT_IMAGE_AR = ["1:1", "4:3", "3:4", "16:9", "9:16"];
1138
+ var SEEDANCE_DURATIONS = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
1128
1139
  var KLING_DURATIONS = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
1129
1140
  var GEMINI_OMNI_DURATIONS = [3, 4, 5, 6, 7, 8, 9, 10];
1130
1141
  var GEMINI_OMNI_DEFAULT_SECONDS = 10;
@@ -1153,6 +1164,7 @@ var IMAGE_GENERATE_MODELS = [
1153
1164
  "google/gemini-3-pro-image-preview",
1154
1165
  "recraft/recraft-v4.1-pro-vector"
1155
1166
  ];
1167
+ var DEFAULT_IMAGE_GENERATE_MODEL = "google/gemini-3.1-flash-image-preview";
1156
1168
  var VIDEO_GENERATE_MODELS = [
1157
1169
  "bytedance/seedance-2.0",
1158
1170
  "google/gemini-omni-flash",
@@ -1161,6 +1173,10 @@ var VIDEO_GENERATE_MODELS = [
1161
1173
  "kwaivgi/kling-v3.0-pro"
1162
1174
  ];
1163
1175
  var DEFAULT_VIDEO_GENERATE_MODEL = "google/gemini-omni-flash";
1176
+ var REFERENCE_TO_VIDEO_MODELS = ["bytedance/seedance-2.0", "google/gemini-omni-flash"];
1177
+ function supportsReferenceToVideo(model) {
1178
+ return REFERENCE_TO_VIDEO_MODELS.includes(model);
1179
+ }
1164
1180
  function isGeminiOmniModel(model) {
1165
1181
  return model.startsWith("google/gemini-omni");
1166
1182
  }
@@ -1265,12 +1281,18 @@ var MODEL_REGISTRY = {
1265
1281
  // no `image_size` (OpenAI picks the pixel dimensions from the ratio, and a
1266
1282
  // `resolution` is silently ignored) and the narrower `..._AR_GPT` ratio set.
1267
1283
  "openai/gpt-image-2": {
1284
+ // Its live endpoint also advertises `background` (auto|opaque) and
1285
+ // `output_compression` (0-100), which we deliberately do not declare. Not an
1286
+ // oversight: a registry param is a promise the whole system keeps — the
1287
+ // canvas node schema, the dashboard control, the CLI flag and the cost model
1288
+ // all have to carry it — and neither knob changes what the picture IS.
1289
+ // Declaring them would widen four surfaces to expose a file-size dial.
1268
1290
  label: "OpenAI GPT Image 2",
1269
1291
  inputs: [],
1270
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1292
+ optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GPT_IMAGE_MAX_REFERENCES }],
1271
1293
  required: ["prompt"],
1272
1294
  params: {
1273
- prompt: { kind: "string" },
1295
+ prompt: { kind: "string", maxLength: GPT_IMAGE_PROMPT_MAX },
1274
1296
  aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR_GPT },
1275
1297
  quality: { kind: "string", enum: OPENROUTER_IMAGE_QUALITY }
1276
1298
  }
@@ -1285,10 +1307,10 @@ var MODEL_REGISTRY = {
1285
1307
  "openai/gpt-5.4-image-2": {
1286
1308
  label: "OpenAI GPT-5.4 Image 2 (legacy)",
1287
1309
  inputs: [],
1288
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1310
+ optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GPT_IMAGE_MAX_REFERENCES }],
1289
1311
  required: ["prompt"],
1290
1312
  params: {
1291
- prompt: { kind: "string" },
1313
+ prompt: { kind: "string", maxLength: GPT_IMAGE_PROMPT_MAX },
1292
1314
  aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR },
1293
1315
  image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE },
1294
1316
  quality: { kind: "string", enum: OPENROUTER_IMAGE_QUALITY }
@@ -1297,7 +1319,7 @@ var MODEL_REGISTRY = {
1297
1319
  "google/gemini-3.1-flash-image-preview": {
1298
1320
  label: "Google Gemini 3.1 Flash Image (Preview)",
1299
1321
  inputs: [],
1300
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1322
+ optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GEMINI_IMAGE_MAX_REFERENCES }],
1301
1323
  required: ["prompt"],
1302
1324
  params: {
1303
1325
  prompt: { kind: "string" },
@@ -1322,7 +1344,7 @@ var MODEL_REGISTRY = {
1322
1344
  // Source: GET /api/v1/images/models/google/gemini-3.1-flash-lite-image/endpoints.
1323
1345
  label: "Google Gemini 3.1 Flash Lite Image",
1324
1346
  inputs: [],
1325
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1347
+ optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GEMINI_IMAGE_MAX_REFERENCES }],
1326
1348
  required: ["prompt"],
1327
1349
  params: {
1328
1350
  prompt: { kind: "string" },
@@ -1333,7 +1355,7 @@ var MODEL_REGISTRY = {
1333
1355
  "google/gemini-3-pro-image-preview": {
1334
1356
  label: "Google Gemini 3 Pro Image (Nano Banana Pro)",
1335
1357
  inputs: [],
1336
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1358
+ optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GEMINI_IMAGE_MAX_REFERENCES }],
1337
1359
  required: ["prompt"],
1338
1360
  params: {
1339
1361
  prompt: { kind: "string" },
@@ -1345,14 +1367,24 @@ var MODEL_REGISTRY = {
1345
1367
  }
1346
1368
  },
1347
1369
  "recraft/recraft-v4.1-pro-vector": {
1370
+ // The vector/logo tier, and the odd one out of this roster in three ways.
1371
+ // All three are read off its live endpoint schema, and all three are the
1372
+ // kind of fact that only shows up as a billed 400 if it is guessed:
1373
+ // - it renders FIVE ratios (no 4:5 / 5:4 / 2:3 / 3:2 / 21:9), so portrait
1374
+ // ad shapes snap to 3:4 rather than being sent as-is;
1375
+ // - it has NO resolution knob (`supported_parameters` lists none), so
1376
+ // `image_size` is refused here instead of accepted and ignored;
1377
+ // - it takes exactly ONE reference image, against 14-16 everywhere else.
1378
+ // Its palette levers are real but arrive as provider passthrough
1379
+ // (`controls`), which is why they are `json` rather than typed enums; the
1380
+ // backend adapter builds that block in `buildImageGenBody`.
1348
1381
  label: "Recraft v4.1 Pro Vector",
1349
1382
  inputs: [],
1350
- optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
1383
+ optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: RECRAFT_MAX_REFERENCES }],
1351
1384
  required: ["prompt"],
1352
1385
  params: {
1353
- prompt: { kind: "string" },
1354
- aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR },
1355
- image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE },
1386
+ prompt: { kind: "string", maxLength: RECRAFT_PROMPT_MAX },
1387
+ aspect_ratio: { kind: "string", enum: RECRAFT_IMAGE_AR },
1356
1388
  strength: { kind: "number", min: 0, max: 1 },
1357
1389
  rgb_colors: { kind: "json" },
1358
1390
  background_rgb_color: { kind: "json" }
@@ -1437,19 +1469,22 @@ var MODEL_REGISTRY = {
1437
1469
  // wrongly recommended and which sends you round the same loop.
1438
1470
  label: "ByteDance Seedance 2.0",
1439
1471
  inputs: [],
1440
- optional_inputs: [{ kind: "image", mimes: REPLICATE_IMAGE_MIMES }],
1472
+ optional_inputs: [{ kind: "image", mimes: REPLICATE_IMAGE_MIMES, max: SEEDANCE_MAX_REFERENCES }],
1441
1473
  required: ["prompt"],
1442
1474
  params: {
1443
1475
  // Replicate's Seedance wrapper hard-caps the prompt at 4000 chars; gate
1444
1476
  // it here so an over-length prompt fails validate (free) not the billed call.
1445
- prompt: { kind: "string", maxLength: 4e3 },
1477
+ prompt: { kind: "string", maxLength: SEEDANCE_PROMPT_MAX },
1446
1478
  aspect_ratio: {
1447
1479
  kind: "string",
1448
1480
  enum: ["1:1", "3:4", "9:16", "4:3", "16:9", "21:9", "9:21"]
1449
1481
  },
1450
1482
  resolution: { kind: "string", enum: ["480p", "720p", "1080p", "4k"] },
1451
1483
  duration: { kind: "number", enum: SEEDANCE_DURATIONS },
1452
- seed: { kind: "number" },
1484
+ // Replicate types this `integer`, and a fractional seed is a schema
1485
+ // rejection rather than a rounded one. No range is advertised, so none is
1486
+ // invented — see `InputSpec.max` on why absent must stay ungated.
1487
+ seed: { kind: "number", integer: true },
1453
1488
  generate_audio: { kind: "boolean" }
1454
1489
  }
1455
1490
  },
@@ -1505,7 +1540,8 @@ var MODEL_REGISTRY = {
1505
1540
  aspect_ratio: { kind: "string", enum: ["16:9", "9:16"] },
1506
1541
  resolution: { kind: "string", enum: ["720p", "1080p", "4K"] },
1507
1542
  duration: { kind: "number", enum: [4, 6, 8] },
1508
- seed: { kind: "number" },
1543
+ // Vertex declares `seed` a uint32: "accepted range 0-4294967295".
1544
+ seed: { kind: "number", integer: true, min: 0, max: VEO_SEED_MAX },
1509
1545
  generate_audio: { kind: "boolean" },
1510
1546
  person_generation: { kind: "string", enum: ["allow_all", "allow_adult"] },
1511
1547
  enhance_prompt: { kind: "boolean" },
@@ -1529,9 +1565,10 @@ var MODEL_REGISTRY = {
1529
1565
  required: ["prompt"],
1530
1566
  params: {
1531
1567
  // Kling caps the prompt shorter than Seedance; gate it here so an
1532
- // over-length prompt fails validate (free) not the billed call.
1533
- prompt: { kind: "string", maxLength: 2500 },
1534
- negative_prompt: { kind: "string" },
1568
+ // over-length prompt fails validate (free) not the billed call. Its
1569
+ // negative prompt is a SEPARATE 2,500 budget, not a shared one.
1570
+ prompt: { kind: "string", maxLength: KLING_PROMPT_MAX },
1571
+ negative_prompt: { kind: "string", maxLength: KLING_PROMPT_MAX },
1535
1572
  aspect_ratio: { kind: "string", enum: ["16:9", "9:16", "1:1"] },
1536
1573
  resolution: { kind: "string", enum: ["720p"] },
1537
1574
  duration: { kind: "number", enum: KLING_DURATIONS },
@@ -1552,7 +1589,7 @@ var MODEL_REGISTRY = {
1552
1589
  aspect_ratio: { kind: "string", enum: ["16:9", "9:16"] },
1553
1590
  resolution: { kind: "string", enum: ["720p", "1080p", "4K"] },
1554
1591
  duration: { kind: "number", enum: [4, 6, 8] },
1555
- seed: { kind: "number" },
1592
+ seed: { kind: "number", integer: true, min: 0, max: VEO_SEED_MAX },
1556
1593
  generate_audio: { kind: "boolean" },
1557
1594
  // Image-to-video and EU/UK/CH/MENA regions cap this at `allow_adult`;
1558
1595
  // `allow_all` is text-to-video only. Allow both so an image-conditioned
@@ -1715,6 +1752,22 @@ var MODEL_REGISTRY = {
1715
1752
  function supportsParam(kind, model, param) {
1716
1753
  return MODEL_REGISTRY[kind]?.[model]?.params[param] !== void 0;
1717
1754
  }
1755
+ var DEFAULT_CLIP_DURATION_S = 5;
1756
+ function nearestClipDuration(model, preferred) {
1757
+ const schema = MODEL_REGISTRY.video_generate[model]?.params.duration;
1758
+ const allowed = schema?.kind === "number" ? schema.enum : void 0;
1759
+ if (!allowed?.length || allowed.includes(preferred)) return preferred;
1760
+ return [...allowed].sort((a, b) => Math.abs(a - preferred) - Math.abs(b - preferred) || a - b)[0];
1761
+ }
1762
+ function promptMaxLength(kind, model) {
1763
+ const schema = MODEL_REGISTRY[kind]?.[model]?.params.prompt;
1764
+ return schema?.kind === "string" ? schema.maxLength : void 0;
1765
+ }
1766
+ function maxInputReferences(kind, model) {
1767
+ const spec = MODEL_REGISTRY[kind]?.[model];
1768
+ const slot = spec?.optional_inputs?.find((input) => input.kind === "image") ?? spec?.inputs.find((input) => input.kind === "image");
1769
+ return slot?.max;
1770
+ }
1718
1771
  function supportedAspectRatios(kind, model) {
1719
1772
  const schema = MODEL_REGISTRY[kind]?.[model]?.params.aspect_ratio;
1720
1773
  return schema?.kind === "string" ? schema.enum : void 0;
@@ -1859,6 +1912,9 @@ function validateNumberValue(key, value, schema, model) {
1859
1912
  message: `${key}: ${value} not allowed by "${model}" (allowed: ${schema.enum.join(", ")})`
1860
1913
  };
1861
1914
  }
1915
+ if (schema.integer && !Number.isInteger(value)) {
1916
+ return { code: "invalid_value", param: key, message: `${key}: ${value} must be a whole number` };
1917
+ }
1862
1918
  if (schema.min !== void 0 && value < schema.min) {
1863
1919
  return { code: "invalid_value", param: key, message: `${key}: ${value} below min ${schema.min}` };
1864
1920
  }
@@ -7258,7 +7314,7 @@ var ImageGenerateParams = z17.object({
7258
7314
  model: imageModelEnum,
7259
7315
  prompt: z17.string().min(1),
7260
7316
  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(),
7261
- image_size: z17.enum(["0.5K", "1K", "2K", "4K"]).optional(),
7317
+ image_size: z17.enum(OPENROUTER_IMAGE_SIZES).optional(),
7262
7318
  // Rendering quality. OpenRouter models without a quality knob ignore it; the
7263
7319
  // registry gates which models accept it (gpt-image, Gemini).
7264
7320
  quality: z17.enum(["auto", "low", "medium", "high"]).optional(),
@@ -7292,7 +7348,7 @@ var ImageAspectAdaptParams = z18.object({
7292
7348
  formats: z18.array(z18.enum(ASPECT_ADAPT_FORMATS)).min(1).max(6).refine((formats) => new Set(formats).size === formats.length, { message: "formats must be unique" }).optional(),
7293
7349
  platform: z18.enum(AD_FORMAT_PLATFORMS).optional(),
7294
7350
  guidance: z18.string().min(1).optional(),
7295
- image_size: z18.enum(["0.5K", "1K", "2K", "4K"]).optional()
7351
+ image_size: z18.enum(OPENROUTER_IMAGE_SIZES).optional()
7296
7352
  }).strict().refine((p) => p.formats === void 0 !== (p.platform === void 0), {
7297
7353
  message: "provide exactly one of `formats` (explicit ratios) or `platform` (a placement preset)",
7298
7354
  path: ["formats"]
@@ -7378,7 +7434,7 @@ var ImageReferenceSheetParams = z21.object({
7378
7434
  style: z21.string().optional(),
7379
7435
  prompt_override: z21.string().min(1).optional(),
7380
7436
  aspect_ratio: z21.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(),
7381
- image_size: z21.enum(["0.5K", "1K", "2K", "4K"]).optional()
7437
+ image_size: z21.enum(OPENROUTER_IMAGE_SIZES).optional()
7382
7438
  }).strict();
7383
7439
  var imageReferenceSheetNode = delegated({
7384
7440
  id: "image_reference_sheet",
@@ -8148,12 +8204,20 @@ export {
8148
8204
  SEEDANCE_DURATIONS,
8149
8205
  ELEVENLABS_MAX_MUSIC_LENGTH_MS,
8150
8206
  IMAGE_GENERATE_MODELS,
8207
+ DEFAULT_IMAGE_GENERATE_MODEL,
8208
+ VIDEO_GENERATE_MODELS,
8151
8209
  DEFAULT_VIDEO_GENERATE_MODEL,
8210
+ supportsReferenceToVideo,
8152
8211
  supportsLastFrame,
8153
8212
  MODEL_REGISTRY,
8154
8213
  supportsParam,
8214
+ DEFAULT_CLIP_DURATION_S,
8215
+ nearestClipDuration,
8216
+ promptMaxLength,
8217
+ maxInputReferences,
8155
8218
  nearestSupportedAspectRatio,
8156
8219
  nearestSupportedImageSize,
8220
+ estimateVideoCredits,
8157
8221
  resolveConcurrency,
8158
8222
  ulid,
8159
8223
  isPersistedAssetRef,
@@ -8180,4 +8244,4 @@ export {
8180
8244
  defaultRegistry,
8181
8245
  createEngineFromEnv
8182
8246
  };
8183
- //# sourceMappingURL=chunk-RWHEFQXI.js.map
8247
+ //# sourceMappingURL=chunk-PJ5KH27L.js.map