---
name: openai-compatible-imagegen
description: Generate, edit, annotate, and deliver images in Codex App through the configured API Key or ChatGPT subscription route, and open a focused canvas from a concrete image result. Use for generation, reference edits, mask edits, transparent delivery, batches, version inspection, exact sizing, grid splitting, preview boards, deterministic QA, and continued work on historical images.
---

# OpenAI-Compatible Images

The conversation is the primary entry point for image generation and iterative edits. Open the focused canvas only from a concrete image result to inspect the image and express edit intent. Never expose credentials, Authorization headers, or local absolute paths in prompts.

## Project binding

Before calling any project-scoped tool, call `bind_imagegen_project` with the current Codex task's project root as `projectRoot`. Preserve the returned `projectBindingId` and pass it unchanged to every subsequent project tool in this task. The project root must come from the current task workspace, never from the Plugin installation directory, MCP `cwd`, roots, Git discovery, or other local state.

The first bind without an ID issues a new random binding, so do not repeat an initial bind in the same task. After configuration changes, rebind with the existing `projectBindingId` and the same `projectRoot`; rebinding the same project is idempotent and refreshes the configuration digest, while changing roots conflicts. MCP persists only a domain-separated digest of the binding ID, not the raw ID. Never substitute, recover, or guess a binding ID from the transport `sessionId`, roots, MCP `cwd`, a recent project, or other local state.

A `projectBindingId` survives MCP process and server restarts. Stop the current operation on `project_binding_required` or `project_binding_invalid`; do not scan old state or guess another ID. If a fresh start is required, create a new isolated binding only from the current task project root and continue with the new ID. Old canvas and submission state does not migrate automatically. App-only tools obtain the same ID from standard `tool-input.arguments.projectBindingId`, not private host fields.

## Authentication routes

Read the binding receipt before the first image operation and keep its `defaultAuthMode` for the task. `apikey` uses the existing API image tools and supports generation, editing, batches, delivery, and canvas submissions. `chatgpt` uses the Codex host image handoff for generation and canvas edits. Both routes accept canvas mask annotations as edit guidance. For generation, call `prepare_host_image_import` with `intent: "generate"`, invoke the host image generation capability once, pass the exact returned local `savedPath` to `stage_host_image_import`, then call `finalize_host_image_import` and deliver or render the committed artifact. For a canvas edit, read the latest submission context, call `prepare_host_image_import` with `intent: "edit"`, the unchanged `parentImageId`, `annotationId`, and `submissionId`, and the complete edit request. The tool returns the clean parent image as model-visible content. Use that image together with the current canvas intent to invoke the host image generation capability once, then stage and finalize the returned output. The committed result is a child version linked to the parent and submission. Each handoff accepts only the image produced by the current host call; do not search session JSONL, caches, or historical output directories.

The ChatGPT host handoff processes one image per operation. API Key tools provide `count` and independent batch tasks. Keep the selected route and do not change to another route automatically.

## Platform runtime

The Plugin supports Windows, macOS, and Linux from one archive. Its Python bridge selects `python` on Windows and `python3` on macOS/Linux and requires Python 3.12 or newer. To choose one explicit executable, set `OPENAI_COMPATIBLE_IMAGEGEN_PYTHON`. An invalid override or failed preflight stops the operation; never probe another command or silently switch runtimes.

Repository safety uses a platform adapter behind `scripts/repository_fs.py`: Windows uses the Windows adapter, while macOS/Linux use the POSIX adapter. This adapter is Plugin-only and is not part of the Standalone Skill. macOS/Linux do not provide **Show in folder**; this does not block image generation, editing, artifact reads, annotations, or canvas work.

## Routing

Use the Plugin MCP tools for image work, including when another skill delegates generation or expects local input/output files. Do not scan the installation or execute bundled Python files to find an alternative generation entry point. `dist/` is the server's private runtime payload; `scripts/imagegen.py` belongs to the separately installed Standalone Skill and uses a different configuration contract. The explicit migration command below is the only documented direct script operation in this Plugin.

If a required tool or input capability is unavailable, report that specific limitation and stop the dependent operation. Do not create `auth.json`, reinterpret a CLI failure as missing Plugin configuration, or switch to Standalone or host generation. Follow the binding's authentication route; a user-approved route change remains subject to that route's Plugin handoff and result-display contract. Pass these entry-point constraints to delegated image workers.

