diff --git a/.changeset/five-parts-leave.md b/.changeset/five-parts-leave.md
new file mode 100644
index 000000000..081995efe
--- /dev/null
+++ b/.changeset/five-parts-leave.md
@@ -0,0 +1,7 @@
+---
+'@tanstack/ai-gemini': minor
+---
+
+- Add NanoBanana native image generation with up to 4K image output, routing all gemini-\* native image models through generateContent API
+- Fix SDK property names (imageGenerationConfig → imageConfig, outputImageSize → imageSize) and rename NanoBanana types to GeminiNativeImage
+- Add Gemini 3.1 Pro model support for text generation
diff --git a/docs/adapters/fal.md b/docs/adapters/fal.md
index 557b07073..5be52698e 100644
--- a/docs/adapters/fal.md
+++ b/docs/adapters/fal.md
@@ -5,7 +5,7 @@ id: fal-adapter
The fal.ai adapter provides access to 600+ models on the fal.ai platform for image generation and video generation. Unlike text-focused adapters, the fal adapter is **media-focused** — it supports `generateImage()` and `generateVideo()` but does not support `chat()` or tools. Audio and speech support are coming soon.
-For a full working example, see the [fal.ai example app](https://github.com/TanStack/ai/tree/main/examples/ts-react-fal).
+For a full working example, see the [fal.ai example app](https://github.com/TanStack/ai/tree/main/examples/ts-react-media).
## Installation
@@ -79,7 +79,7 @@ const proxiedAdapter = falImage("fal-ai/flux/dev", {
## Example: Image Generation
-From the [fal.ai example app](https://github.com/TanStack/ai/tree/main/examples/ts-react-fal):
+From the [fal.ai example app](https://github.com/TanStack/ai/tree/main/examples/ts-react-media):
```typescript
import { generateImage } from "@tanstack/ai";
@@ -155,7 +155,7 @@ import { falVideo } from "@tanstack/ai-fal";
## Example: Text-to-Video
-From the [fal.ai example app](https://github.com/TanStack/ai/tree/main/examples/ts-react-fal):
+From the [fal.ai example app](https://github.com/TanStack/ai/tree/main/examples/ts-react-media):
```typescript
import { generateVideo, getVideoJobStatus } from "@tanstack/ai";
diff --git a/docs/adapters/gemini.md b/docs/adapters/gemini.md
index 53cb479d0..9617c7422 100644
--- a/docs/adapters/gemini.md
+++ b/docs/adapters/gemini.md
@@ -4,7 +4,9 @@ id: gemini-adapter
order: 3
---
-The Google Gemini adapter provides access to Google's Gemini models, including text generation, image generation with Imagen, and experimental text-to-speech.
+The Google Gemini adapter provides access to Google's Gemini models, including text generation, image generation with both Imagen and Gemini native image models (NanoBanana), and experimental text-to-speech.
+
+For a full working example with image generation, see the [media generation example app](https://github.com/TanStack/ai/tree/main/examples/ts-react-media).
## Installation
@@ -158,14 +160,39 @@ console.log(result.summary);
## Image Generation
-Generate images with Imagen:
+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.
+- **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`.
+
+### Example: Gemini Native Image Generation (NanoBanana)
+
+From the [media generation example app](https://github.com/TanStack/ai/tree/main/examples/ts-react-media):
+
+```typescript
+import { generateImage } from "@tanstack/ai";
+import { geminiImage } from "@tanstack/ai-gemini";
+
+const result = await generateImage({
+ adapter: geminiImage("gemini-3.1-flash-image-preview"),
+ prompt: "A futuristic cityscape at sunset",
+ numberOfImages: 1,
+ size: "16:9_4K",
+});
+
+console.log(result.images);
+```
+
+### Example: Imagen
```typescript
import { generateImage } from "@tanstack/ai";
import { geminiImage } from "@tanstack/ai-gemini";
const result = await generateImage({
- adapter: geminiImage("imagen-3.0-generate-002"),
+ adapter: geminiImage("imagen-4.0-generate-001"),
prompt: "A futuristic cityscape at sunset",
numberOfImages: 1,
});
@@ -173,11 +200,51 @@ const result = await generateImage({
console.log(result.images);
```
+### Image Size Options
+
+#### Gemini Native Models (NanoBanana)
+
+Gemini native image models use a template literal size format combining aspect ratio and resolution tier:
+
+```typescript
+// Format: "aspectRatio_resolution"
+size: "16:9_4K"
+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` |
+
+#### Imagen Models
+
+Imagen models use WIDTHxHEIGHT format, which maps to aspect ratios internally:
+
+| Size | Aspect Ratio |
+|------|-------------|
+| `1024x1024` | 1:1 |
+| `1920x1080` | 16:9 |
+| `1080x1920` | 9:16 |
+
+Alternatively, you can specify the aspect ratio directly in Model Options:
+
+```typescript
+const result = await generateImage({
+ adapter: geminiImage("imagen-4.0-generate-001"),
+ prompt: "A landscape photo",
+ modelOptions: {
+ aspectRatio: "16:9",
+ },
+});
+```
+
### Image Model Options
```typescript
const result = await generateImage({
- adapter: geminiImage("imagen-3.0-generate-002"),
+ adapter: geminiImage("imagen-4.0-generate-001"),
prompt: "...",
modelOptions: {
aspectRatio: "16:9", // "1:1" | "3:4" | "4:3" | "9:16" | "16:9"
@@ -221,6 +288,30 @@ GOOGLE_API_KEY=your-api-key-here
2. Create a new API key
3. Add it to your environment variables
+## Popular Image Models
+
+### Gemini Native Image Models (NanoBanana)
+
+These models use the `generateContent` API and support resolution tiers (1K, 2K, 4K).
+
+| Model | Description |
+|-------|-------------|
+| `gemini-3.1-flash-image-preview` | Latest and fastest Gemini native 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-2.0-flash-preview-image-generation` | Gemini 2.0 Flash image generation |
+
+### Imagen Models
+
+These models use the dedicated `generateImages` API.
+
+| Model | Description |
+|-------|-------------|
+| `imagen-4.0-ultra-generate-001` | Best quality Imagen image generation |
+| `imagen-4.0-generate-001` | High quality Imagen image generation |
+| `imagen-4.0-fast-generate-001` | Fast Imagen image generation |
+| `imagen-3.0-generate-002` | Imagen 3 image generation |
+
## API Reference
### `geminiText(config?)`
@@ -252,15 +343,26 @@ Creates a Gemini summarization adapter with an explicit API key.
**Returns:** A Gemini summarize adapter instance.
-### `geminiImage(config?)`
+### `geminiImage(model, config?)`
+
+Creates a Gemini image adapter using environment variables. Automatically routes to the correct API based on model name — `gemini-*` models use `generateContent`, `imagen-*` models use `generateImages`.
+
+**Parameters:**
-Creates a Gemini image generation adapter using environment variables.
+- `model` - The model name (e.g., `"gemini-3.1-flash-image-preview"` or `"imagen-4.0-generate-001"`)
+- `config.baseURL?` - Custom base URL (optional)
**Returns:** A Gemini image adapter instance.
-### `createGeminiImage(apiKey, config?)`
+### `createGeminiImage(model, apiKey, config?)`
+
+Creates a Gemini image adapter with an explicit API key.
-Creates a Gemini image generation adapter with an explicit API key.
+**Parameters:**
+
+- `model` - The model name
+- `apiKey` - Your Google API key
+- `config.baseURL?` - Custom base URL (optional)
**Returns:** A Gemini image adapter instance.
@@ -278,6 +380,8 @@ Creates a Gemini TTS adapter with an explicit API key.
## Next Steps
+- [Image Generation Guide](../guides/image-generation) - Learn more about image generation
+- [Media Generation Example](https://github.com/TanStack/ai/tree/main/examples/ts-react-media) - Full working example with Gemini and fal.ai
- [Getting Started](../getting-started/quick-start) - Learn the basics
- [Tools Guide](../guides/tools) - Learn about tools
- [Other Adapters](./openai) - Explore other providers
diff --git a/docs/guides/image-generation.md b/docs/guides/image-generation.md
index 1239a1239..5e167f50e 100644
--- a/docs/guides/image-generation.md
+++ b/docs/guides/image-generation.md
@@ -13,7 +13,8 @@ TanStack AI provides support for image generation through dedicated image adapte
Image generation is handled by image adapters that follow the same tree-shakeable architecture as other adapters in TanStack AI. The image adapters support:
- **OpenAI**: DALL-E 2, DALL-E 3, GPT-Image-1, and GPT-Image-1-Mini models
-- **Gemini**: Imagen 3 and Imagen 4 models
+- **Gemini**: Gemini native image models (NanoBanana) and Imagen 3/4 models
+- **fal.ai**: 600+ models including Nano Banana Pro, FLUX, and more
## Basic Usage
@@ -37,16 +38,22 @@ console.log(result.images[0].url) // URL to the generated image
### Gemini Image Generation
+Gemini supports two types of image generation: Gemini native models (NanoBanana) and Imagen models. The adapter automatically routes to the correct API based on the model name.
+
```typescript
import { generateImage } from '@tanstack/ai'
import { geminiImage } from '@tanstack/ai-gemini'
-// Create an image adapter (uses GOOGLE_API_KEY from environment)
-const adapter = geminiImage()
-
-// Generate an image
+// Gemini native model (NanoBanana) — uses generateContent API
const result = await generateImage({
- adapter: geminiImage('imagen-3.0-generate-002'),
+ adapter: geminiImage('gemini-3.1-flash-image-preview'),
+ prompt: 'A futuristic cityscape at night',
+ size: '16:9_4K',
+})
+
+// Imagen model — uses generateImages API
+const result2 = await generateImage({
+ adapter: geminiImage('imagen-4.0-generate-001'),
prompt: 'A futuristic cityscape at night',
})
@@ -78,9 +85,24 @@ All image adapters support these common options:
| `dall-e-3` | `1024x1024`, `1792x1024`, `1024x1792` |
| `dall-e-2` | `256x256`, `512x512`, `1024x1024` |
-#### Gemini Models
+#### Gemini Native Models (NanoBanana)
-Gemini uses aspect ratios internally, but TanStack AI accepts WIDTHxHEIGHT format and converts them:
+Gemini native image models use a template literal size format: `"aspectRatio_resolution"`.
+
+| Aspect Ratios | Resolutions |
+|---------------|-------------|
+| `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `9:16`, `16:9`, `21:9` | `1K`, `2K`, `4K` |
+
+```typescript
+// 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
+```
+
+#### Gemini Imagen Models
+
+Imagen models accept WIDTHxHEIGHT format, which maps to aspect ratios internally:
| Size | Aspect Ratio |
|------|-------------|
@@ -134,7 +156,7 @@ const result = await generateImage({
})
```
-### Gemini Model Options
+### Gemini Imagen Model Options
```typescript
const result = await generateImage({
@@ -150,6 +172,18 @@ const result = await generateImage({
})
```
+### Gemini Native Model Options (NanoBanana)
+
+Gemini native image models accept `GenerateContentConfig` options directly in `modelOptions`:
+
+```typescript
+const result = await generateImage({
+ adapter: geminiImage('gemini-3.1-flash-image-preview'),
+ prompt: 'A beautiful garden',
+ size: '16:9_4K',
+})
+```
+
## Response Format
The image generation result includes:
@@ -184,14 +218,23 @@ interface GeneratedImage {
| `dall-e-3` | 1 |
| `dall-e-2` | 1-10 |
-### Gemini Models
+### Gemini Native Models (NanoBanana)
+
+| Model | Description |
+|-------|-------------|
+| `gemini-3.1-flash-image-preview` | Latest and fastest Gemini native 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-2.0-flash-preview-image-generation` | Gemini 2.0 Flash image generation |
+
+### Gemini Imagen Models
| Model | Images per Request |
|-------|-------------------|
-| `imagen-3.0-generate-002` | 1-4 |
+| `imagen-4.0-ultra-generate-001` | 1-4 |
| `imagen-4.0-generate-001` | 1-4 |
| `imagen-4.0-fast-generate-001` | 1-4 |
-| `imagen-4.0-ultra-generate-001` | 1-4 |
+| `imagen-3.0-generate-002` | 1-4 |
## Error Handling
@@ -216,7 +259,7 @@ try {
The image adapters use the same environment variables as the text adapters:
- **OpenAI**: `OPENAI_API_KEY`
-- **Gemini**: `GOOGLE_API_KEY` or `GEMINI_API_KEY`
+- **Gemini** (including NanoBanana): `GOOGLE_API_KEY` or `GEMINI_API_KEY`
## Explicit API Keys
diff --git a/examples/ts-react-fal/.env.example b/examples/ts-react-media/.env.example
similarity index 63%
rename from examples/ts-react-fal/.env.example
rename to examples/ts-react-media/.env.example
index ae8401657..b7c897653 100644
--- a/examples/ts-react-fal/.env.example
+++ b/examples/ts-react-media/.env.example
@@ -1,4 +1,7 @@
-# Duplicate the .env.example file and rename it to .env.local, then add your FAL_KEY.
+# Duplicate the .env.example file and rename it to .env.local, then add your API keys.
# Sign up for an account at https://fal.ai, and add $20 of credits to your account to get started.
FAL_KEY=
+
+# Get a Google API key at https://aistudio.google.com/apikey
+GOOGLE_API_KEY=
diff --git a/examples/ts-react-fal/package.json b/examples/ts-react-media/package.json
similarity index 92%
rename from examples/ts-react-fal/package.json
rename to examples/ts-react-media/package.json
index 2284e5a94..f5be09f00 100644
--- a/examples/ts-react-fal/package.json
+++ b/examples/ts-react-media/package.json
@@ -1,5 +1,5 @@
{
- "name": "ts-react-fal",
+ "name": "ts-react-media",
"private": true,
"type": "module",
"scripts": {
@@ -13,6 +13,7 @@
"@tailwindcss/vite": "^4.1.18",
"@tanstack/ai": "workspace:*",
"@tanstack/ai-fal": "workspace:*",
+ "@tanstack/ai-gemini": "workspace:*",
"@tanstack/react-router": "^1.158.4",
"@tanstack/react-start": "^1.159.0",
"@tanstack/router-plugin": "^1.158.4",
diff --git a/examples/ts-react-fal/src/components/Header.tsx b/examples/ts-react-media/src/components/Header.tsx
similarity index 91%
rename from examples/ts-react-fal/src/components/Header.tsx
rename to examples/ts-react-media/src/components/Header.tsx
index 6cf2dde8b..f8a33961f 100644
--- a/examples/ts-react-fal/src/components/Header.tsx
+++ b/examples/ts-react-media/src/components/Header.tsx
@@ -10,7 +10,7 @@ export default function Header() {
- Image & Video Generation with fal.ai
+ Image & Video Generation
)
diff --git a/examples/ts-react-fal/src/components/ImageGenerator.tsx b/examples/ts-react-media/src/components/ImageGenerator.tsx
similarity index 86%
rename from examples/ts-react-fal/src/components/ImageGenerator.tsx
rename to examples/ts-react-media/src/components/ImageGenerator.tsx
index 9207551a9..484df42c9 100644
--- a/examples/ts-react-fal/src/components/ImageGenerator.tsx
+++ b/examples/ts-react-media/src/components/ImageGenerator.tsx
@@ -16,6 +16,15 @@ type ModelResult = {
error?: string
}
+function getImageSrc(image: { url?: string; b64Json?: string }): string {
+ if (image.url) return image.url
+ if (image.b64Json) return `data:image/png;base64,${image.b64Json}`
+ return ''
+}
+
+const falModels = IMAGE_MODELS.filter((m) => m.provider === 'fal')
+const geminiModels = IMAGE_MODELS.filter((m) => m.provider === 'gemini')
+
export default function ImageGenerator({
onImageGenerated,
}: ImageGeneratorProps) {
@@ -50,9 +59,9 @@ export default function ImageGenerator({
...prev,
[model.id]: { status: 'success', result: response },
}))
- const imageUrl = response.images[0]?.url
- if (imageUrl) {
- onImageGenerated?.(imageUrl)
+ const image = response.images[0]
+ if (image) {
+ onImageGenerated?.(getImageSrc(image))
}
} catch (err) {
setResults((prev) => ({
@@ -77,9 +86,9 @@ export default function ImageGenerator({
data: { prompt, model: selectedModel },
})
setResults({ [selectedModel]: { status: 'success', result: response } })
- const imageUrl = response.images[0]?.url
- if (imageUrl) {
- onImageGenerated?.(imageUrl)
+ const image = response.images[0]
+ if (image) {
+ onImageGenerated?.(getImageSrc(image))
}
} catch (err) {
setResults({
@@ -109,11 +118,20 @@ export default function ImageGenerator({
className="w-full px-4 py-3 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent disabled:opacity-50"
>
- {IMAGE_MODELS.map((model) => (
-
- ))}
+
+
{currentModel && selectedModel !== 'all' && (
@@ -193,7 +211,7 @@ export default function ImageGenerator({
modelResult.result.images.length > 0 && (

diff --git a/examples/ts-react-fal/src/components/VideoGenerator.tsx b/examples/ts-react-media/src/components/VideoGenerator.tsx
similarity index 100%
rename from examples/ts-react-fal/src/components/VideoGenerator.tsx
rename to examples/ts-react-media/src/components/VideoGenerator.tsx
diff --git a/examples/ts-react-fal/src/lib/models.ts b/examples/ts-react-media/src/lib/models.ts
similarity index 65%
rename from examples/ts-react-fal/src/lib/models.ts
rename to examples/ts-react-media/src/lib/models.ts
index 7bd500a84..fedc49a61 100644
--- a/examples/ts-react-fal/src/lib/models.ts
+++ b/examples/ts-react-media/src/lib/models.ts
@@ -5,6 +5,7 @@ export const IMAGE_MODELS = [
description: 'Fast, high-quality image generation',
defaultSize: 'landscape_16_9' as const,
sizeType: 'standard' as const,
+ provider: 'fal' as const,
},
{
id: 'xai/grok-imagine-image',
@@ -12,6 +13,7 @@ export const IMAGE_MODELS = [
description: 'xAI highly aesthetic images with prompt enhancement',
defaultSize: '16:9' as const,
sizeType: 'aspect_ratio' as const,
+ provider: 'fal' as const,
},
{
id: 'fal-ai/flux-2/klein/9b',
@@ -19,6 +21,7 @@ export const IMAGE_MODELS = [
description: 'Enhanced realism, crisp text generation',
defaultSize: 'landscape_16_9' as const,
sizeType: 'standard' as const,
+ provider: 'fal' as const,
},
{
id: 'fal-ai/z-image/turbo',
@@ -26,6 +29,47 @@ export const IMAGE_MODELS = [
description: 'Super fast 6B parameter model',
defaultSize: 'landscape_16_9' as const,
sizeType: 'standard' as const,
+ provider: 'fal' as const,
+ },
+ {
+ id: 'gemini-3.1-flash-image-preview',
+ 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',
+ name: 'NanoBanana Pro (Gemini 3 Pro)',
+ description: 'Higher quality Gemini native image generation',
+ defaultSize: '16:9_4K' as const,
+ sizeType: 'native' as const,
+ provider: 'gemini' as const,
+ },
+ {
+ id: 'imagen-4.0-ultra-generate-001',
+ name: 'Imagen 4.0 Ultra',
+ description: 'Best quality Imagen image generation',
+ defaultSize: '1024x1024' as const,
+ sizeType: 'standard' as const,
+ provider: 'gemini' as const,
+ },
+ {
+ id: 'imagen-4.0-generate-001',
+ name: 'Imagen 4.0',
+ description: 'High quality Imagen image generation',
+ defaultSize: '1024x1024' as const,
+ sizeType: 'standard' as const,
+ provider: 'gemini' as const,
+ },
+ {
+ id: 'imagen-4.0-fast-generate-001',
+ name: 'Imagen 4.0 Fast',
+ description: 'Fast Imagen image generation',
+ defaultSize: '1024x1024' as const,
+ sizeType: 'standard' as const,
+ provider: 'gemini' as const,
},
] as const
diff --git a/examples/ts-react-fal/src/lib/prompts.ts b/examples/ts-react-media/src/lib/prompts.ts
similarity index 100%
rename from examples/ts-react-fal/src/lib/prompts.ts
rename to examples/ts-react-media/src/lib/prompts.ts
diff --git a/examples/ts-react-fal/src/lib/server-functions.ts b/examples/ts-react-media/src/lib/server-functions.ts
similarity index 82%
rename from examples/ts-react-fal/src/lib/server-functions.ts
rename to examples/ts-react-media/src/lib/server-functions.ts
index d7424504d..1aef006c2 100644
--- a/examples/ts-react-fal/src/lib/server-functions.ts
+++ b/examples/ts-react-media/src/lib/server-functions.ts
@@ -1,5 +1,6 @@
import { createServerFn } from '@tanstack/react-start'
import { falImage, falVideo } from '@tanstack/ai-fal'
+import { geminiImage } from '@tanstack/ai-gemini'
import { generateImage, generateVideo, getVideoJobStatus } from '@tanstack/ai'
import type { FalModel } from '@tanstack/ai-fal'
@@ -42,7 +43,7 @@ export const generateImageFn = createServerFn({ method: 'POST' })
adapter: falImage('fal-ai/flux-2/klein/9b'),
prompt: data.prompt,
numberOfImages: 1,
- size: '16:9',
+ size: 'landscape_16_9',
})
}
case 'fal-ai/z-image/turbo': {
@@ -57,6 +58,46 @@ export const generateImageFn = createServerFn({ method: 'POST' })
},
})
}
+ case 'gemini-3.1-flash-image-preview': {
+ return generateImage({
+ adapter: geminiImage('gemini-3.1-flash-image-preview'),
+ prompt: data.prompt,
+ numberOfImages: 1,
+ size: '16:9_4K',
+ })
+ }
+ case 'gemini-3-pro-image-preview': {
+ return generateImage({
+ adapter: geminiImage('gemini-3-pro-image-preview'),
+ prompt: data.prompt,
+ numberOfImages: 1,
+ size: '16:9_4K',
+ })
+ }
+ case 'imagen-4.0-ultra-generate-001': {
+ return generateImage({
+ adapter: geminiImage('imagen-4.0-ultra-generate-001'),
+ prompt: data.prompt,
+ numberOfImages: 1,
+ size: '1024x1024',
+ })
+ }
+ case 'imagen-4.0-generate-001': {
+ return generateImage({
+ adapter: geminiImage('imagen-4.0-generate-001'),
+ prompt: data.prompt,
+ numberOfImages: 1,
+ size: '1024x1024',
+ })
+ }
+ case 'imagen-4.0-fast-generate-001': {
+ return generateImage({
+ adapter: geminiImage('imagen-4.0-fast-generate-001'),
+ prompt: data.prompt,
+ numberOfImages: 1,
+ size: '1024x1024',
+ })
+ }
default:
throw new Error(`Unknown model: ${data.model}`)
}
diff --git a/examples/ts-react-fal/src/routeTree.gen.ts b/examples/ts-react-media/src/routeTree.gen.ts
similarity index 100%
rename from examples/ts-react-fal/src/routeTree.gen.ts
rename to examples/ts-react-media/src/routeTree.gen.ts
diff --git a/examples/ts-react-fal/src/router.tsx b/examples/ts-react-media/src/router.tsx
similarity index 100%
rename from examples/ts-react-fal/src/router.tsx
rename to examples/ts-react-media/src/router.tsx
diff --git a/examples/ts-react-fal/src/routes/__root.tsx b/examples/ts-react-media/src/routes/__root.tsx
similarity index 100%
rename from examples/ts-react-fal/src/routes/__root.tsx
rename to examples/ts-react-media/src/routes/__root.tsx
diff --git a/examples/ts-react-fal/src/routes/index.tsx b/examples/ts-react-media/src/routes/index.tsx
similarity index 97%
rename from examples/ts-react-fal/src/routes/index.tsx
rename to examples/ts-react-media/src/routes/index.tsx
index 13766747d..496876679 100644
--- a/examples/ts-react-fal/src/routes/index.tsx
+++ b/examples/ts-react-media/src/routes/index.tsx
@@ -20,7 +20,7 @@ function VisualPage() {
Visual Content Generator
- Generate images and videos using fal.ai models
+ Generate images and videos using AI models
diff --git a/examples/ts-react-fal/src/styles.css b/examples/ts-react-media/src/styles.css
similarity index 100%
rename from examples/ts-react-fal/src/styles.css
rename to examples/ts-react-media/src/styles.css
diff --git a/examples/ts-react-fal/tsconfig.json b/examples/ts-react-media/tsconfig.json
similarity index 100%
rename from examples/ts-react-fal/tsconfig.json
rename to examples/ts-react-media/tsconfig.json
diff --git a/examples/ts-react-fal/vite.config.ts b/examples/ts-react-media/vite.config.ts
similarity index 100%
rename from examples/ts-react-fal/vite.config.ts
rename to examples/ts-react-media/vite.config.ts
diff --git a/packages/typescript/ai-gemini/package.json b/packages/typescript/ai-gemini/package.json
index adc204409..77798f0db 100644
--- a/packages/typescript/ai-gemini/package.json
+++ b/packages/typescript/ai-gemini/package.json
@@ -40,7 +40,7 @@
"adapter"
],
"dependencies": {
- "@google/genai": "^1.30.0"
+ "@google/genai": "^1.43.0"
},
"peerDependencies": {
"@tanstack/ai": "workspace:^"
diff --git a/packages/typescript/ai-gemini/src/adapters/image.ts b/packages/typescript/ai-gemini/src/adapters/image.ts
index 87c501250..2ccf47b58 100644
--- a/packages/typescript/ai-gemini/src/adapters/image.ts
+++ b/packages/typescript/ai-gemini/src/adapters/image.ts
@@ -5,6 +5,7 @@ import {
getGeminiApiKeyFromEnv,
} from '../utils'
import {
+ parseNativeImageSize,
sizeToAspectRatio,
validateImageSize,
validateNumberOfImages,
@@ -22,6 +23,8 @@ import type {
ImageGenerationResult,
} from '@tanstack/ai'
import type {
+ GenerateContentConfig,
+ GenerateContentResponse,
GenerateImagesConfig,
GenerateImagesResponse,
GoogleGenAI,
@@ -39,14 +42,16 @@ export type GeminiImageModel = (typeof GEMINI_IMAGE_MODELS)[number]
/**
* Gemini Image Generation Adapter
*
- * Tree-shakeable adapter for Gemini Imagen image generation functionality.
- * Supports Imagen 3 and Imagen 4 models.
+ * Tree-shakeable adapter for Gemini image generation functionality.
+ * Supports Imagen 3/4 models (via generateImages API) and Gemini native
+ * image models like Nano Banana 2 (via generateContent API).
*
* Features:
* - Aspect ratio-based image sizing
* - Person generation controls
* - Safety filtering
* - Watermark options
+ * - Extended resolution tiers (Nano Banana 2)
*/
export class GeminiImageAdapter<
TModel extends GeminiImageModel,
@@ -76,15 +81,19 @@ export class GeminiImageAdapter<
async generateImages(
options: ImageGenerationOptions,
): Promise {
- const { model, prompt, numberOfImages, size } = options
+ const { model, prompt } = options
- // Validate inputs
validatePrompt({ prompt, model })
- validateImageSize(model, size)
- validateNumberOfImages(model, numberOfImages)
- // Build request config
- const config = this.buildConfig(options)
+ if (this.isGeminiImageModel(model)) {
+ return this.generateWithGeminiApi(options)
+ }
+
+ // Imagen models path (generateImages API)
+ validateImageSize(model, options.size)
+ validateNumberOfImages(model, options.numberOfImages)
+
+ const config = this.buildImagenConfig(options)
const response = await this.client.models.generateImages({
model,
@@ -92,10 +101,78 @@ export class GeminiImageAdapter<
config,
})
- return this.transformResponse(model, response)
+ return this.transformImagenResponse(model, response)
+ }
+
+ private isGeminiImageModel(model: string): boolean {
+ return model.startsWith('gemini-')
+ }
+
+ private async generateWithGeminiApi(
+ options: ImageGenerationOptions,
+ ): Promise {
+ const { model, prompt, size, numberOfImages, modelOptions } = options
+
+ const parsedSize = size ? parseNativeImageSize(size) : undefined
+
+ // The generateContent API has no numberOfImages parameter.
+ // Instead, augment the prompt to request multiple images when needed.
+ const augmentedPrompt =
+ numberOfImages && numberOfImages > 1
+ ? `${prompt} Generate ${numberOfImages} distinct images.`
+ : prompt
+
+ const config: GenerateContentConfig = {
+ // Include TEXT so the model can interleave descriptions between images
+ responseModalities: ['TEXT', 'IMAGE'],
+ ...(parsedSize && {
+ imageConfig: {
+ ...(parsedSize.aspectRatio && {
+ aspectRatio: parsedSize.aspectRatio,
+ }),
+ ...(parsedSize.resolution && {
+ imageSize: parsedSize.resolution,
+ }),
+ },
+ }),
+ ...modelOptions,
+ }
+
+ const response = await this.client.models.generateContent({
+ model,
+ contents: augmentedPrompt,
+ config,
+ })
+
+ return this.transformGeminiResponse(model, response)
+ }
+
+ private transformGeminiResponse(
+ model: string,
+ response: GenerateContentResponse,
+ ): ImageGenerationResult {
+ const images: Array = []
+ const parts = response.candidates?.[0]?.content?.parts ?? []
+
+ for (const part of parts) {
+ if (
+ part.inlineData?.data &&
+ typeof part.inlineData.data === 'string' &&
+ part.inlineData.data.length > 0
+ ) {
+ images.push({ b64Json: part.inlineData.data })
+ }
+ }
+
+ return {
+ id: generateId(this.name),
+ model,
+ images,
+ usage: undefined,
+ }
}
- private buildConfig(
+ private buildImagenConfig(
options: ImageGenerationOptions,
): GenerateImagesConfig {
const { size, numberOfImages, modelOptions } = options
@@ -108,7 +185,7 @@ export class GeminiImageAdapter<
}
}
- private transformResponse(
+ private transformImagenResponse(
model: string,
response: GenerateImagesResponse,
): ImageGenerationResult {
diff --git a/packages/typescript/ai-gemini/src/image/image-provider-options.ts b/packages/typescript/ai-gemini/src/image/image-provider-options.ts
index 2da393712..6574ed76c 100644
--- a/packages/typescript/ai-gemini/src/image/image-provider-options.ts
+++ b/packages/typescript/ai-gemini/src/image/image-provider-options.ts
@@ -145,11 +145,49 @@ export type GeminiImageSize =
| '1080x1920'
/**
- * Model-specific size options mapping
- * All Imagen models use the same size options
+ * Aspect ratios supported by Gemini native image models (via generateContent API).
+ * Matches the SDK's ImageConfig.aspectRatio values.
+ */
+export type GeminiNativeImageAspectRatio =
+ | '1:1'
+ | '2:3'
+ | '3:2'
+ | '3:4'
+ | '4:3'
+ | '9:16'
+ | '16:9'
+ | '21:9'
+
+/**
+ * Resolution tiers for Gemini native image models.
+ * Matches the SDK's ImageConfig.imageSize values.
+ */
+export type GeminiNativeImageResolution = '1K' | '2K' | '4K'
+
+/**
+ * Template literal size type for Gemini native image models: "16:9_4K", "1:1_2K", etc.
+ */
+export type GeminiNativeImageSize =
+ `${GeminiNativeImageAspectRatio}_${GeminiNativeImageResolution}`
+
+/**
+ * Gemini native image models that use the generateContent API path.
+ * These models support template literal sizes (aspectRatio_resolution).
+ */
+export type GeminiNativeImageModels =
+ | 'gemini-3.1-flash-image-preview'
+ | 'gemini-3-pro-image-preview'
+ | 'gemini-2.5-flash-image'
+ | 'gemini-2.0-flash-preview-image-generation'
+
+/**
+ * Model-specific size options mapping.
+ * Gemini native image models use template literal sizes, Imagen models use pixel sizes.
*/
export type GeminiImageModelSizeByName = {
- [K in GeminiImageModels]: GeminiImageSize
+ [K in GeminiNativeImageModels]: GeminiNativeImageSize
+} & {
+ [K in Exclude]: GeminiImageSize
}
/**
@@ -237,3 +275,15 @@ export function validatePrompt(options: {
throw new Error(`Prompt cannot be empty for model "${model}".`)
}
}
+
+/**
+ * Parses a Gemini native image size string into its components.
+ * Format: "aspectRatio_resolution" e.g. "16:9_4K" → { aspectRatio: "16:9", resolution: "4K" }
+ */
+export function parseNativeImageSize(
+ size: string,
+): { aspectRatio: string; resolution: string } | undefined {
+ const match = size.match(/^(\d+:\d+)_(.+)$/)
+ if (!match) return undefined
+ return { aspectRatio: match[1]!, resolution: match[2]! }
+}
diff --git a/packages/typescript/ai-gemini/src/model-meta.ts b/packages/typescript/ai-gemini/src/model-meta.ts
index 54761a026..c8a187f26 100644
--- a/packages/typescript/ai-gemini/src/model-meta.ts
+++ b/packages/typescript/ai-gemini/src/model-meta.ts
@@ -47,6 +47,44 @@ interface ModelMeta {
providerOptions?: TProviderOptions
}
+const GEMINI_3_1_PRO = {
+ name: 'gemini-3.1-pro-preview',
+ max_input_tokens: 1_048_576,
+ max_output_tokens: 65_536,
+ knowledge_cutoff: '2025-01-01',
+ supports: {
+ input: ['text', 'image', 'audio', 'video', 'document'],
+ output: ['text'],
+ capabilities: [
+ 'batch_api',
+ 'caching',
+ 'code_execution',
+ 'file_search',
+ 'function_calling',
+ 'search_grounding',
+ 'structured_output',
+ 'thinking',
+ 'url_context',
+ ],
+ },
+ pricing: {
+ input: {
+ normal: 2.5,
+ },
+ output: {
+ normal: 15,
+ },
+ },
+} as const satisfies ModelMeta<
+ GeminiToolConfigOptions &
+ GeminiSafetyOptions &
+ GeminiCommonConfigOptions &
+ GeminiCachedContentOptions &
+ GeminiStructuredOutputOptions &
+ GeminiThinkingOptions &
+ GeminiThinkingAdvancedOptions
+>
+
const GEMINI_3_PRO = {
name: 'gemini-3-pro-preview',
max_input_tokens: 1_048_576,
@@ -157,6 +195,39 @@ const GEMINI_3_PRO_IMAGE = {
GeminiThinkingAdvancedOptions
>
+const GEMINI_3_1_FLASH_IMAGE = {
+ name: 'gemini-3.1-flash-image-preview',
+ max_input_tokens: 65_536,
+ max_output_tokens: 65_536,
+ knowledge_cutoff: '2025-01-01',
+ supports: {
+ input: ['text', 'image'],
+ output: ['text', 'image'],
+ capabilities: [
+ 'batch_api',
+ 'image_generation',
+ 'search_grounding',
+ 'structured_output',
+ 'thinking',
+ ],
+ },
+ pricing: {
+ input: {
+ normal: 0.25,
+ },
+ output: {
+ normal: 1.5,
+ },
+ },
+} as const satisfies ModelMeta<
+ GeminiToolConfigOptions &
+ GeminiSafetyOptions &
+ GeminiCommonConfigOptions &
+ GeminiCachedContentOptions &
+ GeminiStructuredOutputOptions &
+ GeminiThinkingOptions
+>
+
const GEMINI_2_5_PRO = {
name: 'gemini-2.5-pro',
max_input_tokens: 1_048_576,
@@ -820,6 +891,7 @@ const VEO_2 = {
} as const */
export const GEMINI_MODELS = [
+ GEMINI_3_1_PRO.name,
GEMINI_3_PRO.name,
GEMINI_3_FLASH.name,
GEMINI_2_5_PRO.name,
@@ -836,6 +908,7 @@ export type GeminiModels = (typeof GEMINI_MODELS)[number]
export type GeminiImageModels = (typeof GEMINI_IMAGE_MODELS)[number]
export const GEMINI_IMAGE_MODELS = [
+ GEMINI_3_1_FLASH_IMAGE.name,
GEMINI_3_PRO_IMAGE.name,
GEMINI_2_5_FLASH_IMAGE.name,
GEMINI_2_FLASH_IMAGE.name,
@@ -911,6 +984,13 @@ export type GeminiTTSVoice = (typeof GEMINI_TTS_VOICES)[number]
// Manual type map for per-model provider options
export type GeminiChatModelProviderOptionsByName = {
// Models with thinking and structured output support
+ [GEMINI_3_1_PRO.name]: GeminiToolConfigOptions &
+ GeminiSafetyOptions &
+ GeminiCommonConfigOptions &
+ GeminiCachedContentOptions &
+ GeminiStructuredOutputOptions &
+ GeminiThinkingOptions &
+ GeminiThinkingAdvancedOptions
[GEMINI_3_PRO.name]: GeminiToolConfigOptions &
GeminiSafetyOptions &
GeminiCommonConfigOptions &
@@ -983,6 +1063,7 @@ export type GeminiChatModelProviderOptionsByName = {
*/
export type GeminiModelInputModalitiesByName = {
// Models with full multimodal support (text, image, audio, video, document)
+ [GEMINI_3_1_PRO.name]: typeof GEMINI_3_1_PRO.supports.input
[GEMINI_3_PRO.name]: typeof GEMINI_3_PRO.supports.input
[GEMINI_3_FLASH.name]: typeof GEMINI_3_FLASH.supports.input
[GEMINI_2_5_PRO.name]: typeof GEMINI_2_5_PRO.supports.input
diff --git a/packages/typescript/ai-gemini/tests/image-adapter.test.ts b/packages/typescript/ai-gemini/tests/image-adapter.test.ts
index b990fdc7e..9ca3af076 100644
--- a/packages/typescript/ai-gemini/tests/image-adapter.test.ts
+++ b/packages/typescript/ai-gemini/tests/image-adapter.test.ts
@@ -1,6 +1,7 @@
import { describe, it, expect, vi } from 'vitest'
import { GeminiImageAdapter, createGeminiImage } from '../src/adapters/image'
import {
+ parseNativeImageSize,
sizeToAspectRatio,
validateImageSize,
validateNumberOfImages,
@@ -115,8 +116,32 @@ describe('Gemini Image Adapter', () => {
})
})
+ describe('parseNativeImageSize', () => {
+ it('parses template literal sizes into components', () => {
+ expect(parseNativeImageSize('16:9_4K')).toEqual({
+ aspectRatio: '16:9',
+ resolution: '4K',
+ })
+ expect(parseNativeImageSize('1:1_2K')).toEqual({
+ aspectRatio: '1:1',
+ resolution: '2K',
+ })
+ expect(parseNativeImageSize('21:9_1K')).toEqual({
+ aspectRatio: '21:9',
+ resolution: '1K',
+ })
+ })
+
+ it('returns undefined for invalid formats', () => {
+ expect(parseNativeImageSize('1024x1024')).toBeUndefined()
+ expect(parseNativeImageSize('invalid')).toBeUndefined()
+ expect(parseNativeImageSize('16:9')).toBeUndefined()
+ expect(parseNativeImageSize('4K')).toBeUndefined()
+ })
+ })
+
describe('generateImages', () => {
- it('calls the Gemini models.generateImages API', async () => {
+ it('calls the Gemini models.generateImages API for Imagen models', async () => {
const mockResponse = {
generatedImages: [
{
@@ -129,7 +154,10 @@ describe('Gemini Image Adapter', () => {
const mockGenerateImages = vi.fn().mockResolvedValueOnce(mockResponse)
- const adapter = createGeminiImage('test-api-key')
+ const adapter = createGeminiImage(
+ 'imagen-3.0-generate-002',
+ 'test-api-key',
+ )
// Replace the internal Gemini SDK client with our mock
;(
adapter as unknown as {
@@ -169,7 +197,10 @@ describe('Gemini Image Adapter', () => {
const mockGenerateImages = vi.fn().mockResolvedValue(mockResponse)
- const adapter = createGeminiImage('test-api-key')
+ const adapter = createGeminiImage(
+ 'imagen-3.0-generate-002',
+ 'test-api-key',
+ )
;(
adapter as unknown as {
client: { models: { generateImages: unknown } }
@@ -194,5 +225,295 @@ describe('Gemini Image Adapter', () => {
expect(result1.id).toMatch(/^gemini-/)
expect(result2.id).toMatch(/^gemini-/)
})
+
+ it('calls generateContent API for Gemini image models', async () => {
+ const mockResponse = {
+ candidates: [
+ {
+ content: {
+ parts: [
+ {
+ inlineData: {
+ mimeType: 'image/png',
+ data: 'gemini-base64-image',
+ },
+ },
+ ],
+ },
+ },
+ ],
+ }
+
+ const mockGenerateContent = vi.fn().mockResolvedValueOnce(mockResponse)
+
+ const adapter = createGeminiImage(
+ 'gemini-3.1-flash-image-preview',
+ 'test-api-key',
+ )
+ ;(
+ adapter as unknown as {
+ client: { models: { generateContent: unknown } }
+ }
+ ).client = {
+ models: {
+ generateContent: mockGenerateContent,
+ },
+ }
+
+ const result = await adapter.generateImages({
+ model: 'gemini-3.1-flash-image-preview',
+ prompt: 'A futuristic city',
+ size: '16:9_4K',
+ })
+
+ expect(mockGenerateContent).toHaveBeenCalledWith({
+ model: 'gemini-3.1-flash-image-preview',
+ contents: 'A futuristic city',
+ config: {
+ responseModalities: ['TEXT', 'IMAGE'],
+ imageConfig: {
+ aspectRatio: '16:9',
+ imageSize: '4K',
+ },
+ },
+ })
+
+ expect(result.model).toBe('gemini-3.1-flash-image-preview')
+ expect(result.images).toHaveLength(1)
+ expect(result.images[0].b64Json).toBe('gemini-base64-image')
+ })
+
+ it('calls generateContent without imageConfig when no size provided', async () => {
+ const mockResponse = {
+ candidates: [
+ {
+ content: {
+ parts: [
+ {
+ inlineData: {
+ mimeType: 'image/png',
+ data: 'gemini-base64-image',
+ },
+ },
+ ],
+ },
+ },
+ ],
+ }
+
+ const mockGenerateContent = vi.fn().mockResolvedValueOnce(mockResponse)
+
+ const adapter = createGeminiImage(
+ 'gemini-3.1-flash-image-preview',
+ 'test-api-key',
+ )
+ ;(
+ adapter as unknown as {
+ client: { models: { generateContent: unknown } }
+ }
+ ).client = {
+ models: {
+ generateContent: mockGenerateContent,
+ },
+ }
+
+ const result = await adapter.generateImages({
+ model: 'gemini-3.1-flash-image-preview',
+ prompt: 'A simple sketch',
+ })
+
+ expect(mockGenerateContent).toHaveBeenCalledWith({
+ model: 'gemini-3.1-flash-image-preview',
+ contents: 'A simple sketch',
+ config: {
+ responseModalities: ['TEXT', 'IMAGE'],
+ },
+ })
+
+ expect(result.images).toHaveLength(1)
+ })
+
+ it('handles empty response from Gemini image model', async () => {
+ const mockResponse = {
+ candidates: [
+ {
+ content: {
+ parts: [],
+ },
+ },
+ ],
+ }
+
+ const mockGenerateContent = vi.fn().mockResolvedValueOnce(mockResponse)
+
+ const adapter = createGeminiImage(
+ 'gemini-3.1-flash-image-preview',
+ 'test-api-key',
+ )
+ ;(
+ adapter as unknown as {
+ client: { models: { generateContent: unknown } }
+ }
+ ).client = {
+ models: {
+ generateContent: mockGenerateContent,
+ },
+ }
+
+ const result = await adapter.generateImages({
+ model: 'gemini-3.1-flash-image-preview',
+ prompt: 'A test prompt',
+ })
+
+ expect(result.images).toHaveLength(0)
+ })
+
+ it('augments prompt when numberOfImages > 1 for Gemini models', async () => {
+ const mockResponse = {
+ candidates: [
+ {
+ content: {
+ parts: [
+ {
+ inlineData: { mimeType: 'image/png', data: 'img1' },
+ },
+ {
+ text: 'Here is the second image:',
+ },
+ {
+ inlineData: { mimeType: 'image/png', data: 'img2' },
+ },
+ ],
+ },
+ },
+ ],
+ }
+
+ const mockGenerateContent = vi.fn().mockResolvedValueOnce(mockResponse)
+
+ const adapter = createGeminiImage(
+ 'gemini-3.1-flash-image-preview',
+ 'test-api-key',
+ )
+ ;(
+ adapter as unknown as {
+ client: { models: { generateContent: unknown } }
+ }
+ ).client = {
+ models: {
+ generateContent: mockGenerateContent,
+ },
+ }
+
+ const result = await adapter.generateImages({
+ model: 'gemini-3.1-flash-image-preview',
+ prompt: 'A futuristic city',
+ numberOfImages: 3,
+ })
+
+ expect(mockGenerateContent).toHaveBeenCalledWith({
+ model: 'gemini-3.1-flash-image-preview',
+ contents: 'A futuristic city Generate 3 distinct images.',
+ config: {
+ responseModalities: ['TEXT', 'IMAGE'],
+ },
+ })
+
+ // Collects all inlineData parts, skipping text parts
+ expect(result.images).toHaveLength(2)
+ expect(result.images[0].b64Json).toBe('img1')
+ expect(result.images[1].b64Json).toBe('img2')
+ })
+
+ it('does not augment prompt when numberOfImages is 1', async () => {
+ const mockResponse = {
+ candidates: [
+ {
+ content: {
+ parts: [
+ {
+ inlineData: { mimeType: 'image/png', data: 'img1' },
+ },
+ ],
+ },
+ },
+ ],
+ }
+
+ const mockGenerateContent = vi.fn().mockResolvedValueOnce(mockResponse)
+
+ const adapter = createGeminiImage(
+ 'gemini-3.1-flash-image-preview',
+ 'test-api-key',
+ )
+ ;(
+ adapter as unknown as {
+ client: { models: { generateContent: unknown } }
+ }
+ ).client = {
+ models: {
+ generateContent: mockGenerateContent,
+ },
+ }
+
+ await adapter.generateImages({
+ model: 'gemini-3.1-flash-image-preview',
+ prompt: 'A simple sketch',
+ numberOfImages: 1,
+ })
+
+ expect(mockGenerateContent).toHaveBeenCalledWith({
+ model: 'gemini-3.1-flash-image-preview',
+ contents: 'A simple sketch',
+ config: {
+ responseModalities: ['TEXT', 'IMAGE'],
+ },
+ })
+ })
+
+ it('does not augment prompt when numberOfImages is undefined', async () => {
+ const mockResponse = {
+ candidates: [
+ {
+ content: {
+ parts: [
+ {
+ inlineData: { mimeType: 'image/png', data: 'img1' },
+ },
+ ],
+ },
+ },
+ ],
+ }
+
+ const mockGenerateContent = vi.fn().mockResolvedValueOnce(mockResponse)
+
+ const adapter = createGeminiImage(
+ 'gemini-3.1-flash-image-preview',
+ 'test-api-key',
+ )
+ ;(
+ adapter as unknown as {
+ client: { models: { generateContent: unknown } }
+ }
+ ).client = {
+ models: {
+ generateContent: mockGenerateContent,
+ },
+ }
+
+ await adapter.generateImages({
+ model: 'gemini-3.1-flash-image-preview',
+ prompt: 'A simple sketch',
+ })
+
+ expect(mockGenerateContent).toHaveBeenCalledWith({
+ model: 'gemini-3.1-flash-image-preview',
+ contents: 'A simple sketch',
+ config: {
+ responseModalities: ['TEXT', 'IMAGE'],
+ },
+ })
+ })
})
})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9b669fda9..6e975a75c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -326,7 +326,7 @@ importers:
specifier: ^5.1.0
version: 5.1.0
- examples/ts-react-fal:
+ examples/ts-react-media:
dependencies:
'@tailwindcss/vite':
specifier: ^4.1.18
@@ -337,6 +337,9 @@ importers:
'@tanstack/ai-fal':
specifier: workspace:*
version: link:../../packages/typescript/ai-fal
+ '@tanstack/ai-gemini':
+ specifier: workspace:*
+ version: link:../../packages/typescript/ai-gemini
'@tanstack/react-router':
specifier: ^1.158.4
version: 1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -762,8 +765,8 @@ importers:
packages/typescript/ai-gemini:
dependencies:
'@google/genai':
- specifier: ^1.30.0
- version: 1.33.0
+ specifier: ^1.43.0
+ version: 1.43.0
devDependencies:
'@tanstack/ai':
specifier: workspace:*
@@ -2387,11 +2390,11 @@ packages:
'@gerrit0/mini-shiki@3.19.0':
resolution: {integrity: sha512-ZSlWfLvr8Nl0T4iA3FF/8VH8HivYF82xQts2DY0tJxZd4wtXJ8AA0nmdW9lmO4hlrh3f9xNwEPtOgqETPqKwDA==}
- '@google/genai@1.33.0':
- resolution: {integrity: sha512-ThUjFZ1N0DU88peFjnQkb8K198EWaW2RmmnDShFQ+O+xkIH9itjpRe358x3L/b4X/A7dimkvq63oz49Vbh7Cog==}
+ '@google/genai@1.43.0':
+ resolution: {integrity: sha512-hklCsJNdMlDM1IwcCVcGQFBg2izY0+t5BIGbRsxi2UnKi6AGKL7pqJqmBDNRbw0bYCs4y3NA7TB+fkKfP/Nrdw==}
engines: {node: '>=20.0.0'}
peerDependencies:
- '@modelcontextprotocol/sdk': ^1.24.0
+ '@modelcontextprotocol/sdk': ^1.25.2
peerDependenciesMeta:
'@modelcontextprotocol/sdk':
optional: true
@@ -3073,6 +3076,36 @@ packages:
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
+ '@protobufjs/aspromise@1.1.2':
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
+
+ '@protobufjs/base64@1.1.2':
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
+
+ '@protobufjs/codegen@2.0.4':
+ resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
+
+ '@protobufjs/eventemitter@1.1.0':
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
+
+ '@protobufjs/fetch@1.1.0':
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
+
+ '@protobufjs/float@1.0.2':
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
+
+ '@protobufjs/inquire@1.1.0':
+ resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
+
+ '@protobufjs/path@1.1.2':
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
+
+ '@protobufjs/pool@1.1.0':
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
+
+ '@protobufjs/utf8@1.1.0':
+ resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
+
'@publint/pack@0.1.2':
resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==}
engines: {node: '>=18'}
@@ -4411,6 +4444,9 @@ packages:
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
+ '@types/retry@0.12.0':
+ resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
+
'@types/unist@2.0.11':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
@@ -6103,9 +6139,6 @@ packages:
h3@1.13.0:
resolution: {integrity: sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==}
- h3@1.15.4:
- resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==}
-
h3@1.15.5:
resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==}
@@ -6796,6 +6829,9 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
+ long@5.3.2:
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
+
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -7388,6 +7424,10 @@ packages:
resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==}
engines: {node: '>=6'}
+ p-retry@4.6.2:
+ resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
+ engines: {node: '>=8'}
+
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
@@ -7577,6 +7617,10 @@ packages:
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
+ protobufjs@7.5.4:
+ resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
+ engines: {node: '>=12.0.0'}
+
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -7758,6 +7802,10 @@ packages:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
+ retry@0.13.1:
+ resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
+ engines: {node: '>= 4'}
+
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -8565,68 +8613,6 @@ packages:
synckit:
optional: true
- unstorage@1.17.3:
- resolution: {integrity: sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==}
- peerDependencies:
- '@azure/app-configuration': ^1.8.0
- '@azure/cosmos': ^4.2.0
- '@azure/data-tables': ^13.3.0
- '@azure/identity': ^4.6.0
- '@azure/keyvault-secrets': ^4.9.0
- '@azure/storage-blob': ^12.26.0
- '@capacitor/preferences': ^6.0.3 || ^7.0.0
- '@deno/kv': '>=0.9.0'
- '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0
- '@planetscale/database': ^1.19.0
- '@upstash/redis': ^1.34.3
- '@vercel/blob': '>=0.27.1'
- '@vercel/functions': ^2.2.12 || ^3.0.0
- '@vercel/kv': ^1.0.1
- aws4fetch: ^1.0.20
- db0: '>=0.2.1'
- idb-keyval: ^6.2.1
- ioredis: ^5.4.2
- uploadthing: ^7.4.4
- peerDependenciesMeta:
- '@azure/app-configuration':
- optional: true
- '@azure/cosmos':
- optional: true
- '@azure/data-tables':
- optional: true
- '@azure/identity':
- optional: true
- '@azure/keyvault-secrets':
- optional: true
- '@azure/storage-blob':
- optional: true
- '@capacitor/preferences':
- optional: true
- '@deno/kv':
- optional: true
- '@netlify/blobs':
- optional: true
- '@planetscale/database':
- optional: true
- '@upstash/redis':
- optional: true
- '@vercel/blob':
- optional: true
- '@vercel/functions':
- optional: true
- '@vercel/kv':
- optional: true
- aws4fetch:
- optional: true
- db0:
- optional: true
- idb-keyval:
- optional: true
- ioredis:
- optional: true
- uploadthing:
- optional: true
-
unstorage@1.17.4:
resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==}
peerDependencies:
@@ -10098,9 +10084,11 @@ snapshots:
'@shikijs/types': 3.20.0
'@shikijs/vscode-textmate': 10.0.2
- '@google/genai@1.33.0':
+ '@google/genai@1.43.0':
dependencies:
google-auth-library: 10.5.0
+ p-retry: 4.6.2
+ protobufjs: 7.5.4
ws: 8.18.3
transitivePeerDependencies:
- bufferutil
@@ -10636,6 +10624,29 @@ snapshots:
'@poppinss/exception@1.2.3': {}
+ '@protobufjs/aspromise@1.1.2': {}
+
+ '@protobufjs/base64@1.1.2': {}
+
+ '@protobufjs/codegen@2.0.4': {}
+
+ '@protobufjs/eventemitter@1.1.0': {}
+
+ '@protobufjs/fetch@1.1.0':
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/inquire': 1.1.0
+
+ '@protobufjs/float@1.0.2': {}
+
+ '@protobufjs/inquire@1.1.0': {}
+
+ '@protobufjs/path@1.1.2': {}
+
+ '@protobufjs/pool@1.1.0': {}
+
+ '@protobufjs/utf8@1.1.0': {}
+
'@publint/pack@0.1.2': {}
'@quansync/fs@1.0.0':
@@ -12695,6 +12706,8 @@ snapshots:
'@types/resolve@1.20.2': {}
+ '@types/retry@0.12.0': {}
+
'@types/unist@2.0.11': {}
'@types/unist@3.0.3': {}
@@ -14778,18 +14791,6 @@ snapshots:
uncrypto: 0.1.3
unenv: 1.10.0
- h3@1.15.4:
- dependencies:
- cookie-es: 1.2.2
- crossws: 0.3.5
- defu: 6.1.4
- destr: 2.0.5
- iron-webcrypto: 1.2.1
- node-mock-http: 1.0.4
- radix3: 1.1.2
- ufo: 1.6.1
- uncrypto: 0.1.3
-
h3@1.15.5:
dependencies:
cookie-es: 1.2.2
@@ -15546,6 +15547,8 @@ snapshots:
chalk: 4.1.2
is-unicode-supported: 0.1.0
+ long@5.3.2: {}
+
longest-streak@3.1.0: {}
lowlight@3.3.0:
@@ -16152,7 +16155,7 @@ snapshots:
exsolve: 1.0.8
globby: 15.0.0
gzip-size: 7.0.0
- h3: 1.15.4
+ h3: 1.15.5
hookable: 5.5.3
httpxy: 0.1.7
ioredis: 5.8.2
@@ -16188,7 +16191,7 @@ snapshots:
unenv: 2.0.0-rc.24
unimport: 5.5.0
unplugin-utils: 0.3.1
- unstorage: 1.17.3(db0@0.3.4)(ioredis@5.8.2)
+ unstorage: 1.17.4(db0@0.3.4)(ioredis@5.8.2)
untyped: 2.0.0
unwasm: 0.3.11
youch: 4.1.0-beta.13
@@ -16610,6 +16613,11 @@ snapshots:
p-map@2.1.0: {}
+ p-retry@4.6.2:
+ dependencies:
+ '@types/retry': 0.12.0
+ retry: 0.13.1
+
p-try@2.2.0: {}
package-json-from-dist@1.0.1: {}
@@ -16768,6 +16776,21 @@ snapshots:
proto-list@1.2.4: {}
+ protobufjs@7.5.4:
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/base64': 1.1.2
+ '@protobufjs/codegen': 2.0.4
+ '@protobufjs/eventemitter': 1.1.0
+ '@protobufjs/fetch': 1.1.0
+ '@protobufjs/float': 1.0.2
+ '@protobufjs/inquire': 1.1.0
+ '@protobufjs/path': 1.1.2
+ '@protobufjs/pool': 1.1.0
+ '@protobufjs/utf8': 1.1.0
+ '@types/node': 24.10.3
+ long: 5.3.2
+
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -16993,6 +17016,8 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
+ retry@0.13.1: {}
+
reusify@1.1.0: {}
rimraf@5.0.10:
@@ -17953,20 +17978,6 @@ snapshots:
dependencies:
rolldown: 1.0.0-beta.53
- unstorage@1.17.3(db0@0.3.4)(ioredis@5.8.2):
- dependencies:
- anymatch: 3.1.3
- chokidar: 4.0.3
- destr: 2.0.5
- h3: 1.15.5
- lru-cache: 10.4.3
- node-fetch-native: 1.6.7
- ofetch: 1.5.1
- ufo: 1.6.1
- optionalDependencies:
- db0: 0.3.4
- ioredis: 5.8.2
-
unstorage@1.17.4(db0@0.3.4)(ioredis@5.8.2):
dependencies:
anymatch: 3.1.3