Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions .changeset/gemini-omni-1-1-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/ai-gemini': patch
---

Add Gemini Omni 1.1 Flash (`gemini-omni-1.1-flash`) as the GA Interactions video model.

`geminiVideo('gemini-omni-1.1-flash')` uses the existing Interactions video path. `gemini-omni-flash-preview` stays as a deprecated alias until it shuts down on 2026-09-30. `modelOptions.resolution` maps onto `response_format.resolution` (`360p` | `720p` | `1080p` | `4k`, default 720p). Duration stays 3–10 seconds per call.
2 changes: 1 addition & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@
"label": "Video Generation",
"to": "media/video-generation",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-20"
"updatedAt": "2026-08-31"
},
{
"label": "Generation Hooks",
Expand Down
23 changes: 17 additions & 6 deletions docs/media/video-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ fal is the exception: `duration` is typed from `@fal-ai/client`'s

#### Gemini Omni Flash (Interactions API) Model Options

Gemini Omni Flash (`gemini-omni-flash-preview`) is Google's multimodal
Gemini Omni Flash (`gemini-omni-1.1-flash`) is Google's multimodal
video-generation model with conversational editing. It only serves the
[Interactions API](https://ai.google.dev/gemini-api/docs/omni), and the same
`geminiVideo()` adapter routes it automatically:
Expand All @@ -620,34 +620,45 @@ video-generation model with conversational editing. It only serves the
When Google delivers by reference instead, the Files API URI passes through
and needs your API key to download, like Veo.

Clips are 720p at 24 FPS. `duration` accepts any value in the **3 to 10 second**
range (fractional seconds included), defaulting to 10 seconds when omitted:
`duration` accepts any value in the **3 to 10 second** range (fractional
seconds included), defaulting to 10 seconds when omitted:

- `availableDurations()` reports
`{ kind: 'range', min: 3, max: 10, unit: 'seconds' }`.
- Out-of-range `duration` values are rejected at job creation.
- `snapDuration(n)` snaps raw seconds into the range, clamping to its bounds and
rounding to whole seconds.

The `size` option maps onto the interaction's output aspect ratio:
The `size` option maps onto the interaction's output aspect ratio
(`'16:9'` default, or `'9:16'`). Pass `modelOptions.resolution` to set
`response_format.resolution`:

- `'360p'`
- `'720p'` (default)
- `'1080p'` (upscaled)
- `'4k'` (upscaled)

```typescript ignore
import { generateVideo, getVideoJobStatus } from "@tanstack/ai";
import { geminiVideo } from "@tanstack/ai-gemini";

const adapter = geminiVideo("gemini-omni-flash-preview");
const adapter = geminiVideo("gemini-omni-1.1-flash");

const { jobId } = await generateVideo({
adapter,
prompt: "A woman playing violin outdoors at golden hour",
size: "9:16", // aspect ratio: '16:9' (default) or '9:16'
duration: 6, // 3-10 seconds; omit for the 10s default
modelOptions: { resolution: "1080p" }, // '360p' | '720p' | '1080p' | '4k'
});

const status = await getVideoJobStatus({ adapter, jobId });
// status.url → 'data:video/mp4;base64,…' once completed
```

`gemini-omni-flash-preview` still type-checks as a deprecated alias until
it shuts down on 2026-09-30. Use `gemini-omni-1.1-flash` in new code.

Image and video prompt parts are sent to the interaction as content blocks,
grouped as images, then videos, then the text prompt (Omni doesn't use Veo's
`metadata.role` routing), so you can condition the generation on stills or short
Expand All @@ -668,7 +679,7 @@ edits the video while preserving everything you didn't mention:
import { generateVideo } from "@tanstack/ai";
import { geminiVideo } from "@tanstack/ai-gemini";

const adapter = geminiVideo("gemini-omni-flash-preview");
const adapter = geminiVideo("gemini-omni-1.1-flash");

// Turn 1: generate
const first = await generateVideo({
Expand Down
2 changes: 1 addition & 1 deletion examples/ts-react-media/src/components/OmniStudio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { MediaPromptPart } from '@tanstack/ai/client'
import { generateVideoFn } from '@/lib/server-functions'
import { readMediaFile, toImagePart, toVideoPart } from '@/lib/media'

const OMNI_MODEL = 'gemini-omni-flash-preview'
const OMNI_MODEL = 'gemini-omni-1.1-flash'
/** Omni bills per second of generated video. */
const PRICE_PER_SECOND = 0.1

Expand Down
2 changes: 1 addition & 1 deletion examples/ts-react-media/src/components/VideoGenerator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export default function VideoGenerator({
const omniInRun =
selectedModel === 'all'
? geminiModels.length > 0
: selectedModel.startsWith('gemini-omni-flash-preview')
: selectedModel.startsWith('gemini-omni')

// Each card runs its own generation, so the form is busy while any of them
// reports that it is.
Expand Down
12 changes: 6 additions & 6 deletions examples/ts-react-media/src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,18 +187,18 @@ export const VIDEO_MODELS = [
provider: 'fal' as const,
},
{
id: 'gemini-omni-flash-preview',
name: 'Gemini Omni Flash (Text-to-Video)',
id: 'gemini-omni-1.1-flash',
name: 'Gemini Omni 1.1 Flash (Text-to-Video)',
description:
'Google multimodal video generation with conversational editing, via the Interactions API (3-10s, 720p)',
'Google multimodal video generation with conversational editing, via the Interactions API (3-10s, 360p/720p/1080p/4k)',
mode: 'text-to-video' as const,
provider: 'gemini' as const,
},
{
id: 'gemini-omni-flash-preview/image-to-video',
name: 'Gemini Omni Flash (Image-to-Video)',
id: 'gemini-omni-1.1-flash/image-to-video',
name: 'Gemini Omni 1.1 Flash (Image-to-Video)',
description:
'Animate an image with Gemini Omni Flash via the Interactions API',
'Animate an image with Gemini Omni 1.1 Flash via the Interactions API',
mode: 'image-to-video' as const,
provider: 'gemini' as const,
},
Expand Down
10 changes: 5 additions & 5 deletions examples/ts-react-media/src/lib/server-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,12 @@ function videoStreamForModel(data: VideoRequest): AsyncIterable<StreamChunk> {
// Gemini Omni Flash (Interactions API, GEMINI_API_KEY). One model
// serves both UI entries; it accepts text, image, AND video prompt
// parts (sent as interaction content blocks: images, then videos,
// then text). Clips are 3–10s at 720p (default 10s when `duration`
// is omitted); `size` is the output aspect ratio. Passing
// then text). Clips are 3–10s (default 10s when `duration` is
// omitted); `size` is the output aspect ratio. Passing
// `previous_interaction_id` chains a prompt onto a prior generation
// for conversational editing.
case 'gemini-omni-flash-preview':
case 'gemini-omni-flash-preview/image-to-video': {
case 'gemini-omni-1.1-flash':
case 'gemini-omni-1.1-flash/image-to-video': {
const prompt = asOmniPrompt(data.prompt)
if (
data.model.endsWith('/image-to-video') &&
Expand All @@ -466,7 +466,7 @@ function videoStreamForModel(data: VideoRequest): AsyncIterable<StreamChunk> {
return generateVideo({
stream: true,
pollingInterval: VIDEO_POLL_INTERVAL_MS,
adapter: geminiVideo('gemini-omni-flash-preview'),
adapter: geminiVideo('gemini-omni-1.1-flash'),
prompt,
size: aspectRatio ?? '16:9',
...(duration !== undefined ? { duration } : {}),
Expand Down
48 changes: 36 additions & 12 deletions packages/ai-gemini/src/adapters/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,15 +228,18 @@ function interactionUsageToTokenUsage(
* requires the API key (`x-goog-api-key` header or `?key=` query
* parameter) to download.
*
* **Gemini Omni Flash** (`gemini-omni-flash-preview`) only serves the
* Interactions API: `createVideoJob` creates a background interaction with
* **Gemini Omni Flash** (`gemini-omni-1.1-flash`, plus the deprecated
* `gemini-omni-flash-preview` alias) only serves the Interactions API:
* `createVideoJob` creates a background interaction with
* `response_modalities: ['video']`, `getVideoStatus` polls it by id, and
* `getVideoUrl` returns the inline base64 MP4 as a `data:` URL (or the
* Files API URI when the server delivers by reference). Image and video
* prompt parts are sent as interaction content blocks, grouped as images,
* then videos, then the text prompt (interleaving is not preserved); pass
* `modelOptions.previous_interaction_id` to conversationally edit a prior
* Omni generation.
* Omni generation. `modelOptions.resolution` maps onto
* `response_format.resolution` (`'360p' | '720p' | '1080p' | '4k'`,
* default 720p).
*
* @experimental Video generation is an experimental feature and may change.
*/
Expand Down Expand Up @@ -344,9 +347,8 @@ export class GeminiVideoAdapter<
>,
): Promise<VideoJobResult> {
const { prompt, size, duration, logger } = options
const modelOptions = options.modelOptions as
| GeminiOmniVideoProviderOptions
| undefined
const { resolution, ...passthroughModelOptions } =
(options.modelOptions as GeminiOmniVideoProviderOptions | undefined) ?? {}

try {
const resolved = resolveMediaPrompt(prompt)
Expand Down Expand Up @@ -384,23 +386,25 @@ export class GeminiVideoAdapter<
)
}

// Aspect ratio and clip length ride on `response_format`. Duration is
// a `"<seconds>s"` string, accepted anywhere in the 3–10s range
// (fractional included) and defaulting to 10s when omitted — verified
// against the live API; the docs don't publish the range constraints.
// Aspect ratio, clip length, and resolution ride on `response_format`.
// Duration is a `"<seconds>s"` string, accepted anywhere in the 3–10s
// range (fractional included) and defaulting to 10s when omitted.
// Resolution is `'360p' | '720p' | '1080p' | '4k'` and defaults to
// 720p when omitted — https://ai.google.dev/gemini-api/docs/omni
const responseFormat =
size !== undefined || duration !== undefined
size !== undefined || duration !== undefined || resolution !== undefined
? {
response_format: {
type: 'video' as const,
...(size !== undefined && { aspect_ratio: size }),
...(duration !== undefined && { duration: `${duration}s` }),
...(resolution !== undefined && { resolution }),
},
}
: {}

const interaction = await this.client.interactions.create({
...modelOptions,
...passthroughModelOptions,
model: this.model,
input: [{ type: 'user_input', content }],
response_modalities: ['video'],
Expand Down Expand Up @@ -659,6 +663,12 @@ export class GeminiVideoAdapter<
}
}

/** @deprecated Shuts down 2026-09-30. Use `gemini-omni-1.1-flash`. */
export function createGeminiVideo(
model: 'gemini-omni-flash-preview',
apiKey: string,
config?: Omit<GeminiVideoConfig, 'apiKey'>,
): GeminiVideoAdapter<'gemini-omni-flash-preview'>
/**
* Creates a Gemini video adapter with an explicit API key.
* Type resolution happens here at the call site.
Expand All @@ -681,6 +691,11 @@ export class GeminiVideoAdapter<
* });
* ```
*/
export function createGeminiVideo<TModel extends GeminiVideoModel>(
model: TModel,
apiKey: string,
config?: Omit<GeminiVideoConfig, 'apiKey'>,
): GeminiVideoAdapter<TModel>
export function createGeminiVideo<TModel extends GeminiVideoModel>(
model: TModel,
apiKey: string,
Expand All @@ -689,6 +704,11 @@ export function createGeminiVideo<TModel extends GeminiVideoModel>(
return new GeminiVideoAdapter({ apiKey, ...config }, model)
}

/** @deprecated Shuts down 2026-09-30. Use `gemini-omni-1.1-flash`. */
export function geminiVideo(
model: 'gemini-omni-flash-preview',
config?: Omit<GeminiVideoConfig, 'apiKey'>,
): GeminiVideoAdapter<'gemini-omni-flash-preview'>
/**
* Creates a Gemini video adapter with automatic API key detection from environment variables.
* Type resolution happens here at the call site.
Expand Down Expand Up @@ -719,6 +739,10 @@ export function createGeminiVideo<TModel extends GeminiVideoModel>(
* const status = await getVideoJobStatus({ adapter, jobId });
* ```
*/
export function geminiVideo<TModel extends GeminiVideoModel>(
model: TModel,
config?: Omit<GeminiVideoConfig, 'apiKey'>,
): GeminiVideoAdapter<TModel>
export function geminiVideo<TModel extends GeminiVideoModel>(
model: TModel,
config?: Omit<GeminiVideoConfig, 'apiKey'>,
Expand Down
1 change: 1 addition & 0 deletions packages/ai-gemini/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export {
export type {
GeminiInteractionsVideoModel,
GeminiOmniVideoProviderOptions,
GeminiOmniVideoResolution,
GeminiVideoModel,
GeminiVideoModelDurationByName,
GeminiVideoModelInputModalitiesByName,
Expand Down
49 changes: 43 additions & 6 deletions packages/ai-gemini/src/model-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -807,11 +807,44 @@ const VEO_3_1_LITE_PREVIEW = {
>

/**
* Gemini Omni Flash — multimodal video generation with conversational
* editing. Serves only the Interactions API (`generateContent` rejects it),
* so it routes through the interactions-based path of the video adapter,
* not Veo's `:predictLongRunning` flow. Pricing is per second of generated
* video ($0.10/sec). 720p / 24 FPS, 3–10 second clips (default 10s).
* Gemini Omni 1.1 Flash — GA. Multimodal video generation with
* conversational editing. Serves only the Interactions API
* (`generateContent` rejects it), so it routes through the
* interactions-based path of the video adapter, not Veo's
* `:predictLongRunning` flow. Pricing is per second of generated video
* ($0.10/sec). 360p / 720p (default) / 1080p / 4k at 24 FPS, 3–10 second
* clips (default 10s).
* @see https://ai.google.dev/gemini-api/docs/models/gemini-omni-flash
* @experimental Omni video generation is an experimental feature and may change.
*/
const GEMINI_OMNI_1_1_FLASH = {
name: 'gemini-omni-1.1-flash',
max_input_tokens: 1_048_576,
max_output_tokens: 1,
supports: {
input: ['text', 'image', 'video'],
output: ['video', 'audio'],
},
pricing: {
input: {
normal: 0,
},
output: {
normal: 0.1,
},
},
} as const satisfies ModelMeta<
GeminiToolConfigOptions &
GeminiSafetyOptions &
GeminiCommonConfigOptions &
GeminiCachedContentOptions
>

/**
* @deprecated `gemini-omni-flash-preview` shuts down on 2026-09-30. Use the
* GA id `gemini-omni-1.1-flash` instead. Kept in the model union so existing
* code still compiles until shutdown.
* @see https://ai.google.dev/gemini-api/docs/models/gemini-omni-flash
* @experimental Omni video generation is an experimental feature and may change.
*/
const GEMINI_OMNI_FLASH_PREVIEW = {
Expand Down Expand Up @@ -1143,15 +1176,19 @@ export const GEMINI_VIDEO_MODELS = [
VEO_3_1_PREVIEW.name,
VEO_3_1_FAST_PREVIEW.name,
VEO_3_1_LITE_PREVIEW.name,
GEMINI_OMNI_1_1_FLASH.name,
// Deprecated alias — shuts down 2026-09-30.
GEMINI_OMNI_FLASH_PREVIEW.name,
] as const

/**
* Video models served by the Interactions API rather than Veo's
* `:predictLongRunning` operations flow.
* `:predictLongRunning` operations flow. GA id first; the trailing
* `-preview` id is a shutdown alias kept so existing code compiles.
* @experimental Omni video generation is an experimental feature and may change.
*/
export const GEMINI_INTERACTIONS_VIDEO_MODELS = [
GEMINI_OMNI_1_1_FLASH.name,
GEMINI_OMNI_FLASH_PREVIEW.name,
] as const

Expand Down
Loading
Loading