1. For API Key generation, call `generate_image` once with a new `submissionKey`, then follow **Asynchronous image jobs** below. For ChatGPT generation, use the `prepare_host_image_import -> host image generation -> stage_host_image_import -> finalize_host_image_import` sequence. For multiple API candidates, pass `count` in that same call. The runtime executes the same number of ordered single-image requests and publishes the ordered group only when all succeed; any failure aborts the group without storing partial candidates. Do not retry or split the group into multiple calls. For transparent delivery, pass top-level `transparency`, never `background=transparent`; the runtime resolves the route, enhances the prompt once before the API request, and preserves PNG originals. After ordinary generation or host import succeeds, call `render_image_results` with the returned IDs before the final response. For transparent generation, call `deliver_image` once per original, then render the successful derivatives together. Do not wait for the user to request display. Submission tools return job receipts, not image bytes.
2. When the user wants to inspect or annotate an image, use **Open canvas** on its result card. Do not proactively call `open_image_editor` after a concrete result is already displayed; that tool is reserved for the result widget.
3. To edit an image with API Key, call `edit_image` with the parent image ID and a new `submissionKey`, then follow **Asynchronous image jobs**. Preserve the canvas `submissionId` independently from this job submission key. For ChatGPT canvas edits, follow the Canvas submission sequence below and use `prepare_host_image_import` with `intent: "edit"`; do not call `edit_image`. For transparent delivery, also pass top-level `transparency` on API Key edits and call `deliver_image` once after success. For an ordinary edit or host import, call `render_image_results` with the returned child IDs before the final response. Do not call `get_image_artifact` only to display them.
4. For API Key requests containing multiple independent generation or ordinary edit tasks with different parameters, call `batch_images` once with a new `submissionKey`. For transparency or other local delivery, pass both `transparency` and `delivery` on that item. Follow **Asynchronous image jobs** to read ordered `items[].result` values: API originals are in `artifacts`, and per-image delivery state and derivatives are in optional `delivery.results`. Prefer each source's derivatives with `deliveryReady=true`; otherwise render its successful original and report delivery state. Render all final results in groups of up to 10 IDs before the final response, without redisplaying IDs shown earlier. Report item failures without retrying generation. Preserve `manifest.batchId` when `manifest.manifestReady=true`; a missing manifest does not reverse successful publication. Mask or canvas submissions remain one separate selected-route edit operation and never enter a batch.
5. After obtaining a stable image ID, call `deliver_image` once for transparency, exact dimensions, `contain` or safe margins, grid splitting, preview boards, or deterministic QA. Consume a saved transparency plan when the original has one; pass `delivery.transparency` only to specify or adjust the route for a historical image. When `deliveryReady=true`, call `render_image_results` once with the derivative IDs before the final response. If there is no successful derivative, report QA and render the preserved original unless it has already been shown. A delivery failure must not leave a successful generated original undisplayed.
6. Use `get_image_artifact` to read an artifact. It returns data without creating a result card. Call `render_image_results` after reading only when the user needs to view a historical image and no result entry already exists.
7. Use `get_image_batch_manifest` with `batchId` to inspect a batch. It reads an immutable record and does not display images. To display a historical image from it, read by stable image ID and call `render_image_results`.
8. Use `get_image_delivery_receipt` with `deliveryReceiptId` to inspect local delivery or QA. It reads an immutable receipt and does not display images. To display a derivative, render the stable image ID recorded in the receipt.
9. Use `list_image_models` to inspect model capabilities.
10. On an explicit canvas submission, the widget calls `prepare_image_edit_submission` once to save annotations and obtain a server-issued `submissionId`. Never call it for unsubmitted annotations. `save_image_annotations` is not part of this atomic path.

## Asynchronous image jobs

API Key `generate_image`, `edit_image`, and `batch_images` persist a job and return `jobId` before image work completes. Every intended submission needs a `submissionKey` (1–128 ASCII letters, digits, dots, underscores, or hyphens, starting with a letter or digit). Choose and retain it before the call. The same key and request in the same project/artifact repository return the same job, including after a lost reply or MCP restart. A changed request with the same key returns `image_job_conflict`; do not invent another key to retry an uncertain generation. A new key is for a new user-authorized generation intent.

