Skip to content

fix(ai): keep image sizes inside proportional limits - #59535

Open
cyzroot-jpg wants to merge 2 commits into
anomalyco:devfrom
cyzroot-jpg:image-sizing-limits
Open

cyzroot-jpg wants to merge 2 commits into
anomalyco:devfrom
cyzroot-jpg:image-sizing-limits

Conversation

@cyzroot-jpg

@cyzroot-jpg cyzroot-jpg commented Oct 27, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #59536

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

fit() clamps the requested aspect to maxRatio before snapping each edge to the provider's multiple, but the independent snap can land just outside the ratio it just clamped to. The gpt-image path then sends a size such as 880x2656 (3.018:1) for 1:3 at 1.5K. This repairs the rounded size against every limit before returning it, so the result stays inside the ratio as well as the edge and pixel bounds.

tier() also accepted 0K, and a malformed aspect such as 0:0 produced NaN. Because missed() compares with >/<, NaN slipped through as a success. Media.AspectRatio now requires both sides to be positive, so every provider rejects 0:0 and 1:0 at the request boundary; non-positive tiers are treated as unknown, and exact() fails instead of returning NaN x NaN.

How did you verify your code works?

  • Added regression tests for the ratio snap and degenerate shapes.
  • bun test test/image-sizing.test.ts
  • bun test in packages/ai (1947 pass, 28 skip)
  • bun run typecheck in packages/ai
  • bunx oxlint packages/ai/src/protocols/utils/image-sizing.ts packages/ai/test/image-sizing.test.ts

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" role="img" aria-label="Image size snapping back within a 3:1 ratio">
  <rect width="240" height="72" fill="none"/>
  <text x="8" y="14" font-size="8" fill="currentColor" opacity=".6">1:3 @ 1.5K</text>
  <g fill="none" stroke="currentColor">
    <rect x="16" y="28" width="18" height="36" opacity=".15"/>
    <rect x="48" y="28" width="18" height="36" stroke-dasharray="2 2">
      <animate attributeName="height" values="36;36;30;30" dur="4s" keyTimes="0;.45;.55;1" repeatCount="indefinite"/>
      <animate attributeName="y" values="28;28;34;34" dur="4s" keyTimes="0;.45;.55;1" repeatCount="indefinite"/>
      <animate attributeName="opacity" values=".1;.1;.9;.9" dur="4s" keyTimes="0;.45;.55;1" repeatCount="indefinite"/>
    </rect>
  </g>
  <line x1="16" y1="68" x2="66" y2="68" stroke="currentColor" opacity=".3"/>
  <text x="80" y="38" font-size="9" fill="currentColor" opacity=".45">880x2656</text>
  <text x="80" y="50" font-size="9" fill="currentColor" opacity=".35">3.018:1</text>
  <text x="148" y="38" font-size="9" fill="currentColor">896x2656</text>
  <text x="148" y="50" font-size="9" fill="currentColor" opacity=".6">2.964:1</text>
  <path d="M132 42 h10 m-3 -3 l3 3 l-3 3" stroke="currentColor" fill="none">
    <animate attributeName="opacity" values=".2;.8;.8;.2" dur="4s" keyTimes="0;.45;.55;1" repeatCount="indefinite"/>
  </path>
</svg>

Screenshots / recordings

N/A, no UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Public API: Media.AspectRatio now rejects aspects with a zero side (only affects requests that used to produce NaN sizes)

Snapping each edge to the provider's size multiple can push the shorter edge below a requested aspect that sits at maxRatio, so 1.5K at 1:3 produced 880x2656 (3.018:1) for gpt-image. Repair the rounded size against every limit before returning it.

Degenerate shapes such as 0:0 or 0K also produced NaN dimensions, and exact treated them as a success because comparisons with NaN are false. Reject malformed aspects and fall back to the default ratio and tier instead of propagating NaN.
@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Oct 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The fix works. I sized every aspect from 1:1 to 40:40 at 0.5K–4K against the gpt-image-2 limits. On base, 1,040 of those sizes pass the 3:1 ratio (for example 1:3 at 1.5K gives 880x2656). With this PR none break any limit. The 0:0 and 0K inputs now give 1024x1024 instead of NaN. The packages/ai tests pass. tsc --noEmit passes; the typecheck script itself fails on base too because a check script it calls doesn't exist.

Public API: unchanged.

One suggestion, inline: Media.AspectRatio accepts 0:0 and 1:0, but only the exact() path now rejects them. The OpenAI and Meta paths call fit() directly, so they quietly send 1024x1024 instead. Rejecting zero sides in the schema would cover every provider at once.

return Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0
}

// A malformed shape degrades to the default rather than producing NaN; `exact` rejects it before sending.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This fallback means a request with aspect: "0:0" or "1:0" quietly turns into 1024x1024 for OpenAI (openai-images.ts calls fit(), not exact()) and for Meta. Only the exact() providers report an error. The schema allows these values today because Media.AspectRatio in packages/ai/src/media.ts uses /^\d+(?:\.\d+)?:\d+(?:\.\d+)?$/. Rejecting zero sides there would fail every provider the same way, including xAI, Google and Runway, which pass aspect through unchanged. You could then drop the validAspect checks here.

@cyzroot-jpg cyzroot-jpg Oct 27, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 1a4f250. Media.AspectRatio now requires both sides to be positive, so OpenAI, Meta and the pass-through providers all reject 0:0 / 1:0 at the request boundary. I dropped the duplicate aspect checks from the sizing helper; tier() still treats 0K as unknown so exact() fails cleanly.

Validate positive sides in Media.AspectRatio so every provider rejects 0:0 and 1:0 at the request boundary instead of only the routes that call ImageSizing.exact(). Drop the duplicate aspect checks from the sizing helper.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The new commit addresses my earlier comment: Media.AspectRatio now rejects zero-sided aspects for every provider, and the validAspect fallback is gone. I re-ran the #59536 reproduction. On base, 1:3 at 1.5K gives 880x2656, 0K gives NaN, and the schema accepts 0:0 and 1:0. With this PR it gives 896x2656, 0K gives 1024x1024, and the schema rejects both. The packages/ai tests and tsc --noEmit pass.

Public API: Media.AspectRatio (used by ImageRequest.aspect and VideoRequest.aspect) now rejects aspects with a zero side, such as 0:0 or 1:0. Only requests that previously produced NaN sizes are affected.

I have no further concerns.

This branch has not been deployed

No deployments
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.

ai: image sizing can exceed maxRatio or produce NaN

1 participant