### 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`
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
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
Navigation path
Options (whitelist — enforced server-side)

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.

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.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_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.