1. Preserve `projectBindingId`, `submissionKey`, and `jobId`. If the submission reply was lost, repeat only the identical submission with its original key to find the job. This lookup does not repeat generation. Once `jobId` is known, use status queries.
2. Call `get_image_job` until `done=true`. For a bounded wait, pass the last `revision` as `afterRevision` and `waitMs=20000`; the query waits at most 20 seconds. A query timeout or cancellation stops only that wait. Query the same job again; do not split or resubmit it. Continue working until the requested results are delivered, or a real interrupted/unknown state requires action; returning `jobId` alone is not task completion.
3. Each response contains a page of up to 10 items in input order, with `nextOffset` for the next page. An offset is an input index, not an event cursor: revisit unfinished items after later revisions. Read every final page, render each successful final image once in calls of up to 10 IDs, and preserve original/derivative relationships. Do not treat the first page or first 10 images as the whole batch.
4. `summary` separates queued, running, succeeded, failed, unknown, cancelled, and localFailed items. `items[].artifactIds` may contain saved originals even when local processing or presentation failed. `unknown` means the provider outcome was not confirmed; it never authorizes automatic regeneration. A failure to return image metadata does not prove no image was produced or billed.
5. On explicit user cancellation, call `cancel_image_job`. It cancels queued items and preserves images. A running item, including an atomic candidate group, can still finish and incur charges. Cancelling a wait does not cancel the job.
6. After `status=interrupted` or `localFailed>0`, `resume_image_job` can continue never-dispatched items and local processing with saved originals. Pass the current `revision` as `expectedRevision`, and reuse that value if the resume reply is lost. It never repeats failed or unknown image requests. If the user cancelled the task, resume only when they ask to continue. Recovery requires the original configuration; do not change models, providers, routes, or authentication to resume it.

Execution belongs to the MCP server process; no persistent OS service is installed. Jobs, checkpoints, and results survive a server restart, but an in-flight request without a saved checkpoint becomes unknown after its execution lease expires (up to 60 seconds). A process crash between image publication and checkpoint persistence can leave an image without a confirmed job result; do not regenerate it automatically. The executor shares eight active item slots across its jobs and honors each batch's smaller concurrency limit. Separate MCP processes do not share that slot limit, but durable ownership prevents them from executing the same job concurrently. Do not create extra processes or submissions to bypass concurrency.

## Local image files

For an existing project image, call `import_local_image` with its project-relative `sourcePath` (forward slashes). It returns `artifact.id`, preserves the original bytes, and creates an independent immutable artifact without a provider request. Import works with either configured authentication route.

For API Key edits, pass the imported ID as `edit_image.parentImageId`; import additional references separately and pass their IDs in `referenceImageIds`. Check that the selected profile supports `edit` and, when needed, `multi_reference`. The Atlas protocol does not support edits. For ChatGPT edits, call `render_image_results` with the imported ID so the user can open its canvas and submit the edit; continue through the Canvas submission sequence only after that submission. Never substitute a host handoff for importing a local file.

When another skill or script needs a file, call `export_image_artifact` with the selected stable `imageId` and a project-relative `destinationPath`, such as `decoded/idle.png`. Use the returned relative path under the bound project root for subsequent local processing. Export copies the original bytes, creates missing parent directories, and returns byte length and SHA-256. It never overwrites files or transcodes formats: match the filename extension to the artifact MIME type. Export the selected successful delivery derivative when the workflow needs a resized or transparent result.

Both paths must stay inside the bound project and outside its artifact repository. Absolute paths, traversal, symbolic links, junctions, and other reparse points are rejected. Imports are limited to 64 MiB and 100 million pixels. For files outside the project, explain this boundary instead of scanning or switching entry points. Do not use exported files to overwrite managed artifact storage. Import/export do not replace the required result rendering after generation, editing, or delivery. Apart from the ChatGPT canvas entry described above, render an imported reference only when the user needs to inspect it.

## Batch tasks

