Skip to content

feat(image_gen): config-only resolution control for nano-banana (default 4K) - #62

Merged
cwest merged 1 commit into
cwest/integrationfrom
wt/t_5694b717
Jul 11, 2026
Merged

feat(image_gen): config-only resolution control for nano-banana (default 4K)#62
cwest merged 1 commit into
cwest/integrationfrom
wt/t_5694b717

Conversation

@cwest

@cwest cwest commented Jul 11, 2026

Copy link
Copy Markdown
Owner

What

Adds an output-resolution knob to the nano-banana Gemini image backend, config-only — no per-call parameter on the image_generate tool schema. image_gen.nano-banana.resolution in config.yaml selects the size sent to the proxy as image_config.image_size (1K/2K/4K, uppercase K), defaulting to 4K.

Verified proxy contract

Probed the live proxy directly to pin the field (the research left this as an implement-time verification):

  • image_config.image_size{1K,2K,4K} changes the decoded PNG dimensions on the text-to-image path and composes with aspect_ratio.
  • image_config.resolution → HTTP 400 (Unknown name "resolution").
  • response_format.image_size → ignored (returns default geometry).

So only image_config.image_size is sent.

E2E evidence (real generation, decoded PNGs, live proxy)

config decoded dims expected
4K + landscape (16:9) 5504×3072 5504×3072 ✅
2K + landscape 2752×1536 2752×1536 ✅
1K + landscape 1376×768 1376×768 ✅
4K + square (1:1) 4096×4096 4096×4096 ✅

Behavior

  • Precedence (first hit wins): NANO_BANANA_IMAGE_RESOLUTION env → image_gen.nano-banana.resolution → default 4K.
  • Normalization: lowercase k → uppercase (proxy rejects lowercase); an out-of-ladder value falls back to the default rather than 400ing the proxy.
  • Per-model cap: a model that caps lower (e.g. Lite = 1K) degrades gracefully (clamps + logs) instead of erroring.
  • Graceful degradation: if the proxy ever rejects image_size, the request is retried once without it (current no-resolution behavior) so the generation still lands. An unrelated 400 (e.g. a safety block) is not masked by the fallback.
  • Edit-path caveat: edits tend to preserve the source image's dimensions regardless; resolution primarily affects text-to-image. Documented in the backend docstring.

Scope guarantee

No change to the core image_generate tool schema — its parameter list (prompt, aspect_ratio, image_url, reference_image_urls) is unchanged; tools/image_generation_tool.py is untouched. Resolution is purely config-driven; every call uses the configured size.

Tests

TDD (behavior contracts, no change-detector snapshots). tests/plugins/image_gen/test_nano_banana_provider.py grew 31 → 46 tests covering precedence, normalization, per-model cap clamp, payload shape, success-response resolution, and both graceful-degradation branches.

  • image_gen suite: 294/294
  • config suite (test_config + drift/validation/set-value/clears-custom): green

…fault 4K)

Add an output-resolution knob for the nano-banana Gemini image backend,
config-only — no per-call parameter on the image_generate tool schema.
image_gen.nano-banana.resolution (config.yaml) selects the size sent as
image_config.image_size (1K/2K/4K, uppercase), defaulting to 4K.

Verified against the live proxy that image_config.image_size is the field
that changes output dimensions on the text-to-image path and composes with
aspect_ratio (4K+16:9 -> 5504x3072, 4K+1:1 -> 4096x4096, 2K+16:9 ->
2752x1536, 1K+16:9 -> 1376x768). The proxy rejects image_config.resolution
and ignores response_format.image_size, so only image_config.image_size is
sent.

Resolution precedence (first hit wins): NANO_BANANA_IMAGE_RESOLUTION env ->
image_gen.nano-banana.resolution -> default 4K. Values are normalized to the
uppercase ladder; an out-of-ladder value falls back to the default rather
than 400ing the proxy. A per-model cap degrades gracefully (Lite = 1K clamps
down and logs, instead of erroring), and if the proxy rejects image_size the
request is retried once without it (current no-resolution behavior) so the
generation still lands. An unrelated 400 (e.g. a safety block) is not masked
by the fallback.

Edits tend to preserve the source image's dimensions regardless, so this
primarily affects text-to-image; documented in the backend docstring.
@cwest
cwest marked this pull request as ready for review July 11, 2026 20:59

@cwest cwest left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No changes needed.

The resolution knob is config-only and the core image_generate tool schema is untouched, which is the constraint that mattered here. The resolver reads image_gen.nano-banana.resolution with a sane precedence (env override, then config, then the 4K default), normalizes casing, and falls back to the default on an out-of-ladder value instead of forwarding something the proxy would reject. It sends image_config.image_size, which is the field that actually moves the pixels, and clamps to each model's documented ceiling so a lower-cap model degrades rather than erroring.

The degradation retry is scoped correctly: it only strips image_size and retries when the proxy's own error names that field, so an unrelated 400 (a safety block, say) still surfaces as an error instead of being swallowed by a blind retry. The config key nests under image_gen next to cache and is additive, so no schema version bump.

Tests are behavior contracts, not snapshots, and cover the whole surface: precedence, normalization, the cap clamp, payload shape, the success response carrying resolution, and both degradation branches. Ran the file locally (46 passed) plus the wider image_gen and config suites (345 passed, 0 failed). Checks are green and there are no open threads.

@cwest
cwest merged commit f03cf1c into cwest/integration Jul 11, 2026
31 checks passed
@cwest
cwest deleted the wt/t_5694b717 branch July 11, 2026 21:06
cwest added a commit that referenced this pull request Jul 26, 2026
…fault 4K) (#62)

Add an output-resolution knob for the nano-banana Gemini image backend,
config-only — no per-call parameter on the image_generate tool schema.
image_gen.nano-banana.resolution (config.yaml) selects the size sent as
image_config.image_size (1K/2K/4K, uppercase), defaulting to 4K.

Verified against the live proxy that image_config.image_size is the field
that changes output dimensions on the text-to-image path and composes with
aspect_ratio (4K+16:9 -> 5504x3072, 4K+1:1 -> 4096x4096, 2K+16:9 ->
2752x1536, 1K+16:9 -> 1376x768). The proxy rejects image_config.resolution
and ignores response_format.image_size, so only image_config.image_size is
sent.

Resolution precedence (first hit wins): NANO_BANANA_IMAGE_RESOLUTION env ->
image_gen.nano-banana.resolution -> default 4K. Values are normalized to the
uppercase ladder; an out-of-ladder value falls back to the default rather
than 400ing the proxy. A per-model cap degrades gracefully (Lite = 1K clamps
down and logs, instead of erroring), and if the proxy rejects image_size the
request is retried once without it (current no-resolution behavior) so the
generation still lands. An unrelated 400 (e.g. a safety block) is not masked
by the fallback.

Edits tend to preserve the source image's dimensions regardless, so this
primarily affects text-to-image; documented in the backend docstring.

(cherry picked from commit f03cf1c)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant