Skip to content
Open
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
37 changes: 37 additions & 0 deletions .changeset/gemini-image-ga-model-ids-and-per-model-sizes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
'@tanstack/ai-gemini': minor
---

Add the GA Gemini native image model ids and give each native image model its own size type.

`gemini-3.1-flash-image-preview` and `gemini-3-pro-image-preview` were shut down on 2026-06-25 and now 404. Their GA replacements β€” `gemini-3.1-flash-image` and `gemini-3-pro-image` β€” are now the primary ids. The `-preview` ids remain in the model union as aliases so existing code keeps compiling; `gemini-2.5-flash-image` stays fully supported ahead of its 2026-10-02 shutdown.

Sizes were a single flat union (`{8 ratios}_{1K|2K|4K}`) applied to every native model. Google documents four different sets, so each model now maps to its own:

| model | aspect ratios | resolutions |
| --------------------------------------- | ------------- | -------------------------------- |
| `gemini-3.1-flash-image` (+ `-preview`) | 14 | `512` `1K` `2K` `4K` |
| `gemini-3.1-flash-lite-image` | 14 | `1K` |
| `gemini-3-pro-image` (+ `-preview`) | 10 | `1K` `2K` `4K` |
| `gemini-2.5-flash-image` | 10 | none β€” bare ratio, e.g. `'16:9'` |

`4:5` and `5:4` are now accepted on every native model (Google lists them for all four; the old union omitted them). `9:21` is deliberately still rejected β€” it exists on Vertex/Cloud only and the Gemini API rejects it.

**Runtime behaviour changes in two places.** The rest of the change is types-only, but these two are real wire-format deltas:

- `parseNativeImageSize()` now accepts a bare aspect ratio. Previously `'16:9'` failed to parse, so the adapter omitted `imageConfig` entirely and the model picked its own aspect ratio; it now parses to `{ aspectRatio: '16:9' }` and the adapter sends `imageConfig.aspectRatio = '16:9'`. A JavaScript caller β€” or a TypeScript caller whose `size` is computed at runtime and widened to `string` β€” that already passed a bare ratio will get a differently-framed image after upgrading, with no compile or runtime error.
- Migrating a `gemini-2.5-flash-image` call from `'16:9_1K'` to the now-required bare `'16:9'` drops `imageSize` from the `generateContent` request. That is intended: Google publishes no `image_size` value or default for this model, so the adapter no longer guesses a tier the API never documented.
Comment thread
L4Ph marked this conversation as resolved.

**BREAKING (types only):** size combinations the selected model never supported no longer compile. No model id was removed.

- `gemini-3.1-flash-lite-image`: `2K` and `4K` are rejected (the model only emits 1K). Use `'<ratio>_1K'`.
- `gemini-3-pro-image` / `gemini-3-pro-image-preview`: the extreme banner ratios `1:4` `4:1` `1:8` `8:1` are rejected (Gemini 3.1 Flash Image only), as is the `512` tier.
- `gemini-2.5-flash-image`: any `_1K` / `_2K` / `_4K` suffix is rejected β€” pass the bare ratio (`'16:9'`, not `'16:9_1K'`) β€” as are the four extreme banner ratios.
- `GeminiNativeImageSize` is now the union of the per-model types rather than one flat template literal. It was not previously reachable from the package entry point, so this is a new export rather than a changed one.

**New type exports**, so the per-model narrowing is nameable and not just inferred at the call site: `GeminiImageModelSizeByName`, `GeminiStandardImageAspectRatio`, `GeminiExtendedImageAspectRatio`, `Gemini31FlashImageSize`, `Gemini31FlashLiteImageSize`, `Gemini3ProImageSize`, `Gemini25FlashImageSize`, `GeminiNativeImageSize`.

**Two caveats worth knowing before you rely on this.**