`batch_images` accepts 1 to 64 independent items with unique `requestId` values. Each `count` is 1 to 16, the sum of all counts is at most 64, and concurrency is 1 to 8. Omitted concurrency uses the configured default. Each item is an ordinary `generate` or `edit`. An OpenAI-compatible advanced batch item with `count=N` uses one provider request with `n=N`; Atlas collects one response per candidate before group publication. Same-prompt conversation candidates still use one `generate_image(count=N)`, preserving N independent single-image requests and atomic group publication; never replace that route with advanced batch.

Job item results preserve input order and allow partial success per task and per returned image. `result.ok=true` means at least one deeply validated API original was published. `result.apiDelivery` records provider count, published count, and safe issue codes. Delivery success is `result.delivery.deliveryReady`. A delivery failure preserves originals and is reported as `local_failed`; it does not reclassify the result as generation failure or trigger model, endpoint, protocol, or API retries. Once `manifest.manifestReady=true`, use `manifest.batchId` to read the immutable manifest. A recording failure does not reverse image publication.

## Local delivery

Use `deliverySize` for exact dimensions; `fit=contain` with `safeMargin` to preserve aspect ratio and padding; `grid`, `expectedCount`, and per-cell `deliverySize` for a known sheet layout; `preview.sizes` and `preview.backgrounds` for multi-size and multi-background inspection; `qa=true` for technical checks; and `components=true` when connected-component metrics are needed.

`deliver_image` reads one stable source image. The original stays immutable. Successful resized images, grid cells, and preview boards are stored with `operation=derive`, separate stable IDs, and a `derivedFrom` relationship outside the edit-version tree. Pass derivative IDs to `render_image_results` only when `deliveryReady=true`. When false, report `qa` and `warnings`; do not invent derivatives, transcode automatically, change format or model, or retry. Current local transforms accept PNG sources only. Preserve complete originals in other formats and report delivery as not ready.

For transparent delivery, choose `transparency.route`: `chroma-matting` for ordinary isolated subjects; `emissive-alpha` for black-background glow, fire, or particles; `mask-alpha` with a stable `maskImageId` when a deliberate mask image exists. Choose `prompt-alpha` only when configured `prompt_only_allow` exactly matches model, operation, and size. Otherwise preserve the original prompt and inspect the API original's alpha instead of claiming success. Never guess or generate `maskImageId`, and never substitute a canvas edit mask for a transparency-delivery mask.

For API Key generation or editing with native transparency, use `transparency.default_route` or pass `transparency.route: "native-alpha"`. This requires `transparency.native.enabled=true` and the `openai-compatible` protocol; Atlas rejects native transparency. The runtime sends `background: "transparent"` and PNG output, with a prompt requiring a real alpha channel. `transparency.native.model_ids` is an optional user-maintained capability declaration, not a code whitelist; the provider decides whether the configured model supports the parameter. If the provider rejects the transparency parameter with a transparency-related HTTP 400/422, the default behavior is one retry with that parameter removed, using the same model, provider, endpoint, prompt, size, quality, references, and mask. The retry can be disabled with `transparency.native.retry_without_parameter=false`. A successful retry, or an opaque native response, enters the configured local `fallback_route` only when local processing is enabled; otherwise it inspects source alpha without changing pixels. Keep the API original immutable and retain validated derivatives when processing succeeds. Do not enable local fallback merely because API retry is enabled. These provider settings do not control ChatGPT host generation.

Transparency runs before resizing, grids, and previews. If it fails, stop later derivatives and report that the original remains usable. Every transparent result records whether the native parameter was sent, rejected, retried, the final route, API attempt count, warnings, and QA status. The final response must explain the route and any fallback. When `parameters.transparency.llm_assisted.enabled=true`, perform at most `max_attempts` local redelivery attempts, adjusting `options` or changing local routes only when the corresponding switches allow it.
Even if policy contains `allow_api_retry`, do not request the image API again through that setting. The native-parameter retry described above is the only configured API retry and is limited to one same-request fallback.

## Canvas submission

