PR #1002 · Native image_generation — UX trace

Captured against a live backend running the PR binary · agent chatgpt-image-gen (provider cliproxy-codex, model gpt-5.4) · image model gpt-image-2 · real ChatGPT OAuth · real Postgres · session 23cb257e.

What this shows

Real end-to-end run of the unified create_image → NativeImageProvider → Codex pathway against a complex Vietnamese infographic prompt. Tool completes with Done, image renders inline with the prompt as caption, and the image model is user-configurable from the existing Chain dialog.

Why it matters

Before this PR, routing create_image to openai-codex failed with provider "openai-codex" does not expose API credentials required for image generation. The new NativeImageProvider interface bridges OAuth-backed providers without exposing static keys — and locks in gpt-image-2 as the quality default.

Review cue

The generated PNG is intentionally compact (thumbnail + expanded view) rather than a raw file attachment, so the PR itself stays small. The captures are here to show the surface, not to ship the asset.

1 · Inline result · create_image returns Done, image + prompt caption render

Tail of the Vietnamese Red Fox encyclopedia prompt, then the tool-result row, then the assistant reply with image and caption.
Implemented / chat / chatgpt-image-gen
Chat view showing the end of the user's Vietnamese infographic prompt, then create_image Done, then the assistant's Vietnamese reply with the generated infographic rendered inline and the prompt as an italic muted caption beneath it.
What this demonstrates The user's infographic prompt flows through to a create_image call that completes with Done. The assistant acknowledges in Vietnamese and the generated image renders inline via MediaGallery. Beneath the image, in muted italic, is the prompt caption — one of the two new UX surfaces this PR adds. The prompt is also embedded into the PNG's tEXt chunk on write, so downloaded files carry their own provenance. Backend trace Log sequence: tool call create_image args_len=N → (4–8 min of work upstream) → create_image: file saved path=/app/workspace/…/generated/…/cao-do-infographic-vietnamese_….png size=…v3.run.completed.

2 · Expanded view · click the image, MediaGallery lightbox

Clicking the inline image opens the existing MediaGallery lightbox. Download button top-right. Full-resolution view confirms the generated asset is what reached the client — no placeholder, no degraded render.
Implemented MediaGallery · lightbox overlay
Lightbox overlay showing the full vertical Red Fox encyclopedia infographic in Vietnamese, with download button top-right, image filename visible bottom-center
What this proves The full 1024×1792 PNG, streamed out of the native Codex Responses API, persisted to the workspace, surfaced through the existing MediaGallery render path with no new plumbing — that's the value of routing through the pre-existing create_image tool rather than inventing a new rail. Download UX Filename is cao-do-infographic-vietnamese_YYYYMMDD-HHmmss_hash.png — resolved by the tool's filename_hint arg + timestamp, not a random UUID. Discoverable in the assistant's workspace at {workspace}/media/{sha256}.{ext} (deduped on hash) and also under the tool's generated/YYYY-MM-DD/ folder.

3 · Where to configure the image model

Whitelist select in the existing "Create Image — Provider Chain" dialog. Default gpt-image-2, legacy gpt-image-1.5, nothing else.
Configurable Built-in Tools → create_image → Provider Chain
Create Image Provider Chain modal with openai-codex row expanded. Model GPT-5.4, Timeout 600s, Retries 1. Settings panel open. Image model dropdown expanded showing Default · gpt-image-2 (recommended) selected, and Legacy · gpt-image-1.5 as the second option.
Navigation path Options (whitelist — enforced server-side)
LabelValueWhen to pick
Default · gpt-image-2gpt-image-2Quality baseline. The motivation of this PR. Recommended for everyone.
Legacy · gpt-image-1.5gpt-image-1.5Only if a pool account lacks gpt-image-2 entitlement or you're cost-tuning.
Anything elseRejected by ValidateImageModel with unsupported image model "…"; allowed: gpt-image-2 (default), gpt-image-1.5 (legacy). Prevents silent upstream 400s.
Where it's stored · how it's threaded Per chain entry, under params.image_model in the create_image tool settings JSON. At runtime: create_image.callProvider reads entry.Params["image_model"]NativeImageRequest.ImageModelValidateImageModel (defaults empty to gpt-image-2) → outbound tools[0].model on POST /codex/responses. Why surface this at all Most operators never need to touch it. It exists so that when Codex eventually rotates image models, or when an account's entitlement differs, the fallback is a clean UI toggle rather than a code change. The whitelist keeps the selector honest — no arbitrary strings, no silent upstream rejections. Also visible in this screenshot Timeout: 600s and Retries: 1 — the new defaults for this chain entry. Image generation of complex prompts legitimately runs 4–8 minutes; the old default of 120s × 2 retries routinely timed out mid-flight with context deadline exceeded. See §4.

4 · What changed — honest summary

Before/after, no hand-waving.
AreaBeforeAfter
Routing create_imageopenai-codex Failed. credentialProvider required static APIKey / APIBase; OAuth providers don't satisfy it. Works. New NativeImageProvider.GenerateImage. CodexProvider implements it via POST /codex/responses with the native image_generation tool.
Responses API wire format stream:true (API rejects false), instructions populated (API rejects missing), tool_choice forces image_generation. SSE stream parsed for response.output_item.done image items and response.completed output walk.
Image model selection Hardcoded literal. Whitelisted: gpt-image-2 (default) + gpt-image-1.5 (legacy). Selector in the Chain dialog. Server validator rejects anything else.
Default chain timeout 120s × 2 retries · image gen routinely died with context deadline exceeded while upstream was still generating. 600s × 1 retry · matches realistic gpt-image-2 completion time. Retries reduced to 1 — stateful upstream runs don't benefit from retry.
Assistant images in UI Rendered inline, no provenance. Prompt caption beneath image (muted italic, line-clamp-2, full text in tooltip). Prompt also embedded in PNG tEXt chunk so downloaded files carry provenance.
Per-request user toggle Added in earlier commits of this PR branch. Removed. Users toggling it off then forgetting = support footgun. Emergency admin kill-switch still exists via AgentConfig.AllowImageGeneration (stored in other_config).
CI drift (unrelated — fixed in-PR) sessions.compact unclassified → RBAC drift test fail. contains() declared twice in tests/integration → compile fail. Classified, deduped. Green.

Out of scope / follow-ups

Honest gap log.
  • Desktop (Wails) surface — UI changes live only in ui/web/. Desktop shell unchanged.
  • Video / audio generation chains — only create_image is routed through NativeImageProvider; create_video / create_audio still use the credentialProvider path.
  • OpenAI-compat track (non-Codex providers sending message.images[]) is wired but untested against a live OpenAI-compat image endpoint — forward-compat infrastructure only.