From b33acb9d1b523e1f0475e9376d900cd2bc8fd16b Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Wed, 1 Jul 2026 12:55:12 +1000 Subject: [PATCH] feat(ai-gemini): drop retired media models, add Veo 3.1 Lite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four media models now return 404 NOT_FOUND from the Gemini Developer API and are removed from the adapter's model lists + type maps: imagen-3.0-generate-002, veo-2.0-generate-001, veo-3.0-generate-001, veo-3.0-fast-generate-001. Add veo-3.1-lite-generate-preview (Veo 3.1 Lite) — the lowest-cost Veo 3.1 tier ($0.05/sec, 720p, video+audio), durations 4|6|8. Update the video duration maps, Imagen image caps, unit tests, and docs accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../gemini-drop-retired-media-models.md | 16 ++++ docs/adapters/gemini.md | 1 - docs/config.json | 7 +- docs/media/image-generation.md | 1 - docs/media/video-generation.md | 6 +- packages/ai-gemini/src/adapters/image.ts | 4 +- .../src/image/image-provider-options.ts | 9 +-- packages/ai-gemini/src/model-meta.ts | 77 +------------------ .../src/video/video-provider-options.ts | 8 +- .../ai-gemini/tests/image-adapter.test.ts | 36 ++++----- .../ai-gemini/tests/video-adapter.test.ts | 53 +++++-------- 11 files changed, 71 insertions(+), 147 deletions(-) create mode 100644 .changeset/gemini-drop-retired-media-models.md diff --git a/.changeset/gemini-drop-retired-media-models.md b/.changeset/gemini-drop-retired-media-models.md new file mode 100644 index 000000000..608531dc4 --- /dev/null +++ b/.changeset/gemini-drop-retired-media-models.md @@ -0,0 +1,16 @@ +--- +'@tanstack/ai-gemini': minor +--- + +Drop retired Gemini media models and add **Veo 3.1 Lite**. + +The following models return `404 NOT_FOUND` from the Gemini Developer API and have been removed from the adapter's model lists and type maps: + +- `imagen-3.0-generate-002` (superseded by the Imagen 4 family) +- `veo-2.0-generate-001` +- `veo-3.0-generate-001` +- `veo-3.0-fast-generate-001` + +Added `veo-3.1-lite-generate-preview` (Veo 3.1 Lite) — the lowest-cost Veo 3.1 tier ($0.05/sec, 720p, video + audio), with the same `4 | 6 | 8` second durations as the rest of the Veo 3.1 family. + +If you were referencing one of the removed model ids, switch to a current model (e.g. `imagen-4.0-generate-001`, `veo-3.1-generate-preview`, or `veo-3.1-lite-generate-preview`). diff --git a/docs/adapters/gemini.md b/docs/adapters/gemini.md index a5d46a933..b9926cec0 100644 --- a/docs/adapters/gemini.md +++ b/docs/adapters/gemini.md @@ -538,7 +538,6 @@ These models use the dedicated `generateImages` API. | `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 diff --git a/docs/config.json b/docs/config.json index 586e5d188..7ee188e5e 100644 --- a/docs/config.json +++ b/docs/config.json @@ -274,13 +274,13 @@ "label": "Image Generation", "to": "media/image-generation", "addedAt": "2026-04-15", - "updatedAt": "2026-06-08" + "updatedAt": "2026-07-01" }, { "label": "Video Generation", "to": "media/video-generation", "addedAt": "2026-04-15", - "updatedAt": "2026-06-24" + "updatedAt": "2026-07-01" }, { "label": "Generation Hooks", @@ -510,7 +510,8 @@ { "label": "Google Gemini", "to": "adapters/gemini", - "addedAt": "2026-04-15" + "addedAt": "2026-04-15", + "updatedAt": "2026-07-01" }, { "label": "Ollama", diff --git a/docs/media/image-generation.md b/docs/media/image-generation.md index b25a42580..45399565a 100644 --- a/docs/media/image-generation.md +++ b/docs/media/image-generation.md @@ -457,7 +457,6 @@ if (result.usage?.unitsBilled != null) { | `imagen-4.0-ultra-generate-001` | 1-4 | | `imagen-4.0-generate-001` | 1-4 | | `imagen-4.0-fast-generate-001` | 1-4 | -| `imagen-3.0-generate-002` | 1-4 | ## Error Handling diff --git a/docs/media/video-generation.md b/docs/media/video-generation.md index ebf9b0606..408bae527 100644 --- a/docs/media/video-generation.md +++ b/docs/media/video-generation.md @@ -540,9 +540,7 @@ the `duration` option: |-------|------------------------------| | `veo-3.1-generate-preview` | `4`, `6`, `8` | | `veo-3.1-fast-generate-preview` | `4`, `6`, `8` | -| `veo-3.0-generate-001` | `4`, `6`, `8` | -| `veo-3.0-fast-generate-001` | `4`, `6`, `8` | -| `veo-2.0-generate-001` | `5`, `6`, `8` | +| `veo-3.1-lite-generate-preview` | `4`, `6`, `8` | If you have raw seconds (for example from a UI slider), coerce them with `snapDuration`, or inspect the full set with `availableDurations`: @@ -551,7 +549,7 @@ If you have raw seconds (for example from a UI slider), coerce them with import { generateVideo } from '@tanstack/ai' import { geminiVideo } from '@tanstack/ai-gemini' -const adapter = geminiVideo('veo-3.0-generate-001') +const adapter = geminiVideo('veo-3.1-lite-generate-preview') adapter.availableDurations() // { kind: 'discrete', values: [4, 6, 8] } adapter.snapDuration(7) // 6 — closest valid duration diff --git a/packages/ai-gemini/src/adapters/image.ts b/packages/ai-gemini/src/adapters/image.ts index b107df197..7fd59bf67 100644 --- a/packages/ai-gemini/src/adapters/image.ts +++ b/packages/ai-gemini/src/adapters/image.ts @@ -420,14 +420,14 @@ export class GeminiImageAdapter< * Creates a Gemini image adapter with explicit API key. * Type resolution happens here at the call site. * - * @param model - The model name (e.g., 'imagen-3.0-generate-002') + * @param model - The model name (e.g., 'imagen-4.0-generate-001') * @param apiKey - Your Google API key * @param config - Optional additional configuration * @returns Configured Gemini image adapter instance with resolved types * * @example * ```typescript - * const adapter = createGeminiImage('imagen-3.0-generate-002', "your-api-key"); + * const adapter = createGeminiImage('imagen-4.0-generate-001', "your-api-key"); * * const result = await generateImage({ * adapter, diff --git a/packages/ai-gemini/src/image/image-provider-options.ts b/packages/ai-gemini/src/image/image-provider-options.ts index 9125cde97..3a0829893 100644 --- a/packages/ai-gemini/src/image/image-provider-options.ts +++ b/packages/ai-gemini/src/image/image-provider-options.ts @@ -256,15 +256,14 @@ export function validateImageSize( /** * Per-model caps on images per request. - * Imagen 3 and the Imagen 4 family all support up to 4 images per request - * via the Gemini API (the rumored 8-image tier is Vertex-only and isn't - * reachable through @google/genai today). Unknown models fall through to - * the shared cap defined below. + * The Imagen 4 family all support up to 4 images per request via the Gemini + * API (the rumored 8-image tier is Vertex-only and isn't reachable through + * @google/genai today). Unknown models fall through to the shared cap + * defined below. * * @see https://ai.google.dev/gemini-api/docs/imagen */ const IMAGEN_MAX_IMAGES_BY_MODEL: Record = { - 'imagen-3.0-generate-002': 4, 'imagen-4.0-generate-001': 4, 'imagen-4.0-ultra-generate-001': 4, 'imagen-4.0-fast-generate-001': 4, diff --git a/packages/ai-gemini/src/model-meta.ts b/packages/ai-gemini/src/model-meta.ts index 76cf52f7e..2e48577d8 100644 --- a/packages/ai-gemini/src/model-meta.ts +++ b/packages/ai-gemini/src/model-meta.ts @@ -610,27 +610,6 @@ const IMAGEN_4_GENERATE_FAST = { GeminiCachedContentOptions > -const IMAGEN_3 = { - name: 'imagen-3.0-generate-002', - max_output_tokens: 4, - supports: { - input: ['text'], - output: ['image'], - }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.03, - }, - }, -} as const satisfies ModelMeta< - GeminiToolConfigOptions & - GeminiSafetyOptions & - GeminiCommonConfigOptions & - GeminiCachedContentOptions -> /** * Veo video generation models. Pricing is per second of generated video * (audio+video rate where the model supports audio). @@ -682,8 +661,8 @@ const VEO_3_1_FAST_PREVIEW = { GeminiCachedContentOptions > -const VEO_3 = { - name: 'veo-3.0-generate-001', +const VEO_3_1_LITE_PREVIEW = { + name: 'veo-3.1-lite-generate-preview', max_input_tokens: 1024, max_output_tokens: 1, supports: { @@ -695,52 +674,7 @@ const VEO_3 = { normal: 0, }, output: { - normal: 0.4, - }, - }, -} as const satisfies ModelMeta< - GeminiToolConfigOptions & - GeminiSafetyOptions & - GeminiCommonConfigOptions & - GeminiCachedContentOptions -> - -const VEO_3_FAST = { - name: 'veo-3.0-fast-generate-001', - max_input_tokens: 1024, - max_output_tokens: 1, - supports: { - input: ['text', 'image'], - output: ['video', 'audio'], - }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.15, - }, - }, -} as const satisfies ModelMeta< - GeminiToolConfigOptions & - GeminiSafetyOptions & - GeminiCommonConfigOptions & - GeminiCachedContentOptions -> - -const VEO_2 = { - name: 'veo-2.0-generate-001', - max_output_tokens: 2, - supports: { - input: ['text', 'image'], - output: ['video'], - }, - pricing: { - input: { - normal: 0, - }, - output: { - normal: 0.35, + normal: 0.05, }, }, } as const satisfies ModelMeta< @@ -853,7 +787,6 @@ export const GEMINI_IMAGE_MODELS = [ GEMINI_3_1_FLASH_IMAGE.name, GEMINI_3_PRO_IMAGE.name, GEMINI_2_5_FLASH_IMAGE.name, - IMAGEN_3.name, IMAGEN_4_GENERATE.name, IMAGEN_4_GENERATE_FAST.name, IMAGEN_4_GENERATE_ULTRA.name, @@ -924,9 +857,7 @@ export type GeminiTTSVoice = (typeof GEMINI_TTS_VOICES)[number] export const GEMINI_VIDEO_MODELS = [ VEO_3_1_PREVIEW.name, VEO_3_1_FAST_PREVIEW.name, - VEO_3.name, - VEO_3_FAST.name, - VEO_2.name, + VEO_3_1_LITE_PREVIEW.name, ] as const // Manual type map for per-model provider options diff --git a/packages/ai-gemini/src/video/video-provider-options.ts b/packages/ai-gemini/src/video/video-provider-options.ts index b1fd5671a..1daee974b 100644 --- a/packages/ai-gemini/src/video/video-provider-options.ts +++ b/packages/ai-gemini/src/video/video-provider-options.ts @@ -87,9 +87,7 @@ export type GeminiVideoModelInputModalitiesByName = { export type GeminiVideoModelDurationByName = { 'veo-3.1-generate-preview': 4 | 6 | 8 'veo-3.1-fast-generate-preview': 4 | 6 | 8 - 'veo-3.0-generate-001': 4 | 6 | 8 - 'veo-3.0-fast-generate-001': 4 | 6 | 8 - 'veo-2.0-generate-001': 5 | 6 | 8 + 'veo-3.1-lite-generate-preview': 4 | 6 | 8 } /** @@ -109,9 +107,7 @@ export const GEMINI_VIDEO_DURATIONS: { } = { 'veo-3.1-generate-preview': { kind: 'discrete', values: [4, 6, 8] }, 'veo-3.1-fast-generate-preview': { kind: 'discrete', values: [4, 6, 8] }, - 'veo-3.0-generate-001': { kind: 'discrete', values: [4, 6, 8] }, - 'veo-3.0-fast-generate-001': { kind: 'discrete', values: [4, 6, 8] }, - 'veo-2.0-generate-001': { kind: 'discrete', values: [5, 6, 8] }, + 'veo-3.1-lite-generate-preview': { kind: 'discrete', values: [4, 6, 8] }, } /** diff --git a/packages/ai-gemini/tests/image-adapter.test.ts b/packages/ai-gemini/tests/image-adapter.test.ts index 64d9b0a76..d08f302a2 100644 --- a/packages/ai-gemini/tests/image-adapter.test.ts +++ b/packages/ai-gemini/tests/image-adapter.test.ts @@ -14,7 +14,7 @@ describe('Gemini Image Adapter', () => { describe('createGeminiImage', () => { it('creates an adapter with the provided API key', () => { const adapter = createGeminiImage( - 'imagen-3.0-generate-002', + 'imagen-4.0-generate-001', 'test-api-key', ) expect(adapter).toBeInstanceOf(GeminiImageAdapter) @@ -24,10 +24,10 @@ describe('Gemini Image Adapter', () => { it('has the correct model', () => { const adapter = createGeminiImage( - 'imagen-3.0-generate-002', + 'imagen-4.0-generate-001', 'test-api-key', ) - expect(adapter.model).toBe('imagen-3.0-generate-002') + expect(adapter.model).toBe('imagen-4.0-generate-001') }) }) @@ -52,7 +52,7 @@ describe('Gemini Image Adapter', () => { describe('validateImageSize', () => { it('accepts valid sizes that map to aspect ratios', () => { expect(() => - validateImageSize('imagen-3.0-generate-002', '1024x1024'), + validateImageSize('imagen-4.0-generate-001', '1024x1024'), ).not.toThrow() expect(() => validateImageSize('imagen-4.0-generate-001', '1920x1080'), @@ -61,13 +61,13 @@ describe('Gemini Image Adapter', () => { it('rejects invalid sizes', () => { expect(() => - validateImageSize('imagen-3.0-generate-002', '999x999'), + validateImageSize('imagen-4.0-generate-001', '999x999'), ).toThrow() }) it('accepts undefined size', () => { expect(() => - validateImageSize('imagen-3.0-generate-002', undefined), + validateImageSize('imagen-4.0-generate-001', undefined), ).not.toThrow() }) }) @@ -75,16 +75,16 @@ describe('Gemini Image Adapter', () => { describe('validateNumberOfImages', () => { it('accepts 1-4 images', () => { expect(() => - validateNumberOfImages('imagen-3.0-generate-002', 1), + validateNumberOfImages('imagen-4.0-generate-001', 1), ).not.toThrow() expect(() => - validateNumberOfImages('imagen-3.0-generate-002', 4), + validateNumberOfImages('imagen-4.0-generate-001', 4), ).not.toThrow() }) it('rejects more than 4 images', () => { expect(() => - validateNumberOfImages('imagen-3.0-generate-002', 5), + validateNumberOfImages('imagen-4.0-generate-001', 5), ).toThrow() }) @@ -108,13 +108,13 @@ describe('Gemini Image Adapter', () => { it('rejects 0 images', () => { expect(() => - validateNumberOfImages('imagen-3.0-generate-002', 0), + validateNumberOfImages('imagen-4.0-generate-001', 0), ).toThrow() }) it('accepts undefined', () => { expect(() => - validateNumberOfImages('imagen-3.0-generate-002', undefined), + validateNumberOfImages('imagen-4.0-generate-001', undefined), ).not.toThrow() }) }) @@ -122,16 +122,16 @@ describe('Gemini Image Adapter', () => { describe('validatePrompt', () => { it('rejects empty prompts', () => { expect(() => - validatePrompt({ prompt: '', model: 'imagen-3.0-generate-002' }), + validatePrompt({ prompt: '', model: 'imagen-4.0-generate-001' }), ).toThrow() expect(() => - validatePrompt({ prompt: ' ', model: 'imagen-3.0-generate-002' }), + validatePrompt({ prompt: ' ', model: 'imagen-4.0-generate-001' }), ).toThrow() }) it('accepts non-empty prompts', () => { expect(() => - validatePrompt({ prompt: 'A cat', model: 'imagen-3.0-generate-002' }), + validatePrompt({ prompt: 'A cat', model: 'imagen-4.0-generate-001' }), ).not.toThrow() }) }) @@ -175,7 +175,7 @@ describe('Gemini Image Adapter', () => { const mockGenerateImages = vi.fn().mockResolvedValueOnce(mockResponse) const adapter = createGeminiImage( - 'imagen-3.0-generate-002', + 'imagen-4.0-generate-001', 'test-api-key', ) // Replace the internal Gemini SDK client with our mock @@ -197,7 +197,7 @@ describe('Gemini Image Adapter', () => { }) expect(mockGenerateImages).toHaveBeenCalledWith({ - model: 'imagen-3.0-generate-002', + model: 'imagen-4.0-generate-001', prompt: 'A cat wearing a hat', config: { numberOfImages: 1, @@ -205,7 +205,7 @@ describe('Gemini Image Adapter', () => { }, }) - expect(result.model).toBe('imagen-3.0-generate-002') + expect(result.model).toBe('imagen-4.0-generate-001') expect(result.images).toHaveLength(1) expect(result.images[0]!.b64Json).toBe('base64encodedimage') }) @@ -218,7 +218,7 @@ describe('Gemini Image Adapter', () => { const mockGenerateImages = vi.fn().mockResolvedValue(mockResponse) const adapter = createGeminiImage( - 'imagen-3.0-generate-002', + 'imagen-4.0-generate-001', 'test-api-key', ) ;( diff --git a/packages/ai-gemini/tests/video-adapter.test.ts b/packages/ai-gemini/tests/video-adapter.test.ts index 1e5945e01..5763d6737 100644 --- a/packages/ai-gemini/tests/video-adapter.test.ts +++ b/packages/ai-gemini/tests/video-adapter.test.ts @@ -90,21 +90,16 @@ describe('Gemini Video Adapter', () => { describe('availableDurations', () => { it('returns the discrete Veo 3.x duration set', () => { - const adapter = createGeminiVideo('veo-3.0-generate-001', 'test-key') + const adapter = createGeminiVideo( + 'veo-3.1-lite-generate-preview', + 'test-key', + ) expect(adapter.availableDurations()).toEqual({ kind: 'discrete', values: [4, 6, 8], }) }) - it('returns the discrete Veo 2 duration set', () => { - const adapter = createGeminiVideo('veo-2.0-generate-001', 'test-key') - expect(adapter.availableDurations()).toEqual({ - kind: 'discrete', - values: [5, 6, 8], - }) - }) - it('covers every model in the duration table', () => { for (const model of Object.keys( GEMINI_VIDEO_DURATIONS, @@ -116,24 +111,23 @@ describe('Gemini Video Adapter', () => { describe('snapDuration', () => { it('snaps to the closest valid duration', () => { - const adapter = createGeminiVideo('veo-3.0-generate-001', 'test-key') + const adapter = createGeminiVideo( + 'veo-3.1-lite-generate-preview', + 'test-key', + ) expect(adapter.snapDuration(3)).toBe(4) expect(adapter.snapDuration(5)).toBe(4) expect(adapter.snapDuration(7)).toBe(6) expect(adapter.snapDuration(100)).toBe(8) }) - - it('snaps Veo 2 values to its own set', () => { - const adapter = createGeminiVideo('veo-2.0-generate-001', 'test-key') - expect(adapter.snapDuration(1)).toBe(5) - expect(adapter.snapDuration(7)).toBe(6) - expect(adapter.snapDuration(9)).toBe(8) - }) }) describe('per-model duration typing', () => { it('types duration as the model-specific union at compile time', () => { - const veo3 = createGeminiVideo('veo-3.0-generate-001', 'test-key') + const veo3 = createGeminiVideo( + 'veo-3.1-lite-generate-preview', + 'test-key', + ) expectTypeOf(veo3.snapDuration).returns.toEqualTypeOf< 4 | 6 | 8 | undefined >() @@ -141,15 +135,6 @@ describe('Gemini Video Adapter', () => { expectTypeOf().toEqualTypeOf< 4 | 6 | 8 | undefined >() - - const veo2 = createGeminiVideo('veo-2.0-generate-001', 'test-key') - expectTypeOf(veo2.snapDuration).returns.toEqualTypeOf< - 5 | 6 | 8 | undefined - >() - type Veo2Options = Parameters[0] - expectTypeOf().toEqualTypeOf< - 5 | 6 | 8 | undefined - >() }) }) @@ -188,18 +173,18 @@ describe('Gemini Video Adapter', () => { it('omits aspectRatio and durationSeconds when size/duration are not given', async () => { const stub = createClientStub() const adapter = new StubbedGeminiVideoAdapter( - 'veo-2.0-generate-001', + 'veo-3.1-lite-generate-preview', stub, ) await adapter.createVideoJob({ - model: 'veo-2.0-generate-001', + model: 'veo-3.1-lite-generate-preview', prompt: 'a sunset', logger: testLogger, }) expect(stub.models.generateVideos).toHaveBeenCalledWith({ - model: 'veo-2.0-generate-001', + model: 'veo-3.1-lite-generate-preview', prompt: 'a sunset', config: {}, }) @@ -208,13 +193,13 @@ describe('Gemini Video Adapter', () => { it('throws when the operation comes back without a name', async () => { const stub = createClientStub({ createResult: {} }) const adapter = new StubbedGeminiVideoAdapter( - 'veo-3.0-generate-001', + 'veo-3.1-lite-generate-preview', stub, ) await expect( adapter.createVideoJob({ - model: 'veo-3.0-generate-001', + model: 'veo-3.1-lite-generate-preview', prompt: 'a sunset', logger: testLogger, }), @@ -292,12 +277,12 @@ describe('Gemini Video Adapter', () => { it('decodes base64 data: URI image sources', async () => { const stub = createClientStub() const adapter = new StubbedGeminiVideoAdapter( - 'veo-3.0-generate-001', + 'veo-3.1-lite-generate-preview', stub, ) await adapter.createVideoJob({ - model: 'veo-3.0-generate-001', + model: 'veo-3.1-lite-generate-preview', prompt: [ { type: 'text', content: 'animate' }, {