On a canvas submission message, read the latest model context fields: `projectBindingId`, `submissionId`, `imageId`, `annotationId`, `prompt`, `annotationCount`, `intents`, and `requestText`. When the task contains multiple canvas contexts, use the newest `submissionId` associated with the current user message and never merge earlier submissions. For the API Key route, pass `projectBindingId` unchanged to `edit_image`, pass `submissionId` unchanged as `edit_image.submissionId`, and pass `imageId` as `edit_image.parentImageId`. For the ChatGPT route, call `prepare_host_image_import` with `route: "chatgpt"`, `intent: "edit"`, `parentImageId: imageId`, `annotationId` (including `null`), and `submissionId`, then use the returned clean parent image with the current edit intent for the host image generation call. Do not call `prepare_image_edit_submission` or `save_image_annotations` again.

Combine the annotation preview, per-region instructions, and additional request into one prompt describing the complete target image, then call the selected edit route once. Mask annotations always remain part of that semantic edit request. For API Key edits, MCP sends the dedicated mask parameter only when the selected model profile declares `capabilities.mask: true`; otherwise it sends the normal edit with the same annotation and submission relationship. The ChatGPT route receives the semantic annotations and clean parent image as separate model-visible inputs. For protected content, name what must remain and what lighting or shading may adapt naturally to the scene. Treat adherence to marked regions as model-dependent. Relay only the user's target; never author, append, or override `MASK_GUARD_V2_BY_STRATEGY`. Stop if the image ID, `submissionId`, or edit intent is missing. Never guess an ID or switch to `generate_image` or another route.

After the selected edit route succeeds, display the new image, stable ID, and parent-child version relationship in the current conversation. The new result retains its **Open canvas** entry.

## Canvas lifecycle

Treat the `editorSessionId` returned by `open_image_editor` as the active canvas session for this task.

- Call `destroy_image_editor` when the user explicitly asks to destroy the canvas.
- Call it when the task has clearly moved to another objective and the current image will no longer be viewed, annotated, or edited.
- Do not destroy the canvas when the user only hides or closes the side panel, discusses another topic temporarily, waits for another generation, or may continue working on the image.
- Never guess an unknown active `editorSessionId` or call the destroy tool without it.

The canvas **Destroy canvas** button and `destroy_image_editor` share one lifecycle. Destruction ends all active canvas sessions for that image within the current project binding and permanently removes its reopen entry for that binding. The state survives MCP process restarts; other project bindings are unaffected, and image artifacts and version relationships remain. After destruction, do not call `open_image_editor` or `render_image_results` for the same image to restore the entry.

The active profile and model ID come from user configuration. Model capabilities come from the configuration declaration and provider response; do not infer them from a model name. After failure, do not switch model, endpoint, provider, or edit route.

## Configuration

The Plugin resolves configuration only from:

1. User configuration: `~/.codex/openai-compatible-imagegen/config.json`
2. Optional project overrides: `<project-root>/.codex/openai-compatible-imagegen/config.json`

The user configuration must exist and provides the trusted baseline with `config_version: 1`, `auth_mode`, defaults, post-processing, and storage. API Key configuration also declares an active profile, provider, user-selected model ID, and transparency policy; ChatGPT-only configuration may omit those API fields. The project file is validated independently before reading the user file. It may override only `defaults.size`, `defaults.quality`, `defaults.output_format`, and `storage.output_directory`. It cannot declare or indirectly change profiles, models, provider, endpoint, authentication, credential environment variables, timeout, concurrency, or route permissions. Invalid or excessive project configuration fails without being ignored or falling back, and before user credentials or network requests are accessed.

An optional user-level provider `proxy.url` routes generation, edit, and provider-returned image URL requests through one HTTP proxy. It must be a complete `http://` or `https://` URL without credentials, path, query, or fragment; SOCKS proxies are unsupported. Project configuration cannot declare or override it. Configuration tools and runtime errors may report that a proxy is configured but must not expose its URL. After a proxy change, rebind the project so the new configuration digest becomes effective.

Effective priority is explicit tool arguments, then allowlisted project overrides, user defaults, and built-in defaults. When user configuration is missing, stop image requests and use the configuration tools below to initialize the selected route; `references/config.example.json` is the manual setup alternative. Legacy `auth.json` and older Plugin configuration are never read, copied, merged, deleted, or overwritten automatically. Migration requires an explicit user command and never reveals API keys.