- _No in-editor deprecation warning on the dead `-preview` ids._ The `@deprecated` tags live on module-private model-metadata consts, and `GeminiImageModels` is projected out of a const array (`(typeof GEMINI_IMAGE_MODELS)[number]`), which collapses to bare string literals β€” JSDoc does not survive that projection. So `geminiImage('gemini-3-pro-image-preview')` still compiles cleanly with no strikethrough and no hint, and fails only at request time. Grep your codebase for `-image-preview` rather than expecting the compiler to flag it.
- _`gemini-3.1-flash-lite-image`'s four extreme ratios (`1:4` `4:1` `1:8` `8:1`) are partially inferred._ Unlike the other three native models, Flash Lite has no per-model ratio table on the Gemini API guide. The 14-value set rests on the Cloud model page's explicit enumeration plus `ai.google.dev`'s bare "a discrete set of 14 aspect ratios" assertion; the only Gemini-API enumeration for this model is a 10-item bullet prefixed "New aspect ratios", read here as a what's-new list rather than an exhaustive set. If the API rejects those four in practice, this type over-accepts and should narrow to the 10-ratio set.
32 changes: 21 additions & 11 deletions docs/adapters/gemini.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ See the [Embeddings guide](../embeddings.md) for the full API.

The Gemini adapter supports two types of image generation:

- **Gemini native image models** (NanoBanana) β€” Use the `generateContent` API with models like `gemini-3.1-flash-image-preview`. These support extended resolution tiers (1K, 2K, 4K) and aspect ratio control.
- **Gemini native image models** (NanoBanana) β€” Use the `generateContent` API with models like `gemini-3.1-flash-image`. These support aspect ratio control plus resolution tiers (`512`, `1K`, `2K`, `4K`); which ratios and tiers are accepted varies per model and is enforced at compile time.
- **Imagen models** β€” Use the `generateImages` API with models like `imagen-4.0-generate-001`. These are dedicated image generation models with WIDTHxHEIGHT sizing.

The adapter automatically routes to the correct API based on the model name β€” models starting with `gemini-` use `generateContent`, while `imagen-` models use `generateImages`.
Expand All @@ -425,7 +425,7 @@ import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";

