GPT Image 2.5 Sunburst is wired to OpenAI's Images API as a new first-class
openai provider. Four cases were driven through the running
system — generate and edit, in Galleria and on the workflow canvas — and
verified against the database, the service logs, and the bytes actually stored.
Galleria and the workflow canvas are separate code paths to the same backend, with their own configs and defaults, so each was exercised independently. Every run was confirmed three ways: the outgoing request body, the database row reaching a terminal state, and the provider path in the service log.
| Case | Surface | Provider path called | Result |
|---|---|---|---|
| Generate | Galleria | /v1/images/generations | pass · 11.6s |
| Edit | Galleria | /v1/images/edits | pass · 12.6s |
| Generate | Workflow node | /v1/images/generations | pass |
| Edit | Workflow node | /v1/images/edits | pass |
| GPT Image 2 regression | Both | fal.run/openai/gpt-image-2 | unaffected |
queued. 8 of 8 Sunburst runs and
23 of 23 overall reached completed. This mattered because a
brand-new asynq task type was introduced, and a missing handler registration would
have hung every generation silently — the gateway still returning 201 and the
enqueue still returning 200.
quoted_credits
matching credits_amount.
Every value below was confirmed by sending it to the live API, not read from
documentation. size takes literal pixel dimensions rather than
preset names, which is what lets the interface quote real output dimensions on the
generate button.
| Wire key | Accepted values | Exposed in UI |
|---|---|---|
| size | auto, 1024x1024, 1536x1024, 1024x1536, custom WxH | 4 options |
| quality | auto, low, medium, high, xhigh, max | all 6 |
| background | auto, transparent, opaque | all 3 |
| output_format | png, jpeg, webp | all 3 |
| n | integer | 1–4 |
POST /api/generations → 201 (from a real Galleria run)
"config":{"size":"1024x1024","quality":"low","background":"opaque","output_format":"png","n":1}
xhigh and
max are both accepted and echoed back, and
size: auto works despite the error insisting on
WIDTHxHEIGHT. Trusting those messages would have stripped two
quality tiers and the Auto size out of the interface for no reason. Each value was
confirmed by using it.
size carries them directly.
This was the one genuinely novel piece of code. Every other provider client in the
repository posts JSON; OpenAI's edit endpoint takes
multipart/form-data with binary file parts, so the reference
images must be fetched to bytes and assembled into a form.
[OpenAI Image] model=gpt-image-2.5-sunburst edit=true images_in=1 [OpenAI] POST /images/edits model=gpt-image-2.5-sunburst images=1
background is one
of the parameters OpenAI rejects on edits, so the service drops it.
quality, background and
n, so the service filters them out. The interface still shows
those controls when references are attached; they are silently ignored.
background: transparent is the capability most likely to look
right and be wrong, because an alpha channel can exist while being opaque everywhere.
So the stored asset was decoded and its pixels counted, rather than trusting
file to report RGBA.
"background":"transparent" and the stored file is
RGBA, all four corners at alpha 0.
fully clear (a<16) 1,200,953 of 1,572,864 76.4% fully opaque (a>239) 364,119 partial (anti-aliased) 7,792 corner alpha [0, 0, 0, 0]
Unlike every other provider here, OpenAI returns no URL — only base64. That
makes the decode the highest-risk part of the change, and a failure would be close to
invisible: a wrong decode stores base64 text labelled
image/png, and the response, the byte count and the metadata all
still look healthy.
$ curl <generation_assets.public_url> -o stored.png $ file stored.png stored.png: PNG image data, 1024 x 1024, 8-bit/color RGB, non-interlaced $ xxd -l 8 stored.png 00000000: 8950 4e47 0d0a 1a0a .PNG....
[]byte(*b64Json) without decoding. It is dead code there
because that provider defaults to URLs, but it was the obvious thing to copy. The
OpenAI client decodes properly, with a RawStdEncoding fallback
and an empty-result guard.
1:1 HD preset, unaffected.
The Size control seeds correctly now; this shot predates that fix.
seed.sql is
development only, so production needs its own insert — a single row, since no
other model moves.
A fresh workflow node used to render an empty Size control. It now seeds correctly.
The node factory hardcoded imageSize: "square_hd" for every
model. That is a FAL preset name, so it matched no OpenAI option and the dropdown had
nothing to display — output was still correct, because the execute-time guard
clamped it, but the control looked broken until touched.
It now reads each model's own declared default instead of a fixed string. All nine
branches were enumerated to confirm the change is limited to one: every model that
declares no size setting still falls back to square_hd, and
gpt-image-2 keeps that value explicitly. Only Sunburst's seeded
value differs, and its wire value is unchanged — it was already arriving at
1024x1024, just via the clamp rather than the default.
Sunburst sorts last among active models, at sequence 25.
It initially shared sequence 5 with gpt-image-2, which made the
order between them depend on a name tiebreak rather than being specified. Moving it to
25 — past the highest active model at 24, before the legacy block at 90 —
makes the position explicit and ties with nothing, while still moving no other row.
gpt-image-2 first. Had that predicate used a
prefix or substring match rather than strict equality, it would have swallowed
gpt-image-2.5-sunburst and made the entire Sunburst branch dead
code — silently, and in a way the node settings would still have appeared to
survive. It uses strict equality, so both branches are live, but that was the one
route by which this could have been quietly wrong.
Stated plainly, because a verification report that implies more coverage than it has is worth less than one that names its own edges.
low and high in the running system. All six were confirmed against the live API directly, not through the app.n > 1.auto, and OpenAI drops
background on edits regardless — so this is arguably
interface behaviour to document rather than a gap, but it is not evidenced either way.
config keys record which provider actually ran a given
generation, so any per-provider count taken from the join will be wrong after a
provider change.
completed row, so error handling is unexercised.