Use MCP configuration tools for the complete Plugin flow. Call `initialize_image_config` to create the fixed user template when missing; it always creates a `.gitignore` containing only `*` in the user configuration directory and, when given a project root, protects the project configuration directory without changing the project root `.gitignore`. Use `inspect_image_config` to read configuration and `update_image_config` to change it, which adds the same directory protection before writing. Prefer `api_key_env`. When the user explicitly requests local plaintext credentials, user-level `api_key` may be written but never returned. Project scope forbids credentials and remains limited to size, quality, output format, and `storage.output_directory`. Rebind the image project after updates.

### Explicit migration

Proceed only when the user explicitly requests migration and provides the source path and source type. Resolve `<plugin-root>` by going two levels above this `SKILL.md`; never infer it from MCP `cwd`, a project Git root, or another installation cache.

Run a redacted dry run for a legacy Standalone configuration.

Windows PowerShell:

```text
python "<plugin-root>/dist/scripts/migrate_image_config.py" --source "<legacy-config>" --source-kind standalone
```

macOS or Linux shell:

```bash
python3 "<plugin-root>/dist/scripts/migrate_image_config.py" --source "<legacy-config>" --source-kind standalone
```

For an older Plugin configuration, use the retained compatibility value `--source-kind development-plugin`. Add `--include-project-overrides --project-root "<project-root>"` to both dry-run and write only when the user explicitly wants allowlisted defaults and output directory written to the current project.

Report `sourceKind`, `sourceSha256`, `userTarget`, `projectTarget`, `readyToWrite`, and the redacted preview. After user confirmation, preserve the source path, source type, user directory, and project override arguments, then run the matching platform command.

Windows PowerShell:

```text
python "<plugin-root>/dist/scripts/migrate_image_config.py" --source "<legacy-config>" --source-kind standalone --write --expected-source-sha256 "<sourceSha256>"
```

macOS or Linux shell:

```bash
python3 "<plugin-root>/dist/scripts/migrate_image_config.py" --source "<legacy-config>" --source-kind standalone --write --expected-source-sha256 "<sourceSha256>"
```

If `readyToWrite=false` requests plaintext-key authorization, stop. Add `--allow-plaintext-api-key` only after the user separately approves plaintext-key migration. On digest mismatch, existing target, incompatible schema, or write failure, report the original migration error and stop. Do not change source, target, route, or authentication method. Preserve the source file after success; never delete or rename it automatically.

Optional `storage.output_directory` must be a safe project-relative path. When absent, use `<project-root>/output/imagegen/`. Reject the project root itself, paths outside the project, files, symbolic links, junctions, and other reparse points. Project binding creates or verifies a `.gitignore` containing only `*` in the artifact directory. Add a missing rule; stop without overwriting when the rule is wrong or the path unsafe. Binding freezes configuration; rebind the same project explicitly after changes.

The artifact root selects one active repository. Once an override is effective, images, versions, annotations, masks, submission recovery, and **Show in folder** read only that directory. Never scan, merge, migrate, or copy older artifacts from the default directory. After removing the override and rebinding, older artifacts in the default directory become available again.

Without provider `proxy.url`, image API requests and URL downloads retain the environment proxy behavior. With it, all three request types use the configured proxy. Set provider `url_download.proxy_mode` to `direct` only after explicit user approval; this overrides the configured or environment proxy only for provider-returned image URL downloads. Never change proxy settings or retry through another network route after TLS, proxy, or network failure.

## Results

`render_image_results` is the sole result-widget entry. It accepts one or more stable image IDs, returns model-visible image content and safe metadata in input order, and provides a separate **Open canvas** entry for each canvas that has not been destroyed. Successful generation, edit, batch, and delivery flows must call it automatically before the final response; the user should never need to ask for the generated image to be shown. Destroyed images remain visible, but their entry reads **Canvas destroyed** and cannot be used.

The result widget treats only standard `ui/notifications/tool-input.arguments.imageIds` from `render_image_results` as image identity. It never guesses from result text, private metadata, or the newest artifact. Each image is read once through App-only `read_image_artifact_data`. Stop that card on mismatched request ID, public artifact ID, private widget data ID, or MIME. Do not call `get_image_artifact` or switch read routes.

Return the result images, stable IDs, corresponding canvas entries, version relationships, and safe error summaries to the user. Editing creates a new version and never overwrites its parent. Images and annotations stay in the project artifact root resolved from the binding configuration. That local path never enters tool results, the widget, or model context.