const result = await generateImage({
adapter: geminiImage("gemini-3.1-flash-image-preview"),
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: "A futuristic cityscape at sunset",
numberOfImages: 1,
size: "16:9_4K",
Expand Down Expand Up @@ -462,10 +462,18 @@ size: "1:1_2K"
size: "9:16_1K"
```

| Component | Values |
|-----------|--------|
| Aspect Ratio | `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `9:16`, `16:9`, `21:9` |
| Resolution | `1K`, `2K`, `4K` |
The accepted set differs per model, and each model's `size` is narrowed to its own set at compile time:

| Model | Aspect ratios | Resolutions |
|-------|---------------|-------------|
| `gemini-3.1-flash-image` | `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9`, `1:4`, `4:1`, `1:8`, `8:1` | `512`, `1K`, `2K`, `4K` |
| `gemini-3.1-flash-lite-image` | same 14 as above (see note) | `1K` only |
| `gemini-3-pro-image` | `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9` | `1K`, `2K`, `4K` |
| `gemini-2.5-flash-image` | same 10 as above | none β€” pass the bare ratio, e.g. `size: "16:9"` |

The `K` is case-sensitive (`1k` is rejected by the API), the smallest tier's token is `512` (not `512px` or `0.5K`), and `9:21` is Vertex/Cloud-only so it is not accepted here. Google documents no `image_size` for `gemini-2.5-flash-image`, so that model takes a bare aspect ratio and the adapter sends no `imageSize`.

> **Note on `gemini-3.1-flash-lite-image`.** The four extreme banner ratios (`1:4`, `4:1`, `1:8`, `8:1`) are partially inferred for this model. Unlike the other three, Flash Lite has no per-model ratio table in Google's Gemini API guide; the 14-value set comes from the Cloud model page's explicit enumeration plus the guide's bare "a discrete set of 14 aspect ratios" assertion. The only Gemini-API enumeration for it is a 10-item bullet headed "New aspect ratios", which we read as a what's-new list rather than an exhaustive one. If the API rejects those four in practice, prefer the 10 standard ratios on this model.

#### Imagen Models

Expand Down Expand Up @@ -547,14 +555,16 @@ GOOGLE_API_KEY=your-api-key-here

### Gemini Native Image Models (NanoBanana)

These models use the `generateContent` API and support resolution tiers (1K, 2K, 4K).
These models use the `generateContent` API and support per-model resolution tiers.

| Model | Description |
|-------|-------------|
| `gemini-3.1-flash-image-preview` | Latest and fastest Gemini native image generation |
| `gemini-3.1-flash-lite-image` | Nano Banana 2 Lite β€” ultra-low-latency, low-cost image generation |
| `gemini-3-pro-image-preview` | Higher quality Gemini native image generation |
| `gemini-2.5-flash-image` | Gemini 2.5 Flash with image generation |
| `gemini-3.1-flash-image` | Nano Banana 2 β€” latest and fastest Gemini native image generation (512/1K/2K/4K) |
| `gemini-3.1-flash-lite-image` | Nano Banana 2 Lite β€” ultra-low-latency, low-cost image generation (1K only) |
| `gemini-3-pro-image` | Nano Banana Pro β€” higher quality Gemini native image generation (1K/2K/4K) |
| `gemini-2.5-flash-image` | Nano Banana β€” legacy; shuts down 2026-10-02. Takes a bare aspect ratio |

The `gemini-3.1-flash-image-preview` and `gemini-3-pro-image-preview` ids were shut down on 2026-06-25. They remain in the type union as deprecated aliases so existing code compiles, but calls to them fail β€” use the GA ids above.

### Imagen Models

Expand Down
15 changes: 9 additions & 6 deletions docs/advanced/runtime-adapter-switching.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const Route = createFileRoute('/api/chat')({

## Using with Image Adapters

The same pattern works for image generation:
The same pattern works for image generation. Unlike the text and summarize adapters above, image adapters don't all accept the same shape of `size` β€” so it travels alongside its adapter in the provider map instead of being passed once for every branch:

```typescript
import { generateImage } from '@tanstack/ai'
Expand All @@ -125,25 +125,28 @@ import { geminiImage } from '@tanstack/ai-gemini'

type ImageProvider = 'openai' | 'gemini'

const imageAdapters: Record<ImageProvider, () => ReturnType<typeof openaiImage | typeof geminiImage>> = {
openai: () => openaiImage('gpt-image-2'),
gemini: () => geminiImage('gemini-3.1-flash-image-preview'),
const imageAdapters = {
openai: () => ({ adapter: openaiImage('gpt-image-2'), size: '1024x1024' as const }),
gemini: () => ({ adapter: geminiImage('gemini-3.1-flash-image'), size: '16:9_4K' as const }),
}

export async function POST(request: Request) {
const body = await request.json()
const provider: ImageProvider = body.provider ?? 'openai'
const { adapter, size } = imageAdapters[provider]()

const result = await generateImage({
adapter: imageAdapters[provider](),
adapter,
prompt: 'A beautiful sunset over mountains',
size: '1024x1024',
size,
})

return Response.json(result)
}
```

`size` is provider-specific, which is why it can't be a single literal shared across branches: Gemini native image models take a `'<aspectRatio>_<tier>'` string (e.g. `'16:9_4K'`), while OpenAI and Imagen models take pixel dimensions (e.g. `'1024x1024'`).

## Using with Summarize Adapters

And for summarization:
Expand Down
7 changes: 4 additions & 3 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@
"label": "Image Generation",
"to": "media/image-generation",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-13"
"updatedAt": "2026-08-14"
},
{
"label": "Video Generation",
Expand Down Expand Up @@ -671,7 +671,8 @@
{
"label": "Runtime Adapter Switching",
"to": "advanced/runtime-adapter-switching",
"addedAt": "2026-04-15"
"addedAt": "2026-04-15",
"updatedAt": "2026-08-14"
},
{
"label": "Tree-Shaking",
Expand Down Expand Up @@ -796,7 +797,7 @@
"label": "Google Gemini",
"to": "adapters/gemini",
"addedAt": "2026-04-15",
"updatedAt": "2026-07-22"
"updatedAt": "2026-08-14"
},
{
"label": "Ollama",
Expand Down
40 changes: 25 additions & 15 deletions docs/media/image-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import { geminiImage } from "@tanstack/ai-gemini";

// Gemini native model (NanoBanana) β€” uses generateContent API
const result = await generateImage({
adapter: geminiImage("gemini-3.1-flash-image-preview"),
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: "A futuristic cityscape at night",
size: "16:9_4K",
});
Expand Down Expand Up @@ -121,19 +121,27 @@ All image adapters support these common options:

#### Gemini Native Models (NanoBanana)

Gemini native image models use a template literal size format: `"aspectRatio_resolution"`.
Gemini native image models use a template literal size format: `"aspectRatio_resolution"`. Each model accepts its own set, narrowed at compile time:

| Aspect Ratios | Resolutions |
| --------------------------------------------------------- | ---------------- |
| `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `9:16`, `16:9`, `21:9` | `1K`, `2K`, `4K` |
| Model | Aspect Ratios | Resolutions |
| ----------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------- |
| `gemini-3.1-flash-image` | `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9`, `1:4`, `4:1`, `1:8`, `8:1` | `512`, `1K`, `2K`, `4K` |
| `gemini-3.1-flash-lite-image` | same 14 as above (see note) | `1K` |
| `gemini-3-pro-image` | `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9` | `1K`, `2K`, `4K` |
| `gemini-2.5-flash-image` | same 10 as above | none β€” bare ratio |

```typescript ignore
// Examples
size: "16:9_4K"; // Widescreen at 4K resolution
size: "1:1_2K"; // Square at 2K resolution
size: "9:16_1K"; // Portrait at 1K resolution
size: "1:8_512"; // Tall banner at the 512 (0.5K) tier β€” Flash Image only
size: "16:9"; // gemini-2.5-flash-image: bare ratio, no resolution suffix
```

The `K` is case-sensitive (`1k` is rejected by the API), the smallest tier's token is `512` (not `512px` or `0.5K`), and `9:21` is Vertex/Cloud-only so it is not accepted. Google documents no `image_size` for `gemini-2.5-flash-image`, so that model takes a bare aspect ratio and the adapter sends no `imageSize`.

> **Note on `gemini-3.1-flash-lite-image`.** The four extreme banner ratios (`1:4`, `4:1`, `1:8`, `8:1`) are partially inferred for this model. Unlike the other three, Flash Lite has no per-model ratio table in Google's Gemini API guide; the 14-value set comes from the Cloud model page's explicit enumeration plus the guide's bare "a discrete set of 14 aspect ratios" assertion. The only Gemini-API enumeration for it is a 10-item bullet headed "New aspect ratios", which we read as a what's-new list rather than an exhaustive one. If the API rejects those four in practice, prefer the 10 standard ratios on this model.

#### Gemini Imagen Models

Imagen models accept WIDTHxHEIGHT format, which maps to aspect ratios internally:
Expand Down Expand Up @@ -191,7 +199,7 @@ import { geminiImage } from "@tanstack/ai-gemini";
import { badExampleUrl, goodExampleUrl } from "./urls";

await generateImage({
adapter: geminiImage("gemini-3.1-flash-image-preview"),
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: [
{ type: "text", content: "Not like this" },
{ type: "image", source: { type: "url", value: badExampleUrl } },
Expand Down Expand Up @@ -334,7 +342,7 @@ import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";

await generateImage({
adapter: geminiImage("gemini-3.1-flash-image-preview"),
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: [
{
type: "text",
Expand Down Expand Up @@ -641,7 +649,7 @@ import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";

const result = await generateImage({
adapter: geminiImage("gemini-3.1-flash-image-preview"),
adapter: geminiImage("gemini-3.1-flash-image"),
prompt: "A beautiful garden",
size: "16:9_4K",
});
Expand Down Expand Up @@ -709,12 +717,14 @@ if (result.usage?.unitsBilled != null) {

#### Gemini Native Models (NanoBanana)

| Model | Description |
| -------------------------------- | ----------------------------------------------------------------- |
| `gemini-3.1-flash-image-preview` | Latest and fastest Gemini native image generation |
| `gemini-3.1-flash-lite-image` | Nano Banana 2 Lite β€” ultra-low-latency, low-cost image generation |
| `gemini-3-pro-image-preview` | Higher quality Gemini native image generation |
| `gemini-2.5-flash-image` | Gemini 2.5 Flash with image generation |
| Model | Description |
| ----------------------------- | -------------------------------------------------------------------------- |
| `gemini-3.1-flash-image` | Nano Banana 2 β€” latest and fastest Gemini native image generation |
| `gemini-3.1-flash-lite-image` | Nano Banana 2 Lite β€” ultra-low-latency, low-cost image generation |
| `gemini-3-pro-image` | Nano Banana Pro β€” higher quality Gemini native image generation |
| `gemini-2.5-flash-image` | Nano Banana β€” legacy; shuts down 2026-10-02 |

The `gemini-3.1-flash-image-preview` and `gemini-3-pro-image-preview` ids were shut down on 2026-06-25. They remain in the type union as deprecated aliases so existing code compiles, but calls to them fail β€” use the GA ids above.

#### Gemini Imagen Models

Expand Down
8 changes: 4 additions & 4 deletions examples/ts-react-chat/src/routes/image-gen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ const IMAGE_MODELS = [
label: 'Gemini 2.5 Flash Image',
},
{
value: 'google/gemini-2.5-flash-image-preview',
label: 'Gemini 2.5 Flash Image Preview',
value: 'google/gemini-3.1-flash-image',
label: 'Gemini 3.1 Flash Image (Nano Banana 2)',
},
{
value: 'google/gemini-3-pro-image-preview',
label: 'Gemini 3 Pro Image Preview',
value: 'google/gemini-3-pro-image',
label: 'Gemini 3 Pro Image (Nano Banana Pro)',
},
] as const

Expand Down
4 changes: 2 additions & 2 deletions examples/ts-react-media/src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ export const IMAGE_MODELS = [
provider: 'fal' as const,
},
{
id: 'gemini-3.1-flash-image-preview',
id: 'gemini-3.1-flash-image',
name: 'NanoBanana 2 (Gemini 3.1 Flash)',
description: 'Latest and fastest Gemini native image generation',
defaultSize: '16:9_4K' as const,
sizeType: 'native' as const,
provider: 'gemini' as const,
},
{
id: 'gemini-3-pro-image-preview',
id: 'gemini-3-pro-image',
name: 'NanoBanana Pro (Gemini 3 Pro)',
description: 'Higher quality Gemini native image generation',
defaultSize: '16:9_4K' as const,
Expand Down
8 changes: 4 additions & 4 deletions examples/ts-react-media/src/lib/server-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,17 @@ export const generateImageFn = createServerFn({ method: 'POST' })
},
})
}
case 'gemini-3.1-flash-image-preview': {
case 'gemini-3.1-flash-image': {
return generateImage({
adapter: geminiImage('gemini-3.1-flash-image-preview'),
adapter: geminiImage('gemini-3.1-flash-image'),
prompt: asImagePrompt(data.prompt),
numberOfImages: 1,
size: '16:9_4K',
})
}
case 'gemini-3-pro-image-preview': {
case 'gemini-3-pro-image': {
return generateImage({
adapter: geminiImage('gemini-3-pro-image-preview'),
adapter: geminiImage('gemini-3-pro-image'),
prompt: asImagePrompt(data.prompt),
numberOfImages: 1,
size: '16:9_4K',
Expand Down
Loading
Loading