Generation — OpenAI
The OpenAI-shaped engine talks to the /v1/images/generations and /v1/images/edits endpoints over a raw fetch call — no openai SDK dependency. It is the domain's shared-option owner: gemini.md and transformers_js.md both extend the option shapes defined here.
import { OpenAIGenerationAdapter } from '@nhtio/adk/batteries/generation/openai'
const generator = new OpenAIGenerationAdapter({
model: 'gpt-image-1',
apiKey: process.env.OPENAI_API_KEY,
})
const [image] = await generator.generate('A watercolor illustration of a lighthouse at dawn')Options
| Option | Type | Default | Notes |
|---|---|---|---|
model | string | (required) | No default anywhere in this domain — always the caller's job. |
apiKey | string | — | Sent as Authorization: Bearer {apiKey}. |
baseURL | string | https://api.openai.com/v1 | Injectable — point it at a gateway/LB. |
headers | Record<string, string> | — | Merged over the default headers; can override Authorization entirely. |
fetch | typeof fetch | global fetch | Swap in a custom transport for tests or non-standard runtimes. |
responseFormatMode | 'auto' | 'send' | 'omit' | 'auto' | See below. |
size | string | — | Passed through verbatim, e.g. '1024x1024'. |
quality | 'low' | 'medium' | 'high' | 'auto' | — | Constructor validation rejects any other value. |
background | 'transparent' | 'opaque' | 'auto' | — | gpt-image-* transparency support. |
outputFormat | 'png' | 'jpeg' | 'webp' | 'png'¹ | Sets the request's output_format (generate) and the returned MIME type + filename extension (both). |
n | number | — | Per-call (generate/edit) — number of images. Sent whenever defined. |
¹ outputFormat has no schema default (it's optional at construction), but the adapter resolves it to 'png' at call time when neither the per-call option nor the constructor option is set. | requestTimeoutMs | number | 0 (disabled) | Aborts in-flight requests past this many ms. | | retry.maxAttempts | number | 1 | Total attempts, including the first. | | retry.baseDelayMs | number | 500 | Base of the exponential backoff. | | retry.maxDelayMs | number | 30000 | Backoff ceiling. | | retry.retriableStatuses | number[] | [429, 500, 502, 503, 504] | Statuses that trigger a retry. | | retry.honorRetryAfter | boolean | true | Honor a Retry-After response header over the computed backoff. |
responseFormatMode — why it exists
OpenAI's own image API is split across two model families with incompatible response_format handling: dall-e-* models accept (and, historically, sometimes require) an explicit response_format: 'b64_json'; gpt-image-* models reject the parameter outright and always return base64 JSON regardless. 'auto' (the default) resolves this by inspecting model:
// conceptually:
const responseFormat =
responseFormatMode === 'send' ? 'b64_json'
: responseFormatMode === 'omit' ? undefined
: model.startsWith('dall-e') ? 'b64_json' : undefined // 'auto'Use 'send'/'omit' to force the behavior explicitly — for example, if you're routing through a gateway whose model-name prefix doesn't match either OpenAI family.
generate()
const outputs = await generator.generate('A neon-lit alley in the rain', {
size: '1024x1024',
quality: 'high',
})POST {baseURL}/images/generations with a JSON body built from the prompt plus any of size/quality/ background/outputFormat/the resolved response_format. The response's data[].b64_json entries are decoded to Uint8Array and returned as GeneratedMediaOutput[]; a response with no data array, an empty data array, or a data entry missing b64_json throws E_OPENAI_GENERATION_MALFORMED_RESPONSE.
edit() — multipart, not JSON
const outputs = await generator.edit(
[{ bytes: pngBytes, mimeType: 'image/png' }],
'Add a red bicycle leaning against the wall',
{ size: '1024x1024' }
)edit() accepts one or more GenerationImageInput values (Uint8Array, { bytes, mimeType? }, or anything Media-shaped — see _shared) and posts them as multipart/form-data:
- Each image is appended under the field name
image[](EDIT_IMAGE_FIELD_NAME), OpenAI's actual probe-confirmed field for multi-image edit requests. mask?,size?,quality?(theOpenAIEditOptionsfields) are appended as additional form fields when present.- No
Content-Typeheader is set manually.fetchcomputes the multipart boundary itself when given aFormDatabody; settingContent-Typeby hand breaks the boundary and the request fails silently server-side. If you write a customfetchoverride, do not add this header.
Gateway-dependent
Not every gateway implements OpenAI-shaped image edits. The domain's own live cross-spec found the LB gateway's /v1/images/edits route returns 404 even though /v1/images/generations works — i.e. edit() is verified directly against OpenAI, not (yet) through every gateway topology. If your deployment routes through a gateway, verify edit() against it before depending on it; the Gemini engine is confirmed to support edit() through the same LB.
Gateway routing
baseURL and headers together let you point this engine at any OpenAI-compatible gateway/LB without touching the calling code — the same polyglot-LB pattern used across the LLM chat-completions batteries:
const generator = new OpenAIGenerationAdapter({
model: 'gemini-2.5-flash-image', // gateway translates the model name
baseURL: process.env.LB_BASE_URL,
headers: { Authorization: `Bearer ${process.env.LB_TOKEN}` },
})Exceptions
| Exception | Status | Fatal? | Thrown when |
|---|---|---|---|
E_INVALID_OPENAI_GENERATION_OPTIONS | 529 | Yes | Adapter options fail openaiGenerationOptionsSchema validation (missing model, unknown key, etc.). |
E_OPENAI_GENERATION_HTTP_ERROR | 502 | No | The upstream HTTP call fails and retries are exhausted (or the status isn't retriable). Detail: [status, body]. |
E_OPENAI_GENERATION_REQUEST_TIMEOUT | 504 | No | requestTimeoutMs elapses before the request settles. Detail: [requestTimeoutMs]. |
E_OPENAI_GENERATION_MALFORMED_RESPONSE | 502 | No | A 2xx response has no usable data[].b64_json entries. Detail: [reason]. |
Where to go next
- Gemini — the engine to reach for when you need
edit()through a gateway this one 404s on. - Recipes — wiring
generate()/edit()into a realTool. - Assembly → Generation batteries — all three engines' option tables side by side.