@koda-sl/baker-cli 0.149.0-dev.7b64ca6b5 → 0.150.0-dev.04484bc24
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 +35 -32
- package/dist/{chunk-ZBRVPPJP.js → chunk-JBDSJZBZ.js} +61 -18
- package/dist/chunk-JBDSJZBZ.js.map +1 -0
- package/dist/cli.js +302 -179
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZBRVPPJP.js.map +0 -1
package/README.md
CHANGED
|
@@ -492,6 +492,8 @@ Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remo
|
|
|
492
492
|
|
|
493
493
|
**Amending a staged op** — `draft amend <ref> (--file patch.json | --patch '<json>')` merges a JSON patch into an already-staged op's payload (objects deep-merge, `null` deletes a key, arrays/scalars replace) and re-validates it in full. Always prefer this over removing and re-staging — it preserves the op's ref (so dependents don't break) and its position in the draft. `draft show <ref>` prints the full staged payload (including warnings/annotations) so you can verify a change looks right before publish.
|
|
494
494
|
|
|
495
|
+
**Already-satisfied writes aren't staged** — an update or pause/resume whose every field already holds the requested value on the live account is dropped instead of staged: the envelope returns `"staged": false, "noop": true` with a `reason` ("this ad is already Paused on Google Ads"), plus a hint. This is a success, not an error — there is nothing to publish. Only a positively-read live snapshot can trigger it; a failed or empty read always stages. Batch responses list the dropped ops under `skipped`, and `count` reflects what actually entered the draft.
|
|
496
|
+
|
|
495
497
|
**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.
|
|
496
498
|
|
|
497
499
|
---
|
|
@@ -844,6 +846,7 @@ audit --format md # deliverable-ready markdown table
|
|
|
844
846
|
|
|
845
847
|
campaign-groups create|update|pause|resume|duplicate # staged writes (see below)
|
|
846
848
|
campaigns create|update|pause|resume|archive|duplicate
|
|
849
|
+
campaigns url-params <ad-set-id> # UTMs for every ad in the ad set
|
|
847
850
|
creatives create|update|pause|resume|duplicate
|
|
848
851
|
audiences create|upload
|
|
849
852
|
conversions create|update
|
|
@@ -866,6 +869,12 @@ baker ads linkedin campaigns create --name "ABM Tier-1" --group li_temp_x1 \
|
|
|
866
869
|
baker ads linkedin creatives create --campaign li_temp_x2 --format image \
|
|
867
870
|
--image-id <bakerImageId> --headline "Book a demo" --landing-url https://example.com/demo --cta REQUEST_DEMO
|
|
868
871
|
|
|
872
|
+
# UTMs belong on the ad set — LinkedIn appends them to every ad in it, live ones included
|
|
873
|
+
baker ads linkedin campaigns url-params 123456 \
|
|
874
|
+
--param "utm_source=linkedin&utm_medium=paid-social" \
|
|
875
|
+
--dynamic utm_campaign=CAMPAIGN_NAME --dynamic utm_content=CREATIVE_ID
|
|
876
|
+
baker ads linkedin campaigns url-params 123456 --clear # remove them all
|
|
877
|
+
|
|
869
878
|
# Fix the classic audit findings
|
|
870
879
|
baker ads linkedin campaigns update 123456 --audience-expansion off --lan off
|
|
871
880
|
baker ads linkedin campaigns update 123456 --bid 7.50 --daily-budget 100 --currency USD
|
|
@@ -1641,6 +1650,8 @@ baker images stock "office reaction" --people only
|
|
|
1641
1650
|
|
|
1642
1651
|
Free tier exists but watermarks previews — pass `--license freemium` to filter to clean free assets explicitly.
|
|
1643
1652
|
|
|
1653
|
+
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).
|
|
1654
|
+
|
|
1644
1655
|
**Flags:**
|
|
1645
1656
|
|
|
1646
1657
|
| Flag | Description |
|
|
@@ -1722,13 +1733,15 @@ baker images generate "flat geometric mascot, brand palette" \
|
|
|
1722
1733
|
| Model | Best for | Aspect ratios | Sizes |
|
|
1723
1734
|
|---|---|---|---|
|
|
1724
1735
|
| `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` |
|
|
1725
|
-
| `google/gemini-3.5-flash` | Fast; extreme aspect ratios | standard **+** `1:4` `4:1` `1:8` `8:1` | `0.5K`–`4K` |
|
|
1726
1736
|
| `google/gemini-3-pro-image-preview` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
|
|
1727
|
-
| `openai/gpt-
|
|
1737
|
+
| `openai/gpt-image-2` | Photoreal + cleanest in-image text — ad/landing reproduction | standard set **minus** `4:5` `5:4` | *(none — derived from the ratio)* |
|
|
1738
|
+
| `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) |
|
|
1728
1739
|
| `recraft/recraft-v4.1-pro-vector` | Vector/flat/SVG-style with palette control | standard set | `1K` `2K` `4K` |
|
|
1729
1740
|
|
|
1730
1741
|
Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9` `21:9`.
|
|
1731
1742
|
|
|
1743
|
+
`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.
|
|
1744
|
+
|
|
1732
1745
|
**Flags:**
|
|
1733
1746
|
|
|
1734
1747
|
| Flag | Description |
|
|
@@ -1748,8 +1761,6 @@ Returns `{ images: [{ imageId, imageUrl, deduped, width, height }], model, costU
|
|
|
1748
1761
|
|
|
1749
1762
|
Brand logo via Brandfetch CDN (`fallback/404`). Probes all 5 logo variants in parallel and returns whichever ones the domain actually publishes. Auto-ingests the first by default.
|
|
1750
1763
|
|
|
1751
|
-
**Variants are returned wordmark-first** (`logo` dark → `logo` light → `symbol` dark → `symbol` light → `icon`), so the default `--auto-ingest 1` gives you the wide lockup a "trusted by" strip needs. `icon` is deliberately last: for many brands (Salesforce, HubSpot) it is a solid coloured square with the mark knocked out of it, which renders as a filled box beside the wordmarks around it and cannot be rescued with CSS. Reach for it only in slots under ~32px, and then ask for it explicitly.
|
|
1752
|
-
|
|
1753
1764
|
```bash
|
|
1754
1765
|
baker images logo stripe.com # all available variants
|
|
1755
1766
|
baker images logo linear.app --variant logo # just full wordmark logos (light + dark)
|
|
@@ -1757,16 +1768,13 @@ baker images logo linear.app --variant icon # just the square app icon
|
|
|
1757
1768
|
baker images logo linear.app --variant symbol # just the bare brand mark (no wordmark), light + dark
|
|
1758
1769
|
baker images logo example.com --no-auto-ingest # browse without writing to library
|
|
1759
1770
|
baker images logo stripe.com --auto-ingest 5 # ingest every returned variant
|
|
1760
|
-
baker images logo stripe.com,intercom.com,notion.so --variant logo # a whole strip in one call
|
|
1761
1771
|
```
|
|
1762
1772
|
|
|
1763
|
-
**Multiple domains.** Pass a comma-separated list (max 20) to source an entire logo row in one call. Domains are fetched in parallel and a failure on one never loses the rest, so a partial strip is still placeable. The response shape switches to `{ results: [{ domain, hits, error? }] }`, and domains Brandfetch didn't know come back in a `hints[]` entry naming the fallback ladder. A single domain returns the original shape unchanged.
|
|
1764
|
-
|
|
1765
1773
|
**The 5 variants — pick by use case:**
|
|
1766
1774
|
|
|
1767
1775
|
| `--variant` | `providerMeta.type` | `providerMeta.theme` | What it is | Typical use |
|
|
1768
1776
|
|---|---|---|---|---|
|
|
1769
|
-
| `icon` | `icon` | `null` (single) | App-style square icon (favicon-grade), often
|
|
1777
|
+
| `icon` | `icon` | `null` (single) | App-style square icon (favicon-grade), often rounded | Favicon, social proof grid, app-tile mock |
|
|
1770
1778
|
| `logo` | `logo` | `light` | Full wordmark designed for **light backgrounds** (dark ink on transparent) | Logo on white page, light hero |
|
|
1771
1779
|
| `logo` | `logo` | `dark` | Full wordmark designed for **dark backgrounds** (light ink on transparent) | Footer on dark, dark hero |
|
|
1772
1780
|
| `symbol` | `symbol` | `light` | Brand mark/glyph only (no wordmark) for **light backgrounds** | Compact UI, avatar, small-space placement |
|
|
@@ -2030,7 +2038,7 @@ Inputs accept a single path, a quoted glob, or a comma-separated list. Writes in
|
|
|
2030
2038
|
Pipeline stages, each opt-in via flag presence and idempotent on already-correct input:
|
|
2031
2039
|
|
|
2032
2040
|
1. Rasterize SVG (only when raster ops are requested — pure resize keeps SVG)
|
|
2033
|
-
2. `--remove-bg` → two-pass background removal with gradient/solid branching (skipped when the source is already transparent)
|
|
2041
|
+
2. `--remove-bg` → two-pass background removal with gradient/solid branching (skipped when the source is already transparent)
|
|
2034
2042
|
3. `--color <hex>` → recolor foreground pixels to the target color. Single-fill assets stay monochrome; multi-fill SVGs preserve relative contrast with target-color tones so foreground/background fills do not collapse into a solid shape.
|
|
2035
2043
|
4. `--shrink-to-content` → trim transparent borders
|
|
2036
2044
|
5. Resize:
|
|
@@ -2750,7 +2758,7 @@ A re-run with no changes hits the cache for every node — total runtime drops t
|
|
|
2750
2758
|
"type": "text_generate",
|
|
2751
2759
|
"inputs": { "topic": "$ref:topic.text" },
|
|
2752
2760
|
"params": {
|
|
2753
|
-
"model": "google/gemini-3.
|
|
2761
|
+
"model": "google/gemini-3.1-flash-image-preview",
|
|
2754
2762
|
"prompt": "Write a 6-word tagline for {{topic}}."
|
|
2755
2763
|
}
|
|
2756
2764
|
}
|
|
@@ -3049,37 +3057,31 @@ Aspect-ratio sets used below:
|
|
|
3049
3057
|
|
|
3050
3058
|
---
|
|
3051
3059
|
|
|
3052
|
-
###### Model: `openai/gpt-
|
|
3060
|
+
###### Model: `openai/gpt-image-2`
|
|
3053
3061
|
|
|
3054
|
-
Photorealistic generalist. Optional `reference` image.
|
|
3062
|
+
Photorealistic generalist, and the cleanest at rendering text inside the image. Optional `reference` image (up to 16).
|
|
3055
3063
|
|
|
3056
3064
|
| Name | Type | Required | Notes |
|
|
3057
3065
|
|---|---|---|---|
|
|
3058
|
-
| `model` | literal | yes | `"openai/gpt-
|
|
3066
|
+
| `model` | literal | yes | `"openai/gpt-image-2"` |
|
|
3059
3067
|
| `prompt` | string | yes | non-empty |
|
|
3060
|
-
| `aspect_ratio` | enum | no |
|
|
3061
|
-
| `image_size` | enum | no | `1K \| 2K \| 4K` |
|
|
3068
|
+
| `aspect_ratio` | enum | no | GPT AR = `1:1, 2:3, 3:2, 3:4, 4:3, 9:16, 16:9, 21:9` — **no `4:5` / `5:4`** |
|
|
3062
3069
|
| `quality` | enum | no | `auto \| low \| medium \| high` — rendering quality; the scaffolder sets `high` for photoreal frames. (Do **not** pass `input_fidelity`: gpt-image-2 forces high fidelity automatically and the param can fail the request.) |
|
|
3063
3070
|
|
|
3071
|
+
No `image_size`: OpenAI derives the pixel dimensions from the ratio, so a size is ignored. For a portrait ad format, render at `3:4` and adapt.
|
|
3072
|
+
|
|
3064
3073
|
```json
|
|
3065
3074
|
{ "id": "hero", "type": "image_generate",
|
|
3066
|
-
"params": { "model": "openai/gpt-
|
|
3075
|
+
"params": { "model": "openai/gpt-image-2", "prompt": "Photorealistic photo of …", "aspect_ratio": "16:9", "quality": "high" } }
|
|
3067
3076
|
```
|
|
3068
3077
|
|
|
3069
|
-
###### Model: `
|
|
3070
|
-
|
|
3071
|
-
Newest Gemini Flash image model. Always returns an image output. Best for fast iteration; supports extreme aspect ratios and the `0.5K` size for cheap previews.
|
|
3078
|
+
###### Model: `openai/gpt-5.4-image-2` (legacy)
|
|
3072
3079
|
|
|
3073
|
-
|
|
3074
|
-
|---|---|---|---|
|
|
3075
|
-
| `model` | literal | yes | `"google/gemini-3.5-flash"` |
|
|
3076
|
-
| `prompt` | string | yes | non-empty |
|
|
3077
|
-
| `aspect_ratio` | enum | no | EXTREME AR |
|
|
3078
|
-
| `image_size` | enum | no | `0.5K \| 1K \| 2K \| 4K` |
|
|
3080
|
+
Kept only so canvases authored before the gpt-image-2 switch still validate and re-run unchanged — `baker canvas rerun` replays a recorded canvas verbatim. It keeps the full standard ratio set (including `4:5` / `5:4`) and still accepts `image_size`, which the provider ignores. Prefer `openai/gpt-image-2` for anything new.
|
|
3079
3081
|
|
|
3080
3082
|
###### Model: `google/gemini-3.1-flash-image-preview`
|
|
3081
3083
|
|
|
3082
|
-
|
|
3084
|
+
Gemini flash image model — the default. Fast iteration, reference editing, extreme aspect ratios, and the `0.5K` size for cheap previews.
|
|
3083
3085
|
|
|
3084
3086
|
| Name | Type | Required | Notes |
|
|
3085
3087
|
|---|---|---|---|
|
|
@@ -3296,7 +3298,7 @@ There is no `prompt` param — the extraction schema is the node. Use `focus` to
|
|
|
3296
3298
|
"params": { "model": "~google/gemini-pro-latest" } },
|
|
3297
3299
|
{ "id": "remix", "type": "image_generate",
|
|
3298
3300
|
"inputs": { "blueprint": "$ref:blueprint.description" },
|
|
3299
|
-
"params": { "model": "openai/gpt-
|
|
3301
|
+
"params": { "model": "openai/gpt-image-2",
|
|
3300
3302
|
"prompt": "Recreate an image matching this exact blueprint, but swap the product for a ceramic mug:\n{{blueprint}}" } }
|
|
3301
3303
|
],
|
|
3302
3304
|
"output": { "node": "remix", "output": "images" }
|
|
@@ -3377,9 +3379,9 @@ Fail-fast: an invalid model selection (wrong count, duplicate or out-of-range pi
|
|
|
3377
3379
|
{
|
|
3378
3380
|
"schema": "baker-canvas/1",
|
|
3379
3381
|
"nodes": [
|
|
3380
|
-
{ "id": "a", "type": "image_generate", "params": { "model": "google/gemini-3.
|
|
3381
|
-
{ "id": "b", "type": "image_generate", "params": { "model": "google/gemini-3.
|
|
3382
|
-
{ "id": "c", "type": "image_generate", "params": { "model": "google/gemini-3.
|
|
3382
|
+
{ "id": "a", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image-preview", "prompt": "hero shot of a ceramic mug, warm morning light" } },
|
|
3383
|
+
{ "id": "b", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image-preview", "prompt": "hero shot of a ceramic mug, studio softbox" } },
|
|
3384
|
+
{ "id": "c", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image-preview", "prompt": "hero shot of a ceramic mug, dramatic side light" } },
|
|
3383
3385
|
{ "id": "best", "type": "image_select",
|
|
3384
3386
|
"inputs": { "images": ["$ref:a.images#0", "$ref:b.images#0", "$ref:c.images#0"] },
|
|
3385
3387
|
"params": { "model": "~google/gemini-pro-latest", "prompt": "most premium-feeling lighting, no harsh shadows" } }
|
|
@@ -4081,7 +4083,7 @@ baker canvas run ./reference-ad.video.canvas.json
|
|
|
4081
4083
|
| `--focus <text>` | — | Known provenance/emphasis to ground the deconstruct. |
|
|
4082
4084
|
| `--deconstruct-model <id>` | `~google/gemini-pro-latest` | Override the `video_deconstruct` model. |
|
|
4083
4085
|
| `--select-model <id>` | `~google/gemini-flash-latest` | Override the element-selection `text_generate` model. |
|
|
4084
|
-
| `--image-model <id>` | `openai/gpt-
|
|
4086
|
+
| `--image-model <id>` | `openai/gpt-image-2` | Override the per-frame `image_generate` model (defaults to the strongest, matching `scaffold-static-ad`). |
|
|
4085
4087
|
| `--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). |
|
|
4086
4088
|
| `--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. |
|
|
4087
4089
|
| `--motion-transfer` | off | Motion driven from a reference / hyper-dynamic → the router picks Kling. |
|
|
@@ -4138,7 +4140,7 @@ baker canvas run ./static-ad.canvas.json
|
|
|
4138
4140
|
| `--describe-model <id>` | registry default (`~google/gemini-pro-latest`) | Override the `image_describe` model. |
|
|
4139
4141
|
| `--select-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the element-selection `text_generate` model. |
|
|
4140
4142
|
| `--layout-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the global-layout `text_generate` model. |
|
|
4141
|
-
| `--gen-model <id>` | registry default (`openai/gpt-
|
|
4143
|
+
| `--gen-model <id>` | registry default (`openai/gpt-image-2`) | Override the `image_generate` model. |
|
|
4142
4144
|
| `--platform <channel>` | `meta` | `_definition.md` channel **and** the default placement set the hero fans out to (`meta`\|`google`\|`linkedin`\|`tiktok`\|`youtube`\|`x`\|`other`; `x`/`other` fan out to nothing). |
|
|
4143
4145
|
| `--placements <preset\|none>` | derived from `--platform` | Override the fan-out set: `meta_feed`\|`meta_feed_stories`\|`meta_all`\|`linkedin`\|`google_demandgen`\|`tiktok`\|`youtube`, or `none` for a single base ad. |
|
|
4144
4146
|
| `--aspect <ratio>` | inferred from the image, else `9:16` | Force the hero's aspect ratio (clamped into the placement set so it stays a free pass-through). |
|
|
@@ -4722,3 +4724,4 @@ Pushing to `main` with changes in `packages/cli/` triggers the GitHub Actions wo
|
|
|
4722
4724
|
npx convex env set BAKER_CLI_VERSION <version> # Override sandbox version
|
|
4723
4725
|
npx convex env remove BAKER_CLI_VERSION # Clean up after testing
|
|
4724
4726
|
```
|
|
4727
|
+
- **0.150.0**: `baker ads linkedin campaigns url-params <ad-set-id>` stages an ad set's URL tracking parameters (LinkedIn's `adTrackingParameters`) — `--param key=value` for fixed values (repeatable or `&`-joined), `--dynamic key=PLACEHOLDER` for values LinkedIn fills in per ad (`CAMPAIGN_NAME`, `CREATIVE_ID`, …), `--clear` to remove them. LinkedIn appends these to the landing URL of every ad in the ad set, including ads already running, so this replaces stamping the same UTM onto each ad's `--landing-url` — which missed later ads and double-appended keys the ad set already set. Account-level parameters remain UI-only (LinkedIn exposes no API for them).
|
|
@@ -1114,6 +1114,7 @@ function normalizeForCanonical(value) {
|
|
|
1114
1114
|
// ../canvas-contract/src/registry.ts
|
|
1115
1115
|
var OPENROUTER_IMAGE_AR = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
|
|
1116
1116
|
var OPENROUTER_IMAGE_AR_EXTREME = [...OPENROUTER_IMAGE_AR, "1:4", "4:1", "1:8", "8:1"];
|
|
1117
|
+
var OPENROUTER_IMAGE_AR_GPT = ["1:1", "2:3", "3:2", "3:4", "4:3", "9:16", "16:9", "21:9"];
|
|
1117
1118
|
var OPENROUTER_IMAGE_SIZE = ["1K", "2K", "4K"];
|
|
1118
1119
|
var OPENROUTER_IMAGE_SIZE_EXTENDED = ["0.5K", ...OPENROUTER_IMAGE_SIZE];
|
|
1119
1120
|
var OPENROUTER_IMAGE_QUALITY = ["auto", "low", "medium", "high"];
|
|
@@ -1134,8 +1135,8 @@ var REPLICATE_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
|
1134
1135
|
var DECONSTRUCT_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
1135
1136
|
var REPLICATE_AUDIO_MIMES = ["audio/wav", "audio/mpeg", "audio/mp3"];
|
|
1136
1137
|
var IMAGE_GENERATE_MODELS = [
|
|
1138
|
+
"openai/gpt-image-2",
|
|
1137
1139
|
"openai/gpt-5.4-image-2",
|
|
1138
|
-
"google/gemini-3.5-flash",
|
|
1139
1140
|
"google/gemini-3.1-flash-image-preview",
|
|
1140
1141
|
"google/gemini-3-pro-image-preview",
|
|
1141
1142
|
"recraft/recraft-v4.1-pro-vector"
|
|
@@ -1234,28 +1235,37 @@ var MODEL_REGISTRY = {
|
|
|
1234
1235
|
// `seed` is omitted on every entry — OpenRouter's `image_config` schema
|
|
1235
1236
|
// has no `seed` slot for image-gen (different from video-gen), so passing
|
|
1236
1237
|
// it would be a silent no-op.
|
|
1237
|
-
|
|
1238
|
-
|
|
1238
|
+
// The direct image model, not the `gpt-5.4-image-2` chat model that emitted
|
|
1239
|
+
// images as a side effect. Notable capability gaps vs the Gemini entries:
|
|
1240
|
+
// no `image_size` (OpenAI picks the pixel dimensions from the ratio, and a
|
|
1241
|
+
// `resolution` is silently ignored) and the narrower `..._AR_GPT` ratio set.
|
|
1242
|
+
"openai/gpt-image-2": {
|
|
1243
|
+
label: "OpenAI GPT Image 2",
|
|
1239
1244
|
inputs: [],
|
|
1240
1245
|
optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
1241
1246
|
required: ["prompt"],
|
|
1242
1247
|
params: {
|
|
1243
1248
|
prompt: { kind: "string" },
|
|
1244
|
-
aspect_ratio: { kind: "string", enum:
|
|
1245
|
-
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE },
|
|
1249
|
+
aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR_GPT },
|
|
1246
1250
|
quality: { kind: "string", enum: OPENROUTER_IMAGE_QUALITY }
|
|
1247
1251
|
}
|
|
1248
1252
|
},
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1253
|
+
// LEGACY — kept so canvases authored before the gpt-image-2 switch still
|
|
1254
|
+
// validate and re-run unchanged (`baker canvas rerun` restores a recorded
|
|
1255
|
+
// canvas verbatim, so dropping this model would break every stored creative
|
|
1256
|
+
// that used it). Its param surface is the ORIGINAL one on purpose: unlike
|
|
1257
|
+
// gpt-image-2 it still honors 4:5/5:4, and `image_size` is accepted-then-
|
|
1258
|
+
// ignored upstream rather than rejected. Verified live on /api/v1/images.
|
|
1259
|
+
// Don't reach for it in new work — the scaffolders default to gpt-image-2.
|
|
1260
|
+
"openai/gpt-5.4-image-2": {
|
|
1261
|
+
label: "OpenAI GPT-5.4 Image 2 (legacy)",
|
|
1252
1262
|
inputs: [],
|
|
1253
1263
|
optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
1254
1264
|
required: ["prompt"],
|
|
1255
1265
|
params: {
|
|
1256
1266
|
prompt: { kind: "string" },
|
|
1257
|
-
aspect_ratio: { kind: "string", enum:
|
|
1258
|
-
image_size: { kind: "string", enum:
|
|
1267
|
+
aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR },
|
|
1268
|
+
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE },
|
|
1259
1269
|
quality: { kind: "string", enum: OPENROUTER_IMAGE_QUALITY }
|
|
1260
1270
|
}
|
|
1261
1271
|
},
|
|
@@ -1607,6 +1617,36 @@ var MODEL_REGISTRY = {
|
|
|
1607
1617
|
}
|
|
1608
1618
|
}
|
|
1609
1619
|
};
|
|
1620
|
+
function supportsParam(kind, model, param) {
|
|
1621
|
+
return MODEL_REGISTRY[kind]?.[model]?.params[param] !== void 0;
|
|
1622
|
+
}
|
|
1623
|
+
function supportedAspectRatios(kind, model) {
|
|
1624
|
+
const schema = MODEL_REGISTRY[kind]?.[model]?.params.aspect_ratio;
|
|
1625
|
+
return schema?.kind === "string" ? schema.enum : void 0;
|
|
1626
|
+
}
|
|
1627
|
+
function ratioValue(ratio) {
|
|
1628
|
+
const [w, h] = ratio.split(":").map(Number);
|
|
1629
|
+
if (!w || !h || !Number.isFinite(w) || !Number.isFinite(h)) return null;
|
|
1630
|
+
return w / h;
|
|
1631
|
+
}
|
|
1632
|
+
function nearestSupportedAspectRatio(kind, model, target) {
|
|
1633
|
+
const supported = supportedAspectRatios(kind, model);
|
|
1634
|
+
if (!supported || supported.includes(target)) return target;
|
|
1635
|
+
const wanted = ratioValue(target);
|
|
1636
|
+
if (wanted === null) return target;
|
|
1637
|
+
let best = target;
|
|
1638
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1639
|
+
for (const candidate of supported) {
|
|
1640
|
+
const value = ratioValue(candidate);
|
|
1641
|
+
if (value === null) continue;
|
|
1642
|
+
const distance2 = Math.abs(Math.log(value / wanted));
|
|
1643
|
+
if (distance2 < bestDistance) {
|
|
1644
|
+
bestDistance = distance2;
|
|
1645
|
+
best = candidate;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
return best;
|
|
1649
|
+
}
|
|
1610
1650
|
|
|
1611
1651
|
// ../canvas-contract/src/validateParams.ts
|
|
1612
1652
|
function isManagedNodeKind(nodeType) {
|
|
@@ -6991,8 +7031,9 @@ var dialogueNode = delegated({
|
|
|
6991
7031
|
// src/engine/nodes/remote/image.ts
|
|
6992
7032
|
import { z as z17 } from "zod";
|
|
6993
7033
|
var IMAGE_GENERATE_MODELS2 = [
|
|
7034
|
+
"openai/gpt-image-2",
|
|
7035
|
+
// Legacy — see the registry entry; kept so pre-switch canvases still run.
|
|
6994
7036
|
"openai/gpt-5.4-image-2",
|
|
6995
|
-
"google/gemini-3.5-flash",
|
|
6996
7037
|
"google/gemini-3.1-flash-image-preview",
|
|
6997
7038
|
"google/gemini-3-pro-image-preview",
|
|
6998
7039
|
"recraft/recraft-v4.1-pro-vector"
|
|
@@ -7002,11 +7043,11 @@ var ImageGenerateParams = z17.object({
|
|
|
7002
7043
|
prompt: z17.string().min(1),
|
|
7003
7044
|
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(),
|
|
7004
7045
|
image_size: z17.enum(["0.5K", "1K", "2K", "4K"]).optional(),
|
|
7005
|
-
// Rendering quality
|
|
7006
|
-
//
|
|
7046
|
+
// Rendering quality. OpenRouter models without a quality knob ignore it; the
|
|
7047
|
+
// registry gates which models accept it (gpt-image, Gemini).
|
|
7007
7048
|
quality: z17.enum(["auto", "low", "medium", "high"]).optional(),
|
|
7008
|
-
// Recraft v4 vector controls
|
|
7009
|
-
// rejects them on non-Recraft models.
|
|
7049
|
+
// Recraft v4 vector controls, sent as the provider's own `controls` block.
|
|
7050
|
+
// Registry rejects them on non-Recraft models.
|
|
7010
7051
|
strength: z17.number().min(0).max(1).optional(),
|
|
7011
7052
|
rgb_colors: z17.array(z17.array(z17.number().int().min(0).max(255))).optional(),
|
|
7012
7053
|
background_rgb_color: z17.array(z17.number().int().min(0).max(255)).optional()
|
|
@@ -7015,8 +7056,8 @@ var imageGenerateNode = delegated({
|
|
|
7015
7056
|
id: "image_generate",
|
|
7016
7057
|
version: "2.2.0",
|
|
7017
7058
|
category: "image",
|
|
7018
|
-
summary: "Generate images for ad creatives. Curated model set: GPT
|
|
7019
|
-
when_to_use: "Use for hero shots, product photography, illustrations, and vector logos. `recraft/recraft-v4.1-pro-vector` for crisp vector / logo work; `openai/gpt-
|
|
7059
|
+
summary: "Generate images for ad creatives. Curated model set: GPT Image 2, Gemini 3.1 Flash Image Preview, Gemini 3 Pro Image, Recraft v4.1 Pro Vector. Per-model param support comes from the canvas-engine model registry.",
|
|
7060
|
+
when_to_use: "Use for hero shots, product photography, illustrations, and vector logos. `recraft/recraft-v4.1-pro-vector` for crisp vector / logo work; `openai/gpt-image-2` for photorealistic and in-image text (NOTE: it has no `image_size`, and no 4:5 / 5:4 \u2014 route portrait ad formats through Gemini or `image_aspect_adapt`); Gemini variants for fast iteration, extreme ratios, and editing via the `reference` input. `reference` accepts ONE image or an ARRAY of images \u2014 wire several to combine references in a single generation (e.g. a subject sheet + a font specimen + the original ad). Every reference is forwarded to the model in array order.",
|
|
7020
7061
|
// `reference` is one image or an ordered array of images. The backend forwards
|
|
7021
7062
|
// each as a separate `image_url` to the provider (OpenRouter accepts many).
|
|
7022
7063
|
inputs: z17.object({ reference: z17.union([ImageRef, z17.array(ImageRef).min(1)]).optional() }).loose(),
|
|
@@ -7872,6 +7913,8 @@ export {
|
|
|
7872
7913
|
IMAGE_GENERATE_MODELS,
|
|
7873
7914
|
DEFAULT_VIDEO_GENERATE_MODEL,
|
|
7874
7915
|
MODEL_REGISTRY,
|
|
7916
|
+
supportsParam,
|
|
7917
|
+
nearestSupportedAspectRatio,
|
|
7875
7918
|
resolveConcurrency,
|
|
7876
7919
|
ulid,
|
|
7877
7920
|
isPersistedAssetRef,
|
|
@@ -7898,4 +7941,4 @@ export {
|
|
|
7898
7941
|
defaultRegistry,
|
|
7899
7942
|
createEngineFromEnv
|
|
7900
7943
|
};
|
|
7901
|
-
//# sourceMappingURL=chunk-
|
|
7944
|
+
//# sourceMappingURL=chunk-JBDSJZBZ.js.map
|