Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/openai-gpt-image-2-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-openai': patch
---

`openaiImage()` accepts `gpt-image-2.5-flare` and `gpt-image-2.5-sunburst`. Both take sizes `1024x1024`, `1536x1024`, `1024x1536` and `auto`, and `quality` adds `xhigh` and `max` to `low`, `medium`, `high` and `auto`. Before, both ids threw `Unknown image model` before any request was sent.
2 changes: 1 addition & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@
"label": "Image Generation",
"to": "media/image-generation",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-19"
"updatedAt": "2026-09-27"
},
{
"label": "Video Generation",
Expand Down
36 changes: 21 additions & 15 deletions docs/media/image-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,15 @@ All image adapters support these common options:

#### OpenAI Models

| Model | Supported Sizes |
| ------------------ | --------------------------------------------- |
| `gpt-image-2` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `gpt-image-1` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `gpt-image-1-mini` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `dall-e-3` | `1024x1024`, `1792x1024`, `1024x1792` |
| `dall-e-2` | `256x256`, `512x512`, `1024x1024` |
| Model | Supported Sizes |
| ------------------------ | --------------------------------------------- |
| `gpt-image-2.5-flare` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `gpt-image-2.5-sunburst` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `gpt-image-2` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `gpt-image-1` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `gpt-image-1-mini` | `1024x1024`, `1536x1024`, `1024x1536`, `auto` |
| `dall-e-3` | `1024x1024`, `1792x1024`, `1024x1792` |
| `dall-e-2` | `256x256`, `512x512`, `1024x1024` |

#### Gemini Native Models (NanoBanana)

Expand Down Expand Up @@ -363,7 +365,7 @@ await generateImage({

| Provider | Behavior |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **OpenAI** | `gpt-image-2` / `gpt-image-1` / `gpt-image-1-mini` → routes to `images.edit()`, up to 16 source images plus optional mask.<br>`dall-e-2` → `images.edit()` with 1 source image only.<br>`dall-e-3` → throws (no edit support). |
| **OpenAI** | `gpt-image-2.5-flare` / `gpt-image-2.5-sunburst` / `gpt-image-2` / `gpt-image-1` / `gpt-image-1-mini` → routes to `images.edit()`, up to 16 source images plus optional mask.<br>`dall-e-2` → `images.edit()` with 1 source image only.<br>`dall-e-3` → throws (no edit support). |
| **Gemini** | Native models (`gemini-*-flash-image`, "nano-banana", etc.) → prompt parts map 1:1 onto multimodal `contents`, preserving interleaved order. Up to ~14 input images (provider limit, not enforced by the SDK).<br>Imagen models → throws (text-to-image only). |
| **fal.ai** | Field names resolve per endpoint from a map generated from the fal SDK's endpoint types (e.g. nano-banana edit gets `image_urls`, Fooocus masks get `mask_image_url`). Defaults for unknown endpoints: 1 input → `image_url`; multiple → `image_urls`; `role: 'mask'` → `mask_url`; `role: 'control'` → `control_image_url`; `role: 'reference'` / `'character'` → `reference_image_urls`. Override with `modelOptions` for endpoint-specific fields. |
| **Grok** | grok-imagine models → xAI's `/v1/images/edits` (up to 3 source images, addressed by xAI in request order; prompt sent verbatim). `role: 'mask'` / `'control'` throw (no Imagine API equivalent). |
Expand Down Expand Up @@ -604,6 +606,8 @@ const result = await generateImage({
});
```

`gpt-image-2.5-flare` and `gpt-image-2.5-sunburst` also take `quality: "xhigh"` and `quality: "max"`.

##### DALL-E 3

```typescript
Expand Down Expand Up @@ -744,13 +748,15 @@ if (result.usage?.billed) {

#### OpenAI Models

| Model | Images per Request |
| ------------------ | ------------------ |
| `gpt-image-2` | 1-10 |
| `gpt-image-1` | 1-10 |
| `gpt-image-1-mini` | 1-10 |
| `dall-e-3` | 1 |
| `dall-e-2` | 1-10 |
| Model | Images per Request |
| ------------------------ | ------------------ |
| `gpt-image-2.5-flare` | 1-10 |
| `gpt-image-2.5-sunburst` | 1-10 |
| `gpt-image-2` | 1-10 |
| `gpt-image-1` | 1-10 |
| `gpt-image-1-mini` | 1-10 |
| `dall-e-3` | 1 |
| `dall-e-2` | 1-10 |

#### Gemini Native Models (NanoBanana)

Expand Down
21 changes: 13 additions & 8 deletions packages/ai-openai/src/adapters/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import type { OpenAIClientConfig } from '../utils/client'
// Per OpenAI docs: dall-e-2 accepts 1 image to `images.edit()`; the
// gpt-image models accept up to 16; dall-e-3 does not support edit at all.
const EDIT_MAX_IMAGES: Record<OpenAIImageModel, number> = {
'gpt-image-2.5-flare': 16,
'gpt-image-2.5-sunburst': 16,
'dall-e-2': 1,
'gpt-image-1': 16,
'gpt-image-1-mini': 16,
Expand All @@ -57,7 +59,8 @@ export interface OpenAIImageConfig extends OpenAIClientConfig {
* OpenAI Image Generation Adapter
*
* Tree-shakeable adapter for OpenAI image generation functionality.
* Supports gpt-image-2, gpt-image-1, gpt-image-1-mini, dall-e-3, and dall-e-2 models.
* Supports gpt-image-2.5-flare, gpt-image-2.5-sunburst, gpt-image-2,
* gpt-image-1, gpt-image-1-mini, dall-e-3, and dall-e-2 models.
*
* Features:
* - Model-specific type-safe provider options
Expand Down Expand Up @@ -123,12 +126,15 @@ export class OpenAIImageAdapter<

// With exactOptionalPropertyTypes, vendor SDK request shapes reject
// `T | undefined` in optional fields. Build the request incrementally and
// only set `size` when it's actually defined.
// only set `size` when it's actually defined. The SDK's `quality` type
// does not list the gpt-image-2.5 `xhigh` / `max` levels yet, so cast the
// spread.
const request: OpenAI_SDK.Images.ImageGenerateParams = {
model,
prompt,
n: numberOfImages ?? 1,
...(modelOptions ?? {}),
...((modelOptions ??
{}) as Partial<OpenAI_SDK.Images.ImageGenerateParamsNonStreaming>),
}
if (size !== undefined) {
request.size = size
Expand Down Expand Up @@ -192,10 +198,9 @@ export class OpenAIImageAdapter<

/**
* Image-conditioned generation via OpenAI's `images.edit()` endpoint.
* dall-e-2 accepts 1 input image; gpt-image-2 / gpt-image-1 /
* gpt-image-1-mini accept up to 16; dall-e-3 rejects entirely. A part with
* `metadata.role === 'mask'` is routed to the SDK's `mask` field (PNG with
* alpha channel).
* dall-e-2 accepts 1 input image; the gpt-image models accept up to 16;
* dall-e-3 rejects entirely. A part with `metadata.role === 'mask'` is
* routed to the SDK's `mask` field (PNG with alpha channel).
*/
private async editImages(args: {
model: OpenAIImageModel
Expand All @@ -211,7 +216,7 @@ export class OpenAIImageAdapter<
if (maxImages === 0) {
throw new Error(
`${this.name}: model "${model}" does not support image prompt parts. ` +
`Use gpt-image-2, gpt-image-1, gpt-image-1-mini, or dall-e-2 for image-conditioned generation.`,
`Use a gpt-image model or dall-e-2 for image-conditioned generation.`,
)
}

Expand Down
37 changes: 36 additions & 1 deletion packages/ai-openai/src/image/image-provider-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
*/
export type GptImageQuality = 'high' | 'medium' | 'low' | 'auto'

/**
* Quality options for gpt-image-2.5-flare and gpt-image-2.5-sunburst models
*/
export type GptImage25Quality = GptImageQuality | 'xhigh' | 'max'

/**
* Quality options for dall-e-3 model
*/
Expand Down Expand Up @@ -122,6 +127,21 @@ export interface GptImage1ProviderOptions extends OpenAIImageBaseProviderOptions
*/
export type GptImage1MiniProviderOptions = GptImage1ProviderOptions

/**
* Provider options for gpt-image-2.5-flare and gpt-image-2.5-sunburst models.
* Same as gpt-image-1, plus the `xhigh` and `max` quality levels.
*/
export interface GptImage25ProviderOptions extends Omit<
GptImage1ProviderOptions,
'quality'
> {
/**
* The quality of the image.
* @default 'auto'
*/
quality?: GptImage25Quality
}

/**
* Provider options for dall-e-3 model
* Field names match the OpenAI API for direct spreading
Expand Down Expand Up @@ -171,6 +191,7 @@ export interface DallE2ProviderOptions extends OpenAIImageBaseProviderOptions {
* Union of all OpenAI image provider options
*/
export type OpenAIImageProviderOptions =
| GptImage25ProviderOptions
| GptImage1ProviderOptions
| GptImage1MiniProviderOptions
| DallE3ProviderOptions
Expand All @@ -181,6 +202,8 @@ export type OpenAIImageProviderOptions =
* Used by the core AI types to narrow providerOptions based on the selected model.
*/
export type OpenAIImageModelProviderOptionsByName = {
'gpt-image-2.5-flare': GptImage25ProviderOptions
'gpt-image-2.5-sunburst': GptImage25ProviderOptions
'gpt-image-2': GptImage1ProviderOptions
'gpt-image-1': GptImage1ProviderOptions
'gpt-image-1-mini': GptImage1MiniProviderOptions
Expand All @@ -192,6 +215,8 @@ export type OpenAIImageModelProviderOptionsByName = {
* Type-only map from model name to its supported sizes.
*/
export type OpenAIImageModelSizeByName = {
'gpt-image-2.5-flare': GptImageSize
'gpt-image-2.5-sunburst': GptImageSize
'gpt-image-2': GptImageSize
'gpt-image-1': GptImageSize
'gpt-image-1-mini': GptImageSize
Expand All @@ -205,6 +230,8 @@ export type OpenAIImageModelSizeByName = {
* dall-e-3 has no edit endpoint, so its prompt is text-only at compile time.
*/
export type OpenAIImageModelInputModalitiesByName = {
'gpt-image-2.5-flare': readonly ['image']
'gpt-image-2.5-sunburst': readonly ['image']
'gpt-image-2': readonly ['image']
'gpt-image-1': readonly ['image']
'gpt-image-1-mini': readonly ['image']
Expand Down Expand Up @@ -232,6 +259,8 @@ export function validateImageSize(
if (!size || size === 'auto') return

const validSizes: Record<string, Array<string>> = {
'gpt-image-2.5-flare': ['1024x1024', '1536x1024', '1024x1536', 'auto'],
'gpt-image-2.5-sunburst': ['1024x1024', '1536x1024', '1024x1536', 'auto'],
'gpt-image-2': ['1024x1024', '1536x1024', '1024x1536', 'auto'],
'gpt-image-1': ['1024x1024', '1536x1024', '1024x1536', 'auto'],
'gpt-image-1-mini': ['1024x1024', '1536x1024', '1024x1536', 'auto'],
Expand Down Expand Up @@ -279,7 +308,13 @@ export function validateNumberOfImages(

export const validateBackground = (options: ImageValidationOptions) => {
if (options.background) {
const supportedModels = ['gpt-image-2', 'gpt-image-1', 'gpt-image-1-mini']
const supportedModels = [
'gpt-image-2.5-flare',
'gpt-image-2.5-sunburst',
'gpt-image-2',
'gpt-image-1',
'gpt-image-1-mini',
]
if (!supportedModels.includes(options.model)) {
throw new Error(
`The model ${options.model} does not support background option.`,
Expand Down
44 changes: 44 additions & 0 deletions packages/ai-openai/src/model-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,48 @@ const GPT_IMAGE_1_MINI = {
OpenAIBaseOptions & OpenAIStreamingOptions & OpenAIMetadataOptions
>

const GPT_IMAGE_2_5_FLARE = {
name: 'gpt-image-2.5-flare',
pricing: {
input: {
normal: 5,
cached: 1.25,
},
output: {
normal: 30,
},
},
supports: {
input: ['text', 'image'],
output: ['image'],
endpoints: ['image-generation', 'image-edit'],
features: [],
},
} as const satisfies ModelMeta<
OpenAIBaseOptions & OpenAIStreamingOptions & OpenAIMetadataOptions
>

const GPT_IMAGE_2_5_SUNBURST = {
name: 'gpt-image-2.5-sunburst',
pricing: {
input: {
normal: 5,
cached: 1.25,
},
output: {
normal: 30,
},
},
supports: {
input: ['text', 'image'],
output: ['image'],
endpoints: ['image-generation', 'image-edit'],
features: [],
},
} as const satisfies ModelMeta<
OpenAIBaseOptions & OpenAIStreamingOptions & OpenAIMetadataOptions
>

const GPT_IMAGE_2 = {
name: 'gpt-image-2',
knowledge_cutoff: '2026-04-21',
Expand Down Expand Up @@ -2715,6 +2757,8 @@ export function openAIModelRejectsSamplingParams(model: string): boolean {

// Image generation models (based on endpoints: "image-generation" or "image-edit")
export const OPENAI_IMAGE_MODELS = [
GPT_IMAGE_2_5_FLARE.name,
GPT_IMAGE_2_5_SUNBURST.name,
GPT_IMAGE_2.name,
GPT_IMAGE_1.name,
GPT_IMAGE_1_MINI.name,
Expand Down
31 changes: 31 additions & 0 deletions packages/ai-openai/tests/image-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,37 @@ describe('OpenAI Image Adapter', () => {
})
})

it.each(['gpt-image-2.5-flare', 'gpt-image-2.5-sunburst'] as const)(
'sends %s with a listed size and the xhigh quality',
async (model) => {
const adapter = new TestOpenAIImageAdapter(
{ apiKey: 'test-api-key' },
model,
)
const mockGenerate = adapter
.spyOnImagesGenerate()
.mockResolvedValueOnce({ created: 0, data: [{ b64_json: 'img' }] })

const result = await adapter.generateImages({
model,
prompt: 'A cat wearing a hat',
size: '1536x1024',
modelOptions: { quality: 'xhigh' },
logger: testLogger,
})

expect(mockGenerate).toHaveBeenCalledWith({
model,
prompt: 'A cat wearing a hat',
n: 1,
size: '1536x1024',
quality: 'xhigh',
stream: false,
})
expect(result.images[0]!.b64Json).toBe('img')
},
)

it('throws when the response contains no usable images', async () => {
const adapter = new TestOpenAIImageAdapter(
{ apiKey: 'test-api-key' },
Expand Down
Loading
Loading