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.
+ chatgpt-image-gen
+
+ 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 existingMediaGallery lightbox. Download button top-right. Full-resolution view confirms the generated asset is what reached the client — no placeholder, no degraded render.
+
+ 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. Defaultgpt-image-2, legacy gpt-image-1.5, nothing else.
+
+ | Label | Value | When to pick |
|---|---|---|
| Default · gpt-image-2 | gpt-image-2 | Quality baseline. The motivation of this PR. Recommended for everyone. |
| Legacy · gpt-image-1.5 | gpt-image-1.5 | Only if a pool account lacks gpt-image-2 entitlement or you're cost-tuning. |
| Anything else | — | Rejected by ValidateImageModel with unsupported image model "…"; allowed: gpt-image-2 (default), gpt-image-1.5 (legacy). Prevents silent upstream 400s. |
params.image_model in the create_image tool settings JSON. At runtime: create_image.callProvider reads entry.Params["image_model"] → NativeImageRequest.ImageModel → ValidateImageModel (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. +| Area | Before | After |
|---|---|---|
Routing create_image → openai-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_imageis routed throughNativeImageProvider;create_video/create_audiostill use thecredentialProviderpath.
+ - OpenAI-compat track (non-Codex providers sending
message.images[]) is wired but untested against a live OpenAI-compat image endpoint — forward-compat infrastructure only.
+