@tanstack/ai-gemini 0.26.4 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -15
- package/dist/esm/adapters/text.d.ts +9 -0
- package/dist/esm/adapters/text.js +165 -13
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/video.d.ts +13 -5
- package/dist/esm/adapters/video.js +14 -58
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/files/index.d.ts +51 -0
- package/dist/esm/files/index.js +59 -0
- package/dist/esm/files/index.js.map +1 -0
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.js +3 -2
- package/dist/esm/message-types.d.ts +36 -0
- package/dist/esm/model-meta.d.ts +7 -6
- package/dist/esm/model-meta.js +39 -7
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/tools/tool-converter.js +1 -1
- package/dist/esm/tools/tool-converter.js.map +1 -1
- package/dist/esm/video/video-provider-options.d.ts +32 -7
- package/dist/esm/video/video-provider-options.js +20 -1
- package/dist/esm/video/video-provider-options.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/text.ts +253 -20
- package/src/adapters/video.ts +43 -12
- package/src/files/index.ts +114 -0
- package/src/index.ts +12 -0
- package/src/message-types.ts +37 -0
- package/src/model-meta.ts +47 -6
- package/src/tools/tool-converter.ts +3 -7
- package/src/video/video-provider-options.ts +52 -7
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createGeminiClient, getGeminiApiKeyFromEnv } from "../utils/client.js";
|
|
2
2
|
import "../utils/index.js";
|
|
3
|
-
import { getGeminiVideoDurationOptions, isInteractionsVideoModel } from "../video/video-provider-options.js";
|
|
3
|
+
import { getGeminiVideoDurationOptions, isInteractionsVideoModel, parseGeminiOmniVideoSize } from "../video/video-provider-options.js";
|
|
4
4
|
import { GenerateVideosOperation, VideoGenerationReferenceType } from "@google/genai";
|
|
5
5
|
import { resolveMediaPrompt } from "@tanstack/ai";
|
|
6
6
|
import { BaseVideoAdapter, snapToDurationOption } from "@tanstack/ai/adapters";
|
|
@@ -139,15 +139,19 @@ function interactionUsageToTokenUsage(usage) {
|
|
|
139
139
|
* requires the API key (`x-goog-api-key` header or `?key=` query
|
|
140
140
|
* parameter) to download.
|
|
141
141
|
*
|
|
142
|
-
* **Gemini Omni Flash** (`gemini-omni-flash
|
|
143
|
-
*
|
|
142
|
+
* **Gemini Omni Flash** (`gemini-omni-1.1-flash`, plus the deprecated
|
|
143
|
+
* `gemini-omni-flash-preview` alias) only serves the Interactions API:
|
|
144
|
+
* `createVideoJob` creates a background interaction with
|
|
144
145
|
* `response_modalities: ['video']`, `getVideoStatus` polls it by id, and
|
|
145
146
|
* `getVideoUrl` returns the inline base64 MP4 as a `data:` URL (or the
|
|
146
147
|
* Files API URI when the server delivers by reference). Image and video
|
|
147
148
|
* prompt parts are sent as interaction content blocks, grouped as images,
|
|
148
149
|
* then videos, then the text prompt (interleaving is not preserved); pass
|
|
149
150
|
* `modelOptions.previous_interaction_id` to conversationally edit a prior
|
|
150
|
-
* Omni generation.
|
|
151
|
+
* Omni generation. `size` is an `aspectRatio_resolution` template
|
|
152
|
+
* (`'16:9'` or `'16:9_1080p'`); the optional suffix maps onto
|
|
153
|
+
* `response_format.resolution` (`'360p' | '720p' | '1080p' | '4k'`,
|
|
154
|
+
* default 720p).
|
|
151
155
|
*
|
|
152
156
|
* @experimental Video generation is an experimental feature and may change.
|
|
153
157
|
*/
|
|
@@ -218,9 +222,13 @@ var GeminiVideoAdapter = class extends BaseVideoAdapter {
|
|
|
218
222
|
if (content.length === 0) throw new Error(`${this.name}.createVideoJob: the prompt produced no content to send (model: ${this.model}).`);
|
|
219
223
|
const durations = this.availableDurations();
|
|
220
224
|
if (duration !== void 0 && durations.kind === "range" && (duration < durations.min || duration > durations.max)) throw new Error(`${this.name}.createVideoJob: duration ${duration}s is outside the ${durations.min}–${durations.max}s range supported by ${this.model}. Use snapDuration() to snap arbitrary values into range.`);
|
|
221
|
-
const
|
|
225
|
+
const parsedSize = size !== void 0 ? parseGeminiOmniVideoSize(size) : void 0;
|
|
226
|
+
const responseFormat = parsedSize !== void 0 || duration !== void 0 ? { response_format: {
|
|
222
227
|
type: "video",
|
|
223
|
-
...
|
|
228
|
+
...parsedSize !== void 0 && {
|
|
229
|
+
aspect_ratio: parsedSize.aspectRatio,
|
|
230
|
+
...parsedSize.resolution !== void 0 && { resolution: parsedSize.resolution }
|
|
231
|
+
},
|
|
224
232
|
...duration !== void 0 && { duration: `${duration}s` }
|
|
225
233
|
} } : {};
|
|
226
234
|
const interaction = await this.client.interactions.create({
|
|
@@ -407,64 +415,12 @@ var GeminiVideoAdapter = class extends BaseVideoAdapter {
|
|
|
407
415
|
return await this.client.interactions.get(jobId);
|
|
408
416
|
}
|
|
409
417
|
};
|
|
410
|
-
/**
|
|
411
|
-
* Creates a Gemini video adapter with an explicit API key.
|
|
412
|
-
* Type resolution happens here at the call site.
|
|
413
|
-
*
|
|
414
|
-
* @experimental Video generation is an experimental feature and may change.
|
|
415
|
-
*
|
|
416
|
-
* @param model - The model name (e.g., 'veo-3.1-generate-preview')
|
|
417
|
-
* @param apiKey - Your Google API key
|
|
418
|
-
* @param config - Optional additional configuration
|
|
419
|
-
* @returns Configured Gemini video adapter instance with resolved types
|
|
420
|
-
*
|
|
421
|
-
* @example
|
|
422
|
-
* ```typescript
|
|
423
|
-
* const adapter = createGeminiVideo('veo-3.1-generate-preview', 'your-api-key');
|
|
424
|
-
*
|
|
425
|
-
* const { jobId } = await generateVideo({
|
|
426
|
-
* adapter,
|
|
427
|
-
* prompt: 'A beautiful sunset over the ocean',
|
|
428
|
-
* duration: adapter.snapDuration(7), // → 6
|
|
429
|
-
* });
|
|
430
|
-
* ```
|
|
431
|
-
*/
|
|
432
418
|
function createGeminiVideo(model, apiKey, config) {
|
|
433
419
|
return new GeminiVideoAdapter({
|
|
434
420
|
apiKey,
|
|
435
421
|
...config
|
|
436
422
|
}, model);
|
|
437
423
|
}
|
|
438
|
-
/**
|
|
439
|
-
* Creates a Gemini video adapter with automatic API key detection from environment variables.
|
|
440
|
-
* Type resolution happens here at the call site.
|
|
441
|
-
*
|
|
442
|
-
* Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:
|
|
443
|
-
* - `process.env` (Node.js)
|
|
444
|
-
* - `window.env` (Browser with injected env)
|
|
445
|
-
*
|
|
446
|
-
* @experimental Video generation is an experimental feature and may change.
|
|
447
|
-
*
|
|
448
|
-
* @param model - The model name (e.g., 'veo-3.1-generate-preview')
|
|
449
|
-
* @param config - Optional configuration (excluding apiKey which is auto-detected)
|
|
450
|
-
* @returns Configured Gemini video adapter instance with resolved types
|
|
451
|
-
* @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment
|
|
452
|
-
*
|
|
453
|
-
* @example
|
|
454
|
-
* ```typescript
|
|
455
|
-
* // Automatically uses GOOGLE_API_KEY from environment
|
|
456
|
-
* const adapter = geminiVideo('veo-3.1-generate-preview');
|
|
457
|
-
*
|
|
458
|
-
* // Create a video generation job
|
|
459
|
-
* const { jobId } = await generateVideo({
|
|
460
|
-
* adapter,
|
|
461
|
-
* prompt: 'A cat playing piano'
|
|
462
|
-
* });
|
|
463
|
-
*
|
|
464
|
-
* // Poll for status
|
|
465
|
-
* const status = await getVideoJobStatus({ adapter, jobId });
|
|
466
|
-
* ```
|
|
467
|
-
*/
|
|
468
424
|
function geminiVideo(model, config) {
|
|
469
425
|
return createGeminiVideo(model, getGeminiApiKeyFromEnv(), config);
|
|
470
426
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","names":[],"sources":["../../../src/adapters/video.ts"],"sourcesContent":["import {\n GenerateVideosOperation,\n VideoGenerationReferenceType,\n} from '@google/genai'\nimport { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseVideoAdapter, snapToDurationOption } from '@tanstack/ai/adapters'\nimport { arrayBufferToBase64 } from '@tanstack/ai-utils'\nimport { createGeminiClient, getGeminiApiKeyFromEnv } from '../utils'\nimport {\n getGeminiVideoDurationOptions,\n isInteractionsVideoModel,\n} from '../video/video-provider-options'\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type {\n ImagePart,\n MediaInputMetadata,\n TokenUsage,\n VideoGenerationOptions,\n VideoJobResult,\n VideoPart,\n VideoStatusResult,\n VideoUrlResult,\n} from '@tanstack/ai'\nimport type {\n GenerateVideosConfig,\n GoogleGenAI,\n Image,\n Interactions,\n VideoGenerationReferenceImage,\n} from '@google/genai'\nimport type {\n GeminiOmniVideoProviderOptions,\n GeminiVideoModel,\n GeminiVideoModelDurationByName,\n GeminiVideoModelInputModalitiesByName,\n GeminiVideoModelProviderOptionsByName,\n GeminiVideoModelSizeByName,\n GeminiVideoProviderOptions,\n GeminiVideoSize,\n} from '../video/video-provider-options'\nimport type { GeminiClientConfig } from '../utils/client'\n\ntype Interaction = Interactions.Interaction\ntype InteractionContent = Interactions.Content\n\n/**\n * Configuration for Gemini video adapter.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface GeminiVideoConfig extends GeminiClientConfig {\n /**\n * Opt into fetching HTTP(S) image URL inputs. Veo's predict API accepts\n * only inline `imageBytes` or a `gcsUri`, so an HTTP(S) URL has to be\n * downloaded and base64-encoded locally — which buffers the whole image in\n * memory and can OOM constrained runtimes (e.g. Cloudflare Workers). When\n * `false` (the default), HTTP(S) URL image inputs throw; pass a `data:` URI\n * or a `gs://` reference, or set this to `true` to opt into buffering.\n */\n allowUrlFetch?: boolean\n}\n\n/**\n * Extract a human-readable message from a long-running operation's error,\n * which the SDK types as `Record<string, unknown>` (a google.rpc.Status).\n */\nfunction operationErrorMessage(error: Record<string, unknown>): string {\n if (typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n return JSON.stringify(error)\n}\n\n/**\n * Convert a TanStack image prompt part into the genai `Image` shape Veo\n * accepts: base64 `imageBytes` (data sources, data: URIs, fetched HTTP\n * URLs) or a `gcsUri` passthrough for Cloud Storage references.\n *\n * Unlike `generateContent` (chat / native image generation), Veo's predict\n * API has no `fileData.fileUri` equivalent — `Image` only accepts\n * `imageBytes` or `gcsUri`. An HTTP(S) URL therefore has to be fetched and\n * inlined locally, which buffers the whole image in memory; that only happens\n * when the caller opts in via `allowUrlFetch`, otherwise it throws. Prefer a\n * `gs://` reference on memory-constrained runtimes.\n */\nasync function imagePartToVeoImage(\n part: ImagePart<MediaInputMetadata>,\n allowUrlFetch: boolean,\n): Promise<Image> {\n if (part.source.type === 'data') {\n return {\n imageBytes: part.source.value,\n mimeType: part.source.mimeType || 'image/png',\n }\n }\n const url = part.source.value\n if (url.startsWith('gs://')) {\n return {\n gcsUri: url,\n ...(part.source.mimeType && { mimeType: part.source.mimeType }),\n }\n }\n if (url.startsWith('data:')) {\n const match = url.match(/^data:([^;,]+)?(;base64)?,(.*)$/)\n if (!match || !match[2]) {\n throw new Error(\n 'gemini: only base64 data: URIs are supported for video image inputs.',\n )\n }\n return {\n imageBytes: match[3] ?? '',\n mimeType: match[1] || part.source.mimeType || 'image/png',\n }\n }\n if (!allowUrlFetch) {\n throw new Error(\n `gemini Veo: HTTP(S) URL image inputs are not fetched by default because ` +\n `Veo accepts only inline bytes, so the image would be downloaded and ` +\n `buffered in memory (risking OOM on constrained runtimes). Pass a ` +\n `data: URI or a gs:// reference, or set \\`allowUrlFetch: true\\` on the ` +\n `adapter config to opt into fetching. URL: ${url}`,\n )\n }\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image input (${response.status} ${response.statusText}): ${url}`,\n )\n }\n const blob = await response.blob()\n const buffer = await blob.arrayBuffer()\n return {\n imageBytes: arrayBufferToBase64(buffer),\n mimeType: part.source.mimeType || blob.type || 'image/png',\n }\n}\n\n/**\n * Convert an image or video prompt part into an Interactions API content\n * block. Data sources become inline base64 `data`; URL sources pass through\n * as `uri` (Files API URIs — mirrors the Interactions text adapter).\n */\nfunction mediaPartToInteractionsContent(\n part: ImagePart<MediaInputMetadata> | VideoPart<MediaInputMetadata>,\n): InteractionContent {\n const mimeType = part.source.mimeType\n if (part.type === 'image') {\n return part.source.type === 'data'\n ? { type: 'image', data: part.source.value, mime_type: mimeType }\n : { type: 'image', uri: part.source.value, mime_type: mimeType }\n }\n return part.source.type === 'data'\n ? { type: 'video', data: part.source.value, mime_type: mimeType }\n : { type: 'video', uri: part.source.value, mime_type: mimeType }\n}\n\n/**\n * Pull the generated video out of a completed interaction. Prefers the\n * SDK's `output_video` sugar, then walks `steps` back-to-front for the last\n * `model_output` step carrying a video content block (the wire shape the\n * raw REST response uses).\n */\nfunction extractInteractionVideo(\n interaction: Interaction,\n): { data?: string; uri?: string; mimeType: string } | undefined {\n const direct = interaction.output_video\n if (direct && (direct.data || direct.uri)) {\n return {\n data: direct.data,\n uri: direct.uri,\n mimeType: direct.mime_type || 'video/mp4',\n }\n }\n const steps = interaction.steps ?? []\n for (let i = steps.length - 1; i >= 0; i--) {\n const step = steps[i]\n if (step?.type !== 'model_output') continue\n for (const block of step.content ?? []) {\n if (block.type === 'video' && (block.data || block.uri)) {\n return {\n data: block.data,\n uri: block.uri,\n mimeType: block.mime_type || 'video/mp4',\n }\n }\n }\n }\n return undefined\n}\n\n/**\n * Map Interactions usage onto the canonical TokenUsage shape. Omni reports\n * video output via `output_tokens_by_modality`; fall back to the video\n * modality entry when the total is absent.\n */\nfunction interactionUsageToTokenUsage(\n usage: Interaction['usage'],\n): TokenUsage | undefined {\n if (!usage) return undefined\n const videoTokens = usage.output_tokens_by_modality?.find(\n (entry) => entry.modality === 'video',\n )?.tokens\n const promptTokens = usage.total_input_tokens ?? 0\n const completionTokens = usage.total_output_tokens ?? videoTokens ?? 0\n return {\n promptTokens,\n completionTokens,\n totalTokens: usage.total_tokens ?? promptTokens + completionTokens,\n }\n}\n\n/**\n * Gemini Video Generation Adapter (Veo + Gemini Omni Flash)\n *\n * Tree-shakeable adapter for Google video generation, routing by model:\n *\n * **Veo models** run as a long-running operation: `createVideoJob` starts\n * the operation via the `:predictLongRunning` endpoint, `getVideoStatus`\n * polls it, and `getVideoUrl` extracts the generated video's URI once it\n * completes. Image prompt parts are routed by `metadata.role`:\n * - `'start_frame'` (or the first un-roled image) → the input image the\n * video starts from\n * - `'end_frame'` → `lastFrame` (the frame the video ends on)\n * - `'reference'` / `'character'` → `referenceImages` (asset references,\n * Veo 3.1)\n *\n * Note: the returned Veo video URI is served by the Gemini Files API and\n * requires the API key (`x-goog-api-key` header or `?key=` query\n * parameter) to download.\n *\n * **Gemini Omni Flash** (`gemini-omni-flash-preview`) only serves the\n * Interactions API: `createVideoJob` creates a background interaction with\n * `response_modalities: ['video']`, `getVideoStatus` polls it by id, and\n * `getVideoUrl` returns the inline base64 MP4 as a `data:` URL (or the\n * Files API URI when the server delivers by reference). Image and video\n * prompt parts are sent as interaction content blocks, grouped as images,\n * then videos, then the text prompt (interleaving is not preserved); pass\n * `modelOptions.previous_interaction_id` to conversationally edit a prior\n * Omni generation.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport class GeminiVideoAdapter<\n TModel extends GeminiVideoModel,\n> extends BaseVideoAdapter<\n TModel,\n GeminiVideoModelProviderOptionsByName[TModel],\n GeminiVideoModelProviderOptionsByName,\n GeminiVideoModelSizeByName,\n GeminiVideoModelInputModalitiesByName,\n GeminiVideoModelDurationByName\n> {\n readonly name = 'gemini' as const\n\n protected client: GoogleGenAI\n private readonly allowUrlFetch: boolean\n\n constructor(config: GeminiVideoConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n this.allowUrlFetch = config.allowUrlFetch ?? false\n }\n\n async createVideoJob(\n options: VideoGenerationOptions<\n GeminiVideoModelProviderOptionsByName[TModel],\n GeminiVideoSize,\n GeminiVideoModelDurationByName[TModel]\n >,\n ): Promise<VideoJobResult> {\n const { prompt, size, duration, logger } = options\n\n logger.request(\n `activity=video.create provider=${this.name} model=${this.model} size=${size ?? 'default'} duration=${duration ?? 'default'}`,\n { provider: this.name, model: this.model },\n )\n\n if (isInteractionsVideoModel(this.model)) {\n return await this.createInteractionsVideoJob(options)\n }\n const modelOptions = options.modelOptions as\n | GeminiVideoProviderOptions\n | undefined\n\n try {\n const resolved = resolveMediaPrompt(prompt)\n\n if (resolved.videos.length > 0) {\n throw new Error(\n `${this.name}.createVideoJob does not support video prompt parts (model: ${this.model}).`,\n )\n }\n if (resolved.audios.length > 0) {\n throw new Error(\n `${this.name}.createVideoJob does not support audio prompt parts (model: ${this.model}).`,\n )\n }\n\n const { image, lastFrame, referenceImages } = await this.routeImageParts(\n resolved.images,\n )\n\n const config: GenerateVideosConfig = {\n ...modelOptions,\n ...(size !== undefined && { aspectRatio: size }),\n ...(duration !== undefined && { durationSeconds: duration }),\n ...(lastFrame && { lastFrame }),\n ...(referenceImages.length > 0 && { referenceImages }),\n }\n\n const operation = await this.client.models.generateVideos({\n model: this.model,\n prompt: resolved.text,\n ...(image && { image }),\n config,\n })\n\n if (!operation.name) {\n throw new Error(\n 'Veo did not return an operation name for the video generation job.',\n )\n }\n\n return { jobId: operation.name, model: this.model }\n } catch (error) {\n logger.errors(`${this.name}.createVideoJob fatal`, {\n error,\n source: `${this.name}.createVideoJob`,\n })\n throw error\n }\n }\n\n /**\n * Gemini Omni Flash job creation via the Interactions API. Creates a\n * background interaction requesting video output; the interaction id is\n * the job id polled by `getVideoStatus` / `getVideoUrl`.\n */\n private async createInteractionsVideoJob(\n options: VideoGenerationOptions<\n GeminiVideoModelProviderOptionsByName[TModel],\n GeminiVideoSize,\n GeminiVideoModelDurationByName[TModel]\n >,\n ): Promise<VideoJobResult> {\n const { prompt, size, duration, logger } = options\n const modelOptions = options.modelOptions as\n | GeminiOmniVideoProviderOptions\n | undefined\n\n try {\n const resolved = resolveMediaPrompt(prompt)\n\n if (resolved.audios.length > 0) {\n throw new Error(\n `${this.name}.createVideoJob does not support audio prompt parts (model: ${this.model}).`,\n )\n }\n\n const content: Array<InteractionContent> = [\n ...resolved.images.map(mediaPartToInteractionsContent),\n ...resolved.videos.map(mediaPartToInteractionsContent),\n ]\n if (resolved.text) {\n content.push({ type: 'text', text: resolved.text })\n }\n if (content.length === 0) {\n throw new Error(\n `${this.name}.createVideoJob: the prompt produced no content to send (model: ${this.model}).`,\n )\n }\n\n // Reject out-of-range durations locally rather than snapping (which\n // would silently change the clip length the caller asked for) or\n // letting the live API reject them after the round trip.\n const durations = this.availableDurations()\n if (\n duration !== undefined &&\n durations.kind === 'range' &&\n (duration < durations.min || duration > durations.max)\n ) {\n throw new Error(\n `${this.name}.createVideoJob: duration ${duration}s is outside the ${durations.min}–${durations.max}s range supported by ${this.model}. Use snapDuration() to snap arbitrary values into range.`,\n )\n }\n\n // Aspect ratio and clip length ride on `response_format`. Duration is\n // a `\"<seconds>s\"` string, accepted anywhere in the 3–10s range\n // (fractional included) and defaulting to 10s when omitted — verified\n // against the live API; the docs don't publish the range constraints.\n const responseFormat =\n size !== undefined || duration !== undefined\n ? {\n response_format: {\n type: 'video' as const,\n ...(size !== undefined && { aspect_ratio: size }),\n ...(duration !== undefined && { duration: `${duration}s` }),\n },\n }\n : {}\n\n const interaction = await this.client.interactions.create({\n ...modelOptions,\n model: this.model,\n input: [{ type: 'user_input', content }],\n response_modalities: ['video'],\n background: true,\n ...responseFormat,\n })\n\n if (!interaction.id) {\n throw new Error(\n 'Gemini Omni did not return an interaction id for the video generation job.',\n )\n }\n\n return { jobId: interaction.id, model: this.model }\n } catch (error) {\n logger.errors(`${this.name}.createVideoJob fatal`, {\n error,\n source: `${this.name}.createVideoJob`,\n })\n throw error\n }\n }\n\n /**\n * Route image prompt parts onto Veo's request fields by `metadata.role`.\n */\n private async routeImageParts(\n parts: Array<ImagePart<MediaInputMetadata>>,\n ): Promise<{\n image: Image | undefined\n lastFrame: Image | undefined\n referenceImages: Array<VideoGenerationReferenceImage>\n }> {\n let image: Image | undefined\n let lastFrame: Image | undefined\n const referenceImages: Array<VideoGenerationReferenceImage> = []\n\n for (const part of parts) {\n const role = part.metadata?.role\n switch (role) {\n case 'end_frame': {\n if (lastFrame) {\n throw new Error(\n `${this.name}: Veo accepts at most one 'end_frame' image.`,\n )\n }\n lastFrame = await imagePartToVeoImage(part, this.allowUrlFetch)\n break\n }\n case 'reference':\n case 'character': {\n referenceImages.push({\n image: await imagePartToVeoImage(part, this.allowUrlFetch),\n referenceType: VideoGenerationReferenceType.ASSET,\n })\n break\n }\n case 'start_frame':\n case undefined: {\n if (image) {\n throw new Error(\n `${this.name}: Veo accepts at most one starting image; received multiple 'start_frame'/un-roled images. Use metadata.role ('end_frame', 'reference') to disambiguate the others.`,\n )\n }\n image = await imagePartToVeoImage(part, this.allowUrlFetch)\n break\n }\n case 'mask':\n case 'control':\n throw new Error(\n `${this.name}: unsupported image role \"${role}\" for Veo video generation.`,\n )\n }\n }\n\n return { image, lastFrame, referenceImages }\n }\n\n async getVideoStatus(jobId: string): Promise<VideoStatusResult> {\n if (isInteractionsVideoModel(this.model)) {\n return await this.getInteractionsVideoStatus(jobId)\n }\n const operation = await this.getOperation(jobId)\n\n if (!operation.done) {\n return { jobId, status: 'processing' }\n }\n\n if (operation.error) {\n return {\n jobId,\n status: 'failed',\n error: operationErrorMessage(operation.error),\n }\n }\n\n // The operation can finish \"successfully\" with every sample dropped by\n // Responsible-AI filters — surface that as a failure instead of letting\n // getVideoUrl() throw on an empty response.\n const videos = operation.response?.generatedVideos ?? []\n if (videos.length === 0) {\n const reasons = operation.response?.raiMediaFilteredReasons\n return {\n jobId,\n status: 'failed',\n error: reasons?.length\n ? `Video was filtered by Responsible-AI: ${reasons.join('; ')}`\n : 'Veo returned no generated videos.',\n }\n }\n\n return { jobId, status: 'completed' }\n }\n\n /**\n * Poll an Omni background interaction. `in_progress` maps to\n * 'processing'; a `completed` interaction with no video content (e.g.\n * filtered output) is surfaced as a failure so `getVideoUrl` doesn't\n * throw on an empty response. `requires_action` also fails: the adapter\n * never sends tools, so it can only arise via\n * `previous_interaction_id` chaining onto a tool-bearing interaction —\n * and such an interaction never progresses without a client response,\n * so polling it would spin until timeout.\n */\n private async getInteractionsVideoStatus(\n jobId: string,\n ): Promise<VideoStatusResult> {\n const interaction = await this.getInteraction(jobId)\n const status = interaction.status\n\n if (status === 'in_progress') {\n return { jobId, status: 'processing' }\n }\n if (status === 'requires_action') {\n return {\n jobId,\n status: 'failed',\n error:\n 'Gemini Omni interaction is waiting on a client action (tool response), which the video jobs flow does not support.',\n }\n }\n if (status === 'completed') {\n if (!extractInteractionVideo(interaction)) {\n return {\n jobId,\n status: 'failed',\n error:\n 'Gemini Omni completed the interaction without returning a video (the output may have been filtered).',\n }\n }\n return { jobId, status: 'completed' }\n }\n return {\n jobId,\n status: 'failed',\n error: `Gemini Omni video generation ended with status \"${status}\".`,\n }\n }\n\n async getVideoUrl(jobId: string): Promise<VideoUrlResult> {\n if (isInteractionsVideoModel(this.model)) {\n return await this.getInteractionsVideoUrl(jobId)\n }\n const operation = await this.getOperation(jobId)\n\n if (!operation.done) {\n throw new Error(\n `Video is not ready yet. Check status first. Job ID: ${jobId}`,\n )\n }\n\n if (operation.error) {\n throw new Error(\n `Video generation failed: ${operationErrorMessage(operation.error)}`,\n )\n }\n\n const uri = operation.response?.generatedVideos?.[0]?.video?.uri\n if (!uri) {\n const reasons = operation.response?.raiMediaFilteredReasons\n throw new Error(\n reasons?.length\n ? `Video was filtered by Responsible-AI: ${reasons.join('; ')}`\n : `Video URL not found in operation response. Job ID: ${jobId}`,\n )\n }\n\n return { jobId, url: uri }\n }\n\n /**\n * Extract the finished Omni video. Inline base64 output (the API default)\n * becomes a `data:` URL — matching the OpenAI Sora adapter's inline\n * delivery — and URI delivery passes through (Files API URIs need the API\n * key to download, like Veo). Usage carries the video-modality output\n * tokens (Omni bills per second of video, reported as tokens).\n */\n private async getInteractionsVideoUrl(\n jobId: string,\n ): Promise<VideoUrlResult> {\n const interaction = await this.getInteraction(jobId)\n const status = interaction.status\n\n if (status === 'in_progress') {\n throw new Error(\n `Video is not ready yet. Check status first. Job ID: ${jobId}`,\n )\n }\n if (status !== 'completed') {\n throw new Error(\n `Video generation failed: Gemini Omni interaction ended with status \"${status}\". Job ID: ${jobId}`,\n )\n }\n\n const video = extractInteractionVideo(interaction)\n if (!video) {\n throw new Error(\n `Video not found in interaction response (the output may have been filtered). Job ID: ${jobId}`,\n )\n }\n\n const usage = interactionUsageToTokenUsage(interaction.usage)\n const url = video.uri ?? `data:${video.mimeType};base64,${video.data}`\n return { jobId, url, ...(usage && { usage }) }\n }\n\n override availableDurations(): DurationOptions<\n GeminiVideoModelDurationByName[TModel]\n > {\n return getGeminiVideoDurationOptions(this.model)\n }\n\n override snapDuration(\n seconds: number,\n ): GeminiVideoModelDurationByName[TModel] | undefined {\n return snapToDurationOption(seconds, this.availableDurations())\n }\n\n /**\n * Fetch the long-running operation by name. The SDK's\n * `operations.getVideosOperation` needs a real `GenerateVideosOperation`\n * instance (it calls `_fromAPIResponse` on it), so reconstruct one from\n * the job ID rather than passing an object literal.\n */\n private async getOperation(jobId: string): Promise<GenerateVideosOperation> {\n const operation = new GenerateVideosOperation()\n operation.name = jobId\n return await this.client.operations.getVideosOperation({ operation })\n }\n\n /**\n * Fetch an Omni background interaction by id.\n */\n private async getInteraction(jobId: string): Promise<Interaction> {\n return await this.client.interactions.get(jobId)\n }\n}\n\n/**\n * Creates a Gemini video adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @param model - The model name (e.g., 'veo-3.1-generate-preview')\n * @param apiKey - Your Google API key\n * @param config - Optional additional configuration\n * @returns Configured Gemini video adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGeminiVideo('veo-3.1-generate-preview', 'your-api-key');\n *\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: 'A beautiful sunset over the ocean',\n * duration: adapter.snapDuration(7), // → 6\n * });\n * ```\n */\nexport function createGeminiVideo<TModel extends GeminiVideoModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiVideoConfig, 'apiKey'>,\n): GeminiVideoAdapter<TModel> {\n return new GeminiVideoAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini video adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @param model - The model name (e.g., 'veo-3.1-generate-preview')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Gemini video adapter instance with resolved types\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses GOOGLE_API_KEY from environment\n * const adapter = geminiVideo('veo-3.1-generate-preview');\n *\n * // Create a video generation job\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: 'A cat playing piano'\n * });\n *\n * // Poll for status\n * const status = await getVideoJobStatus({ adapter, jobId });\n * ```\n */\nexport function geminiVideo<TModel extends GeminiVideoModel>(\n model: TModel,\n config?: Omit<GeminiVideoConfig, 'apiKey'>,\n): GeminiVideoAdapter<TModel> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiVideo(model, apiKey, config)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"video.js","names":[],"sources":["../../../src/adapters/video.ts"],"sourcesContent":["import {\n GenerateVideosOperation,\n VideoGenerationReferenceType,\n} from '@google/genai'\nimport { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseVideoAdapter, snapToDurationOption } from '@tanstack/ai/adapters'\nimport { arrayBufferToBase64 } from '@tanstack/ai-utils'\nimport { createGeminiClient, getGeminiApiKeyFromEnv } from '../utils'\nimport {\n getGeminiVideoDurationOptions,\n isInteractionsVideoModel,\n parseGeminiOmniVideoSize,\n} from '../video/video-provider-options'\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type {\n ImagePart,\n MediaInputMetadata,\n TokenUsage,\n VideoGenerationOptions,\n VideoJobResult,\n VideoPart,\n VideoStatusResult,\n VideoUrlResult,\n} from '@tanstack/ai'\nimport type {\n GenerateVideosConfig,\n GoogleGenAI,\n Image,\n Interactions,\n VideoGenerationReferenceImage,\n} from '@google/genai'\nimport type {\n GeminiOmniVideoProviderOptions,\n GeminiVideoModel,\n GeminiVideoModelDurationByName,\n GeminiVideoModelInputModalitiesByName,\n GeminiVideoModelProviderOptionsByName,\n GeminiVideoModelSizeByName,\n GeminiVideoProviderOptions,\n} from '../video/video-provider-options'\nimport type { GeminiClientConfig } from '../utils/client'\n\ntype Interaction = Interactions.Interaction\ntype InteractionContent = Interactions.Content\n\n/**\n * Configuration for Gemini video adapter.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface GeminiVideoConfig extends GeminiClientConfig {\n /**\n * Opt into fetching HTTP(S) image URL inputs. Veo's predict API accepts\n * only inline `imageBytes` or a `gcsUri`, so an HTTP(S) URL has to be\n * downloaded and base64-encoded locally — which buffers the whole image in\n * memory and can OOM constrained runtimes (e.g. Cloudflare Workers). When\n * `false` (the default), HTTP(S) URL image inputs throw; pass a `data:` URI\n * or a `gs://` reference, or set this to `true` to opt into buffering.\n */\n allowUrlFetch?: boolean\n}\n\n/**\n * Extract a human-readable message from a long-running operation's error,\n * which the SDK types as `Record<string, unknown>` (a google.rpc.Status).\n */\nfunction operationErrorMessage(error: Record<string, unknown>): string {\n if (typeof error.message === 'string' && error.message.length > 0) {\n return error.message\n }\n return JSON.stringify(error)\n}\n\n/**\n * Convert a TanStack image prompt part into the genai `Image` shape Veo\n * accepts: base64 `imageBytes` (data sources, data: URIs, fetched HTTP\n * URLs) or a `gcsUri` passthrough for Cloud Storage references.\n *\n * Unlike `generateContent` (chat / native image generation), Veo's predict\n * API has no `fileData.fileUri` equivalent — `Image` only accepts\n * `imageBytes` or `gcsUri`. An HTTP(S) URL therefore has to be fetched and\n * inlined locally, which buffers the whole image in memory; that only happens\n * when the caller opts in via `allowUrlFetch`, otherwise it throws. Prefer a\n * `gs://` reference on memory-constrained runtimes.\n */\nasync function imagePartToVeoImage(\n part: ImagePart<MediaInputMetadata>,\n allowUrlFetch: boolean,\n): Promise<Image> {\n if (part.source.type === 'data') {\n return {\n imageBytes: part.source.value,\n mimeType: part.source.mimeType || 'image/png',\n }\n }\n const url = part.source.value\n if (url.startsWith('gs://')) {\n return {\n gcsUri: url,\n ...(part.source.mimeType && { mimeType: part.source.mimeType }),\n }\n }\n if (url.startsWith('data:')) {\n const match = url.match(/^data:([^;,]+)?(;base64)?,(.*)$/)\n if (!match || !match[2]) {\n throw new Error(\n 'gemini: only base64 data: URIs are supported for video image inputs.',\n )\n }\n return {\n imageBytes: match[3] ?? '',\n mimeType: match[1] || part.source.mimeType || 'image/png',\n }\n }\n if (!allowUrlFetch) {\n throw new Error(\n `gemini Veo: HTTP(S) URL image inputs are not fetched by default because ` +\n `Veo accepts only inline bytes, so the image would be downloaded and ` +\n `buffered in memory (risking OOM on constrained runtimes). Pass a ` +\n `data: URI or a gs:// reference, or set \\`allowUrlFetch: true\\` on the ` +\n `adapter config to opt into fetching. URL: ${url}`,\n )\n }\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image input (${response.status} ${response.statusText}): ${url}`,\n )\n }\n const blob = await response.blob()\n const buffer = await blob.arrayBuffer()\n return {\n imageBytes: arrayBufferToBase64(buffer),\n mimeType: part.source.mimeType || blob.type || 'image/png',\n }\n}\n\n/**\n * Convert an image or video prompt part into an Interactions API content\n * block. Data sources become inline base64 `data`; URL sources pass through\n * as `uri` (Files API URIs — mirrors the Interactions text adapter).\n */\nfunction mediaPartToInteractionsContent(\n part: ImagePart<MediaInputMetadata> | VideoPart<MediaInputMetadata>,\n): InteractionContent {\n const mimeType = part.source.mimeType\n if (part.type === 'image') {\n return part.source.type === 'data'\n ? { type: 'image', data: part.source.value, mime_type: mimeType }\n : { type: 'image', uri: part.source.value, mime_type: mimeType }\n }\n return part.source.type === 'data'\n ? { type: 'video', data: part.source.value, mime_type: mimeType }\n : { type: 'video', uri: part.source.value, mime_type: mimeType }\n}\n\n/**\n * Pull the generated video out of a completed interaction. Prefers the\n * SDK's `output_video` sugar, then walks `steps` back-to-front for the last\n * `model_output` step carrying a video content block (the wire shape the\n * raw REST response uses).\n */\nfunction extractInteractionVideo(\n interaction: Interaction,\n): { data?: string; uri?: string; mimeType: string } | undefined {\n const direct = interaction.output_video\n if (direct && (direct.data || direct.uri)) {\n return {\n data: direct.data,\n uri: direct.uri,\n mimeType: direct.mime_type || 'video/mp4',\n }\n }\n const steps = interaction.steps ?? []\n for (let i = steps.length - 1; i >= 0; i--) {\n const step = steps[i]\n if (step?.type !== 'model_output') continue\n for (const block of step.content ?? []) {\n if (block.type === 'video' && (block.data || block.uri)) {\n return {\n data: block.data,\n uri: block.uri,\n mimeType: block.mime_type || 'video/mp4',\n }\n }\n }\n }\n return undefined\n}\n\n/**\n * Map Interactions usage onto the canonical TokenUsage shape. Omni reports\n * video output via `output_tokens_by_modality`; fall back to the video\n * modality entry when the total is absent.\n */\nfunction interactionUsageToTokenUsage(\n usage: Interaction['usage'],\n): TokenUsage | undefined {\n if (!usage) return undefined\n const videoTokens = usage.output_tokens_by_modality?.find(\n (entry) => entry.modality === 'video',\n )?.tokens\n const promptTokens = usage.total_input_tokens ?? 0\n const completionTokens = usage.total_output_tokens ?? videoTokens ?? 0\n return {\n promptTokens,\n completionTokens,\n totalTokens: usage.total_tokens ?? promptTokens + completionTokens,\n }\n}\n\n/**\n * Gemini Video Generation Adapter (Veo + Gemini Omni Flash)\n *\n * Tree-shakeable adapter for Google video generation, routing by model:\n *\n * **Veo models** run as a long-running operation: `createVideoJob` starts\n * the operation via the `:predictLongRunning` endpoint, `getVideoStatus`\n * polls it, and `getVideoUrl` extracts the generated video's URI once it\n * completes. Image prompt parts are routed by `metadata.role`:\n * - `'start_frame'` (or the first un-roled image) → the input image the\n * video starts from\n * - `'end_frame'` → `lastFrame` (the frame the video ends on)\n * - `'reference'` / `'character'` → `referenceImages` (asset references,\n * Veo 3.1)\n *\n * Note: the returned Veo video URI is served by the Gemini Files API and\n * requires the API key (`x-goog-api-key` header or `?key=` query\n * parameter) to download.\n *\n * **Gemini Omni Flash** (`gemini-omni-1.1-flash`, plus the deprecated\n * `gemini-omni-flash-preview` alias) only serves the Interactions API:\n * `createVideoJob` creates a background interaction with\n * `response_modalities: ['video']`, `getVideoStatus` polls it by id, and\n * `getVideoUrl` returns the inline base64 MP4 as a `data:` URL (or the\n * Files API URI when the server delivers by reference). Image and video\n * prompt parts are sent as interaction content blocks, grouped as images,\n * then videos, then the text prompt (interleaving is not preserved); pass\n * `modelOptions.previous_interaction_id` to conversationally edit a prior\n * Omni generation. `size` is an `aspectRatio_resolution` template\n * (`'16:9'` or `'16:9_1080p'`); the optional suffix maps onto\n * `response_format.resolution` (`'360p' | '720p' | '1080p' | '4k'`,\n * default 720p).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport class GeminiVideoAdapter<\n TModel extends GeminiVideoModel,\n> extends BaseVideoAdapter<\n TModel,\n GeminiVideoModelProviderOptionsByName[TModel],\n GeminiVideoModelProviderOptionsByName,\n GeminiVideoModelSizeByName,\n GeminiVideoModelInputModalitiesByName,\n GeminiVideoModelDurationByName\n> {\n readonly name = 'gemini' as const\n\n protected client: GoogleGenAI\n private readonly allowUrlFetch: boolean\n\n constructor(config: GeminiVideoConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n this.allowUrlFetch = config.allowUrlFetch ?? false\n }\n\n async createVideoJob(\n options: VideoGenerationOptions<\n GeminiVideoModelProviderOptionsByName[TModel],\n GeminiVideoModelSizeByName[TModel],\n GeminiVideoModelDurationByName[TModel]\n >,\n ): Promise<VideoJobResult> {\n const { prompt, size, duration, logger } = options\n\n logger.request(\n `activity=video.create provider=${this.name} model=${this.model} size=${size ?? 'default'} duration=${duration ?? 'default'}`,\n { provider: this.name, model: this.model },\n )\n\n if (isInteractionsVideoModel(this.model)) {\n return await this.createInteractionsVideoJob(options)\n }\n const modelOptions = options.modelOptions as\n | GeminiVideoProviderOptions\n | undefined\n\n try {\n const resolved = resolveMediaPrompt(prompt)\n\n if (resolved.videos.length > 0) {\n throw new Error(\n `${this.name}.createVideoJob does not support video prompt parts (model: ${this.model}).`,\n )\n }\n if (resolved.audios.length > 0) {\n throw new Error(\n `${this.name}.createVideoJob does not support audio prompt parts (model: ${this.model}).`,\n )\n }\n\n const { image, lastFrame, referenceImages } = await this.routeImageParts(\n resolved.images,\n )\n\n const config: GenerateVideosConfig = {\n ...modelOptions,\n ...(size !== undefined && { aspectRatio: size }),\n ...(duration !== undefined && { durationSeconds: duration }),\n ...(lastFrame && { lastFrame }),\n ...(referenceImages.length > 0 && { referenceImages }),\n }\n\n const operation = await this.client.models.generateVideos({\n model: this.model,\n prompt: resolved.text,\n ...(image && { image }),\n config,\n })\n\n if (!operation.name) {\n throw new Error(\n 'Veo did not return an operation name for the video generation job.',\n )\n }\n\n return { jobId: operation.name, model: this.model }\n } catch (error) {\n logger.errors(`${this.name}.createVideoJob fatal`, {\n error,\n source: `${this.name}.createVideoJob`,\n })\n throw error\n }\n }\n\n /**\n * Gemini Omni Flash job creation via the Interactions API. Creates a\n * background interaction requesting video output; the interaction id is\n * the job id polled by `getVideoStatus` / `getVideoUrl`.\n */\n private async createInteractionsVideoJob(\n options: VideoGenerationOptions<\n GeminiVideoModelProviderOptionsByName[TModel],\n GeminiVideoModelSizeByName[TModel],\n GeminiVideoModelDurationByName[TModel]\n >,\n ): Promise<VideoJobResult> {\n const { prompt, size, duration, logger } = options\n const modelOptions = options.modelOptions as\n | GeminiOmniVideoProviderOptions\n | undefined\n\n try {\n const resolved = resolveMediaPrompt(prompt)\n\n if (resolved.audios.length > 0) {\n throw new Error(\n `${this.name}.createVideoJob does not support audio prompt parts (model: ${this.model}).`,\n )\n }\n\n const content: Array<InteractionContent> = [\n ...resolved.images.map(mediaPartToInteractionsContent),\n ...resolved.videos.map(mediaPartToInteractionsContent),\n ]\n if (resolved.text) {\n content.push({ type: 'text', text: resolved.text })\n }\n if (content.length === 0) {\n throw new Error(\n `${this.name}.createVideoJob: the prompt produced no content to send (model: ${this.model}).`,\n )\n }\n\n // Reject out-of-range durations locally rather than snapping (which\n // would silently change the clip length the caller asked for) or\n // letting the live API reject them after the round trip.\n const durations = this.availableDurations()\n if (\n duration !== undefined &&\n durations.kind === 'range' &&\n (duration < durations.min || duration > durations.max)\n ) {\n throw new Error(\n `${this.name}.createVideoJob: duration ${duration}s is outside the ${durations.min}–${durations.max}s range supported by ${this.model}. Use snapDuration() to snap arbitrary values into range.`,\n )\n }\n\n // Aspect ratio, clip length, and resolution ride on `response_format`.\n // Duration is a `\"<seconds>s\"` string. Resolution comes from the\n // optional `size` suffix (`'16:9_1080p'`), defaulting to 720p when\n // omitted — https://ai.google.dev/gemini-api/docs/omni\n const parsedSize =\n size !== undefined ? parseGeminiOmniVideoSize(size) : undefined\n const responseFormat =\n parsedSize !== undefined || duration !== undefined\n ? {\n response_format: {\n type: 'video' as const,\n ...(parsedSize !== undefined && {\n aspect_ratio: parsedSize.aspectRatio,\n ...(parsedSize.resolution !== undefined && {\n resolution: parsedSize.resolution,\n }),\n }),\n ...(duration !== undefined && { duration: `${duration}s` }),\n },\n }\n : {}\n\n const interaction = await this.client.interactions.create({\n ...modelOptions,\n model: this.model,\n input: [{ type: 'user_input', content }],\n response_modalities: ['video'],\n background: true,\n ...responseFormat,\n })\n\n if (!interaction.id) {\n throw new Error(\n 'Gemini Omni did not return an interaction id for the video generation job.',\n )\n }\n\n return { jobId: interaction.id, model: this.model }\n } catch (error) {\n logger.errors(`${this.name}.createVideoJob fatal`, {\n error,\n source: `${this.name}.createVideoJob`,\n })\n throw error\n }\n }\n\n /**\n * Route image prompt parts onto Veo's request fields by `metadata.role`.\n */\n private async routeImageParts(\n parts: Array<ImagePart<MediaInputMetadata>>,\n ): Promise<{\n image: Image | undefined\n lastFrame: Image | undefined\n referenceImages: Array<VideoGenerationReferenceImage>\n }> {\n let image: Image | undefined\n let lastFrame: Image | undefined\n const referenceImages: Array<VideoGenerationReferenceImage> = []\n\n for (const part of parts) {\n const role = part.metadata?.role\n switch (role) {\n case 'end_frame': {\n if (lastFrame) {\n throw new Error(\n `${this.name}: Veo accepts at most one 'end_frame' image.`,\n )\n }\n lastFrame = await imagePartToVeoImage(part, this.allowUrlFetch)\n break\n }\n case 'reference':\n case 'character': {\n referenceImages.push({\n image: await imagePartToVeoImage(part, this.allowUrlFetch),\n referenceType: VideoGenerationReferenceType.ASSET,\n })\n break\n }\n case 'start_frame':\n case undefined: {\n if (image) {\n throw new Error(\n `${this.name}: Veo accepts at most one starting image; received multiple 'start_frame'/un-roled images. Use metadata.role ('end_frame', 'reference') to disambiguate the others.`,\n )\n }\n image = await imagePartToVeoImage(part, this.allowUrlFetch)\n break\n }\n case 'mask':\n case 'control':\n throw new Error(\n `${this.name}: unsupported image role \"${role}\" for Veo video generation.`,\n )\n }\n }\n\n return { image, lastFrame, referenceImages }\n }\n\n async getVideoStatus(jobId: string): Promise<VideoStatusResult> {\n if (isInteractionsVideoModel(this.model)) {\n return await this.getInteractionsVideoStatus(jobId)\n }\n const operation = await this.getOperation(jobId)\n\n if (!operation.done) {\n return { jobId, status: 'processing' }\n }\n\n if (operation.error) {\n return {\n jobId,\n status: 'failed',\n error: operationErrorMessage(operation.error),\n }\n }\n\n // The operation can finish \"successfully\" with every sample dropped by\n // Responsible-AI filters — surface that as a failure instead of letting\n // getVideoUrl() throw on an empty response.\n const videos = operation.response?.generatedVideos ?? []\n if (videos.length === 0) {\n const reasons = operation.response?.raiMediaFilteredReasons\n return {\n jobId,\n status: 'failed',\n error: reasons?.length\n ? `Video was filtered by Responsible-AI: ${reasons.join('; ')}`\n : 'Veo returned no generated videos.',\n }\n }\n\n return { jobId, status: 'completed' }\n }\n\n /**\n * Poll an Omni background interaction. `in_progress` maps to\n * 'processing'; a `completed` interaction with no video content (e.g.\n * filtered output) is surfaced as a failure so `getVideoUrl` doesn't\n * throw on an empty response. `requires_action` also fails: the adapter\n * never sends tools, so it can only arise via\n * `previous_interaction_id` chaining onto a tool-bearing interaction —\n * and such an interaction never progresses without a client response,\n * so polling it would spin until timeout.\n */\n private async getInteractionsVideoStatus(\n jobId: string,\n ): Promise<VideoStatusResult> {\n const interaction = await this.getInteraction(jobId)\n const status = interaction.status\n\n if (status === 'in_progress') {\n return { jobId, status: 'processing' }\n }\n if (status === 'requires_action') {\n return {\n jobId,\n status: 'failed',\n error:\n 'Gemini Omni interaction is waiting on a client action (tool response), which the video jobs flow does not support.',\n }\n }\n if (status === 'completed') {\n if (!extractInteractionVideo(interaction)) {\n return {\n jobId,\n status: 'failed',\n error:\n 'Gemini Omni completed the interaction without returning a video (the output may have been filtered).',\n }\n }\n return { jobId, status: 'completed' }\n }\n return {\n jobId,\n status: 'failed',\n error: `Gemini Omni video generation ended with status \"${status}\".`,\n }\n }\n\n async getVideoUrl(jobId: string): Promise<VideoUrlResult> {\n if (isInteractionsVideoModel(this.model)) {\n return await this.getInteractionsVideoUrl(jobId)\n }\n const operation = await this.getOperation(jobId)\n\n if (!operation.done) {\n throw new Error(\n `Video is not ready yet. Check status first. Job ID: ${jobId}`,\n )\n }\n\n if (operation.error) {\n throw new Error(\n `Video generation failed: ${operationErrorMessage(operation.error)}`,\n )\n }\n\n const uri = operation.response?.generatedVideos?.[0]?.video?.uri\n if (!uri) {\n const reasons = operation.response?.raiMediaFilteredReasons\n throw new Error(\n reasons?.length\n ? `Video was filtered by Responsible-AI: ${reasons.join('; ')}`\n : `Video URL not found in operation response. Job ID: ${jobId}`,\n )\n }\n\n return { jobId, url: uri }\n }\n\n /**\n * Extract the finished Omni video. Inline base64 output (the API default)\n * becomes a `data:` URL — matching the OpenAI Sora adapter's inline\n * delivery — and URI delivery passes through (Files API URIs need the API\n * key to download, like Veo). Usage carries the video-modality output\n * tokens (Omni bills per second of video, reported as tokens).\n */\n private async getInteractionsVideoUrl(\n jobId: string,\n ): Promise<VideoUrlResult> {\n const interaction = await this.getInteraction(jobId)\n const status = interaction.status\n\n if (status === 'in_progress') {\n throw new Error(\n `Video is not ready yet. Check status first. Job ID: ${jobId}`,\n )\n }\n if (status !== 'completed') {\n throw new Error(\n `Video generation failed: Gemini Omni interaction ended with status \"${status}\". Job ID: ${jobId}`,\n )\n }\n\n const video = extractInteractionVideo(interaction)\n if (!video) {\n throw new Error(\n `Video not found in interaction response (the output may have been filtered). Job ID: ${jobId}`,\n )\n }\n\n const usage = interactionUsageToTokenUsage(interaction.usage)\n const url = video.uri ?? `data:${video.mimeType};base64,${video.data}`\n return { jobId, url, ...(usage && { usage }) }\n }\n\n override availableDurations(): DurationOptions<\n GeminiVideoModelDurationByName[TModel]\n > {\n return getGeminiVideoDurationOptions(this.model)\n }\n\n override snapDuration(\n seconds: number,\n ): GeminiVideoModelDurationByName[TModel] | undefined {\n return snapToDurationOption(seconds, this.availableDurations())\n }\n\n /**\n * Fetch the long-running operation by name. The SDK's\n * `operations.getVideosOperation` needs a real `GenerateVideosOperation`\n * instance (it calls `_fromAPIResponse` on it), so reconstruct one from\n * the job ID rather than passing an object literal.\n */\n private async getOperation(jobId: string): Promise<GenerateVideosOperation> {\n const operation = new GenerateVideosOperation()\n operation.name = jobId\n return await this.client.operations.getVideosOperation({ operation })\n }\n\n /**\n * Fetch an Omni background interaction by id.\n */\n private async getInteraction(jobId: string): Promise<Interaction> {\n return await this.client.interactions.get(jobId)\n }\n}\n\n/** @deprecated Shuts down 2026-09-30. Use `gemini-omni-1.1-flash`. */\nexport function createGeminiVideo(\n model: 'gemini-omni-flash-preview',\n apiKey: string,\n config?: Omit<GeminiVideoConfig, 'apiKey'>,\n): GeminiVideoAdapter<'gemini-omni-flash-preview'>\n/**\n * Creates a Gemini video adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @param model - The model name (e.g., 'veo-3.1-generate-preview')\n * @param apiKey - Your Google API key\n * @param config - Optional additional configuration\n * @returns Configured Gemini video adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGeminiVideo('veo-3.1-generate-preview', 'your-api-key');\n *\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: 'A beautiful sunset over the ocean',\n * duration: adapter.snapDuration(7), // → 6\n * });\n * ```\n */\nexport function createGeminiVideo<TModel extends GeminiVideoModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiVideoConfig, 'apiKey'>,\n): GeminiVideoAdapter<TModel>\nexport function createGeminiVideo<TModel extends GeminiVideoModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiVideoConfig, 'apiKey'>,\n): GeminiVideoAdapter<TModel> {\n return new GeminiVideoAdapter({ apiKey, ...config }, model)\n}\n\n/** @deprecated Shuts down 2026-09-30. Use `gemini-omni-1.1-flash`. */\nexport function geminiVideo(\n model: 'gemini-omni-flash-preview',\n config?: Omit<GeminiVideoConfig, 'apiKey'>,\n): GeminiVideoAdapter<'gemini-omni-flash-preview'>\n/**\n * Creates a Gemini video adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @param model - The model name (e.g., 'veo-3.1-generate-preview')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Gemini video adapter instance with resolved types\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses GOOGLE_API_KEY from environment\n * const adapter = geminiVideo('veo-3.1-generate-preview');\n *\n * // Create a video generation job\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: 'A cat playing piano'\n * });\n *\n * // Poll for status\n * const status = await getVideoJobStatus({ adapter, jobId });\n * ```\n */\nexport function geminiVideo<TModel extends GeminiVideoModel>(\n model: TModel,\n config?: Omit<GeminiVideoConfig, 'apiKey'>,\n): GeminiVideoAdapter<TModel>\nexport function geminiVideo<TModel extends GeminiVideoModel>(\n model: TModel,\n config?: Omit<GeminiVideoConfig, 'apiKey'>,\n): GeminiVideoAdapter<TModel> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiVideo(model, apiKey, config)\n}\n"],"mappings":";;;;;;;;;;;;AAkEA,SAAS,sBAAsB,OAAwC;CACrE,IAAI,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,SAAS,GAC9D,OAAO,MAAM;CAEf,OAAO,KAAK,UAAU,KAAK;AAC7B;;;;;;;;;;;;;AAcA,eAAe,oBACb,MACA,eACgB;CAChB,IAAI,KAAK,OAAO,SAAS,QACvB,OAAO;EACL,YAAY,KAAK,OAAO;EACxB,UAAU,KAAK,OAAO,YAAY;CACpC;CAEF,MAAM,MAAM,KAAK,OAAO;CACxB,IAAI,IAAI,WAAW,OAAO,GACxB,OAAO;EACL,QAAQ;EACR,GAAI,KAAK,OAAO,YAAY,EAAE,UAAU,KAAK,OAAO,SAAS;CAC/D;CAEF,IAAI,IAAI,WAAW,OAAO,GAAG;EAC3B,MAAM,QAAQ,IAAI,MAAM,iCAAiC;EACzD,IAAI,CAAC,SAAS,CAAC,MAAM,IACnB,MAAM,IAAI,MACR,sEACF;EAEF,OAAO;GACL,YAAY,MAAM,MAAM;GACxB,UAAU,MAAM,MAAM,KAAK,OAAO,YAAY;EAChD;CACF;CACA,IAAI,CAAC,eACH,MAAM,IAAI,MACR,gUAI+C,KACjD;CAEF,MAAM,WAAW,MAAM,MAAM,GAAG;CAChC,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MACR,gCAAgC,SAAS,OAAO,GAAG,SAAS,WAAW,KAAK,KAC9E;CAEF,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,MAAM,SAAS,MAAM,KAAK,YAAY;CACtC,OAAO;EACL,YAAY,oBAAoB,MAAM;EACtC,UAAU,KAAK,OAAO,YAAY,KAAK,QAAQ;CACjD;AACF;;;;;;AAOA,SAAS,+BACP,MACoB;CACpB,MAAM,WAAW,KAAK,OAAO;CAC7B,IAAI,KAAK,SAAS,SAChB,OAAO,KAAK,OAAO,SAAS,SACxB;EAAE,MAAM;EAAS,MAAM,KAAK,OAAO;EAAO,WAAW;CAAS,IAC9D;EAAE,MAAM;EAAS,KAAK,KAAK,OAAO;EAAO,WAAW;CAAS;CAEnE,OAAO,KAAK,OAAO,SAAS,SACxB;EAAE,MAAM;EAAS,MAAM,KAAK,OAAO;EAAO,WAAW;CAAS,IAC9D;EAAE,MAAM;EAAS,KAAK,KAAK,OAAO;EAAO,WAAW;CAAS;AACnE;;;;;;;AAQA,SAAS,wBACP,aAC+D;CAC/D,MAAM,SAAS,YAAY;CAC3B,IAAI,WAAW,OAAO,QAAQ,OAAO,MACnC,OAAO;EACL,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,UAAU,OAAO,aAAa;CAChC;CAEF,MAAM,QAAQ,YAAY,SAAS,CAAC;CACpC,KAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,OAAO,MAAM;EACnB,IAAI,MAAM,SAAS,gBAAgB;EACnC,KAAK,MAAM,SAAS,KAAK,WAAW,CAAC,GACnC,IAAI,MAAM,SAAS,YAAY,MAAM,QAAQ,MAAM,MACjD,OAAO;GACL,MAAM,MAAM;GACZ,KAAK,MAAM;GACX,UAAU,MAAM,aAAa;EAC/B;CAGN;AAEF;;;;;;AAOA,SAAS,6BACP,OACwB;CACxB,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,MAAM,cAAc,MAAM,2BAA2B,MAClD,UAAU,MAAM,aAAa,OAChC,CAAC,EAAE;CACH,MAAM,eAAe,MAAM,sBAAsB;CACjD,MAAM,mBAAmB,MAAM,uBAAuB,eAAe;CACrE,OAAO;EACL;EACA;EACA,aAAa,MAAM,gBAAgB,eAAe;CACpD;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,IAAa,qBAAb,cAEU,iBAOR;CACA,OAAgB;CAEhB;CACA;CAEA,YAAY,QAA2B,OAAe;EACpD,MAAM,CAAC,GAAG,KAAK;EACf,KAAK,SAAS,mBAAmB,MAAM;EACvC,KAAK,gBAAgB,OAAO,iBAAiB;CAC/C;CAEA,MAAM,eACJ,SAKyB;EACzB,MAAM,EAAE,QAAQ,MAAM,UAAU,WAAW;EAE3C,OAAO,QACL,kCAAkC,KAAK,KAAK,SAAS,KAAK,MAAM,QAAQ,QAAQ,UAAU,YAAY,YAAY,aAClH;GAAE,UAAU,KAAK;GAAM,OAAO,KAAK;EAAM,CAC3C;EAEA,IAAI,yBAAyB,KAAK,KAAK,GACrC,OAAO,MAAM,KAAK,2BAA2B,OAAO;EAEtD,MAAM,eAAe,QAAQ;EAI7B,IAAI;GACF,MAAM,WAAW,mBAAmB,MAAM;GAE1C,IAAI,SAAS,OAAO,SAAS,GAC3B,MAAM,IAAI,MACR,GAAG,KAAK,KAAK,8DAA8D,KAAK,MAAM,GACxF;GAEF,IAAI,SAAS,OAAO,SAAS,GAC3B,MAAM,IAAI,MACR,GAAG,KAAK,KAAK,8DAA8D,KAAK,MAAM,GACxF;GAGF,MAAM,EAAE,OAAO,WAAW,oBAAoB,MAAM,KAAK,gBACvD,SAAS,MACX;GAEA,MAAM,SAA+B;IACnC,GAAG;IACH,GAAI,SAAS,KAAA,KAAa,EAAE,aAAa,KAAK;IAC9C,GAAI,aAAa,KAAA,KAAa,EAAE,iBAAiB,SAAS;IAC1D,GAAI,aAAa,EAAE,UAAU;IAC7B,GAAI,gBAAgB,SAAS,KAAK,EAAE,gBAAgB;GACtD;GAEA,MAAM,YAAY,MAAM,KAAK,OAAO,OAAO,eAAe;IACxD,OAAO,KAAK;IACZ,QAAQ,SAAS;IACjB,GAAI,SAAS,EAAE,MAAM;IACrB;GACF,CAAC;GAED,IAAI,CAAC,UAAU,MACb,MAAM,IAAI,MACR,oEACF;GAGF,OAAO;IAAE,OAAO,UAAU;IAAM,OAAO,KAAK;GAAM;EACpD,SAAS,OAAO;GACd,OAAO,OAAO,GAAG,KAAK,KAAK,wBAAwB;IACjD;IACA,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;;;;;;CAOA,MAAc,2BACZ,SAKyB;EACzB,MAAM,EAAE,QAAQ,MAAM,UAAU,WAAW;EAC3C,MAAM,eAAe,QAAQ;EAI7B,IAAI;GACF,MAAM,WAAW,mBAAmB,MAAM;GAE1C,IAAI,SAAS,OAAO,SAAS,GAC3B,MAAM,IAAI,MACR,GAAG,KAAK,KAAK,8DAA8D,KAAK,MAAM,GACxF;GAGF,MAAM,UAAqC,CACzC,GAAG,SAAS,OAAO,IAAI,8BAA8B,GACrD,GAAG,SAAS,OAAO,IAAI,8BAA8B,CACvD;GACA,IAAI,SAAS,MACX,QAAQ,KAAK;IAAE,MAAM;IAAQ,MAAM,SAAS;GAAK,CAAC;GAEpD,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,MACR,GAAG,KAAK,KAAK,kEAAkE,KAAK,MAAM,GAC5F;GAMF,MAAM,YAAY,KAAK,mBAAmB;GAC1C,IACE,aAAa,KAAA,KACb,UAAU,SAAS,YAClB,WAAW,UAAU,OAAO,WAAW,UAAU,MAElD,MAAM,IAAI,MACR,GAAG,KAAK,KAAK,4BAA4B,SAAS,mBAAmB,UAAU,IAAI,GAAG,UAAU,IAAI,uBAAuB,KAAK,MAAM,0DACxI;GAOF,MAAM,aACJ,SAAS,KAAA,IAAY,yBAAyB,IAAI,IAAI,KAAA;GACxD,MAAM,iBACJ,eAAe,KAAA,KAAa,aAAa,KAAA,IACrC,EACE,iBAAiB;IACf,MAAM;IACN,GAAI,eAAe,KAAA,KAAa;KAC9B,cAAc,WAAW;KACzB,GAAI,WAAW,eAAe,KAAA,KAAa,EACzC,YAAY,WAAW,WACzB;IACF;IACA,GAAI,aAAa,KAAA,KAAa,EAAE,UAAU,GAAG,SAAS,GAAG;GAC3D,EACF,IACA,CAAC;GAEP,MAAM,cAAc,MAAM,KAAK,OAAO,aAAa,OAAO;IACxD,GAAG;IACH,OAAO,KAAK;IACZ,OAAO,CAAC;KAAE,MAAM;KAAc;IAAQ,CAAC;IACvC,qBAAqB,CAAC,OAAO;IAC7B,YAAY;IACZ,GAAG;GACL,CAAC;GAED,IAAI,CAAC,YAAY,IACf,MAAM,IAAI,MACR,4EACF;GAGF,OAAO;IAAE,OAAO,YAAY;IAAI,OAAO,KAAK;GAAM;EACpD,SAAS,OAAO;GACd,OAAO,OAAO,GAAG,KAAK,KAAK,wBAAwB;IACjD;IACA,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;;;;CAKA,MAAc,gBACZ,OAKC;EACD,IAAI;EACJ,IAAI;EACJ,MAAM,kBAAwD,CAAC;EAE/D,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,OAAO,KAAK,UAAU;GAC5B,QAAQ,MAAR;IACE,KAAK;KACH,IAAI,WACF,MAAM,IAAI,MACR,GAAG,KAAK,KAAK,6CACf;KAEF,YAAY,MAAM,oBAAoB,MAAM,KAAK,aAAa;KAC9D;IAEF,KAAK;IACL,KAAK;KACH,gBAAgB,KAAK;MACnB,OAAO,MAAM,oBAAoB,MAAM,KAAK,aAAa;MACzD,eAAe,6BAA6B;KAC9C,CAAC;KACD;IAEF,KAAK;IACL,KAAK,KAAA;KACH,IAAI,OACF,MAAM,IAAI,MACR,GAAG,KAAK,KAAK,oKACf;KAEF,QAAQ,MAAM,oBAAoB,MAAM,KAAK,aAAa;KAC1D;IAEF,KAAK;IACL,KAAK,WACH,MAAM,IAAI,MACR,GAAG,KAAK,KAAK,4BAA4B,KAAK,4BAChD;GACJ;EACF;EAEA,OAAO;GAAE;GAAO;GAAW;EAAgB;CAC7C;CAEA,MAAM,eAAe,OAA2C;EAC9D,IAAI,yBAAyB,KAAK,KAAK,GACrC,OAAO,MAAM,KAAK,2BAA2B,KAAK;EAEpD,MAAM,YAAY,MAAM,KAAK,aAAa,KAAK;EAE/C,IAAI,CAAC,UAAU,MACb,OAAO;GAAE;GAAO,QAAQ;EAAa;EAGvC,IAAI,UAAU,OACZ,OAAO;GACL;GACA,QAAQ;GACR,OAAO,sBAAsB,UAAU,KAAK;EAC9C;EAOF,KADe,UAAU,UAAU,mBAAmB,CAAC,EAAA,CAC5C,WAAW,GAAG;GACvB,MAAM,UAAU,UAAU,UAAU;GACpC,OAAO;IACL;IACA,QAAQ;IACR,OAAO,SAAS,SACZ,yCAAyC,QAAQ,KAAK,IAAI,MAC1D;GACN;EACF;EAEA,OAAO;GAAE;GAAO,QAAQ;EAAY;CACtC;;;;;;;;;;;CAYA,MAAc,2BACZ,OAC4B;EAC5B,MAAM,cAAc,MAAM,KAAK,eAAe,KAAK;EACnD,MAAM,SAAS,YAAY;EAE3B,IAAI,WAAW,eACb,OAAO;GAAE;GAAO,QAAQ;EAAa;EAEvC,IAAI,WAAW,mBACb,OAAO;GACL;GACA,QAAQ;GACR,OACE;EACJ;EAEF,IAAI,WAAW,aAAa;GAC1B,IAAI,CAAC,wBAAwB,WAAW,GACtC,OAAO;IACL;IACA,QAAQ;IACR,OACE;GACJ;GAEF,OAAO;IAAE;IAAO,QAAQ;GAAY;EACtC;EACA,OAAO;GACL;GACA,QAAQ;GACR,OAAO,mDAAmD,OAAO;EACnE;CACF;CAEA,MAAM,YAAY,OAAwC;EACxD,IAAI,yBAAyB,KAAK,KAAK,GACrC,OAAO,MAAM,KAAK,wBAAwB,KAAK;EAEjD,MAAM,YAAY,MAAM,KAAK,aAAa,KAAK;EAE/C,IAAI,CAAC,UAAU,MACb,MAAM,IAAI,MACR,uDAAuD,OACzD;EAGF,IAAI,UAAU,OACZ,MAAM,IAAI,MACR,4BAA4B,sBAAsB,UAAU,KAAK,GACnE;EAGF,MAAM,MAAM,UAAU,UAAU,kBAAkB,EAAE,EAAE,OAAO;EAC7D,IAAI,CAAC,KAAK;GACR,MAAM,UAAU,UAAU,UAAU;GACpC,MAAM,IAAI,MACR,SAAS,SACL,yCAAyC,QAAQ,KAAK,IAAI,MAC1D,sDAAsD,OAC5D;EACF;EAEA,OAAO;GAAE;GAAO,KAAK;EAAI;CAC3B;;;;;;;;CASA,MAAc,wBACZ,OACyB;EACzB,MAAM,cAAc,MAAM,KAAK,eAAe,KAAK;EACnD,MAAM,SAAS,YAAY;EAE3B,IAAI,WAAW,eACb,MAAM,IAAI,MACR,uDAAuD,OACzD;EAEF,IAAI,WAAW,aACb,MAAM,IAAI,MACR,uEAAuE,OAAO,aAAa,OAC7F;EAGF,MAAM,QAAQ,wBAAwB,WAAW;EACjD,IAAI,CAAC,OACH,MAAM,IAAI,MACR,wFAAwF,OAC1F;EAGF,MAAM,QAAQ,6BAA6B,YAAY,KAAK;EAE5D,OAAO;GAAE;GAAO,KADJ,MAAM,OAAO,QAAQ,MAAM,SAAS,UAAU,MAAM;GAC3C,GAAI,SAAS,EAAE,MAAM;EAAG;CAC/C;CAEA,qBAEE;EACA,OAAO,8BAA8B,KAAK,KAAK;CACjD;CAEA,aACE,SACoD;EACpD,OAAO,qBAAqB,SAAS,KAAK,mBAAmB,CAAC;CAChE;;;;;;;CAQA,MAAc,aAAa,OAAiD;EAC1E,MAAM,YAAY,IAAI,wBAAwB;EAC9C,UAAU,OAAO;EACjB,OAAO,MAAM,KAAK,OAAO,WAAW,mBAAmB,EAAE,UAAU,CAAC;CACtE;;;;CAKA,MAAc,eAAe,OAAqC;EAChE,OAAO,MAAM,KAAK,OAAO,aAAa,IAAI,KAAK;CACjD;AACF;AAmCA,SAAgB,kBACd,OACA,QACA,QAC4B;CAC5B,OAAO,IAAI,mBAAmB;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AAC5D;AAyCA,SAAgB,YACd,OACA,QAC4B;CAE5B,OAAO,kBAAkB,OADV,uBACiB,GAAQ,MAAM;AAChD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GeminiVideoMetadata } from '../message-types.js';
|
|
2
|
+
import { VideoPart } from '@tanstack/ai';
|
|
3
|
+
/**
|
|
4
|
+
* A file uploaded to the Gemini Files API and ready to reference in a message.
|
|
5
|
+
*/
|
|
6
|
+
export interface GeminiUploadedFile {
|
|
7
|
+
/** Resource name, e.g. `"files/abc123"`. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** File URI to reference from message content (as a `url` source). */
|
|
10
|
+
uri: string;
|
|
11
|
+
/** MIME type reported by the Files API. */
|
|
12
|
+
mimeType: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Options for {@link uploadGeminiFile}.
|
|
16
|
+
*/
|
|
17
|
+
export interface GeminiUploadFileOptions {
|
|
18
|
+
/**
|
|
19
|
+
* API key. Falls back to `GOOGLE_API_KEY` / `GEMINI_API_KEY` from the
|
|
20
|
+
* environment when omitted.
|
|
21
|
+
*/
|
|
22
|
+
apiKey?: string;
|
|
23
|
+
/**
|
|
24
|
+
* MIME type of the file (e.g. `"video/mp4"`). Recommended so the Files API
|
|
25
|
+
* processes and serves the file with the correct type.
|
|
26
|
+
*/
|
|
27
|
+
mimeType?: string;
|
|
28
|
+
/** Poll interval while the file is `PROCESSING`, in ms. Default `5000`. */
|
|
29
|
+
pollIntervalMs?: number;
|
|
30
|
+
/** Max time to wait for processing, in ms. Default `300000` (5 min). */
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Upload a file via the Gemini Files API and wait until it is `ACTIVE`.
|
|
35
|
+
*
|
|
36
|
+
* Large media (notably video) must be uploaded rather than inlined as base64;
|
|
37
|
+
* the Files API processes uploads asynchronously. This wraps the upload +
|
|
38
|
+
* poll-until-ready loop and returns a reference you can drop into message
|
|
39
|
+
* content as a `url` source (see {@link geminiVideoPart}).
|
|
40
|
+
*
|
|
41
|
+
* @throws if the upload has no URI, or processing fails or times out.
|
|
42
|
+
*/
|
|
43
|
+
export declare function uploadGeminiFile(file: string | Blob, options?: GeminiUploadFileOptions): Promise<GeminiUploadedFile>;
|
|
44
|
+
/**
|
|
45
|
+
* Build a TanStack AI video content part from an uploaded Gemini file.
|
|
46
|
+
*
|
|
47
|
+
* Pass `metadata` to control understanding — e.g.
|
|
48
|
+
* `{ processing: 'agentic' }` to route through the agentic Interactions path,
|
|
49
|
+
* or `{ fps, startOffset, endOffset }` for single-pass sampling controls.
|
|
50
|
+
*/
|
|
51
|
+
export declare function geminiVideoPart(file: GeminiUploadedFile, metadata?: GeminiVideoMetadata): VideoPart<GeminiVideoMetadata>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createGeminiClient, getGeminiApiKeyFromEnv } from "../utils/client.js";
|
|
2
|
+
import "../utils/index.js";
|
|
3
|
+
import { FileState } from "@google/genai";
|
|
4
|
+
//#region src/files/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Upload a file via the Gemini Files API and wait until it is `ACTIVE`.
|
|
7
|
+
*
|
|
8
|
+
* Large media (notably video) must be uploaded rather than inlined as base64;
|
|
9
|
+
* the Files API processes uploads asynchronously. This wraps the upload +
|
|
10
|
+
* poll-until-ready loop and returns a reference you can drop into message
|
|
11
|
+
* content as a `url` source (see {@link geminiVideoPart}).
|
|
12
|
+
*
|
|
13
|
+
* @throws if the upload has no URI, or processing fails or times out.
|
|
14
|
+
*/
|
|
15
|
+
async function uploadGeminiFile(file, options = {}) {
|
|
16
|
+
const { apiKey = getGeminiApiKeyFromEnv(), mimeType, pollIntervalMs = 5e3, timeoutMs = 3e5 } = options;
|
|
17
|
+
const client = createGeminiClient({ apiKey });
|
|
18
|
+
let uploaded = await client.files.upload({
|
|
19
|
+
file,
|
|
20
|
+
...mimeType && { config: { mimeType } }
|
|
21
|
+
});
|
|
22
|
+
const fileName = uploaded.name;
|
|
23
|
+
if (!fileName) throw new Error("Gemini file upload did not return a file name.");
|
|
24
|
+
const deadline = Date.now() + timeoutMs;
|
|
25
|
+
while (uploaded.state === FileState.PROCESSING) {
|
|
26
|
+
if (Date.now() > deadline) throw new Error(`Gemini file processing timed out after ${timeoutMs}ms (${fileName}).`);
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
28
|
+
uploaded = await client.files.get({ name: fileName });
|
|
29
|
+
}
|
|
30
|
+
if (uploaded.state === FileState.FAILED) throw new Error(`Gemini file processing failed: ${uploaded.error?.message ?? String(uploaded.state)}`);
|
|
31
|
+
if (!uploaded.uri) throw new Error("Gemini file upload did not return a URI.");
|
|
32
|
+
return {
|
|
33
|
+
name: fileName,
|
|
34
|
+
uri: uploaded.uri,
|
|
35
|
+
mimeType: uploaded.mimeType ?? mimeType ?? "application/octet-stream"
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build a TanStack AI video content part from an uploaded Gemini file.
|
|
40
|
+
*
|
|
41
|
+
* Pass `metadata` to control understanding — e.g.
|
|
42
|
+
* `{ processing: 'agentic' }` to route through the agentic Interactions path,
|
|
43
|
+
* or `{ fps, startOffset, endOffset }` for single-pass sampling controls.
|
|
44
|
+
*/
|
|
45
|
+
function geminiVideoPart(file, metadata) {
|
|
46
|
+
return {
|
|
47
|
+
type: "video",
|
|
48
|
+
source: {
|
|
49
|
+
type: "url",
|
|
50
|
+
value: file.uri,
|
|
51
|
+
mimeType: file.mimeType
|
|
52
|
+
},
|
|
53
|
+
...metadata && { metadata }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { geminiVideoPart, uploadGeminiFile };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/files/index.ts"],"sourcesContent":["import { FileState } from '@google/genai'\nimport { createGeminiClient, getGeminiApiKeyFromEnv } from '../utils'\nimport type { GeminiVideoMetadata } from '../message-types'\nimport type { VideoPart } from '@tanstack/ai'\n\n/**\n * A file uploaded to the Gemini Files API and ready to reference in a message.\n */\nexport interface GeminiUploadedFile {\n /** Resource name, e.g. `\"files/abc123\"`. */\n name: string\n /** File URI to reference from message content (as a `url` source). */\n uri: string\n /** MIME type reported by the Files API. */\n mimeType: string\n}\n\n/**\n * Options for {@link uploadGeminiFile}.\n */\nexport interface GeminiUploadFileOptions {\n /**\n * API key. Falls back to `GOOGLE_API_KEY` / `GEMINI_API_KEY` from the\n * environment when omitted.\n */\n apiKey?: string\n /**\n * MIME type of the file (e.g. `\"video/mp4\"`). Recommended so the Files API\n * processes and serves the file with the correct type.\n */\n mimeType?: string\n /** Poll interval while the file is `PROCESSING`, in ms. Default `5000`. */\n pollIntervalMs?: number\n /** Max time to wait for processing, in ms. Default `300000` (5 min). */\n timeoutMs?: number\n}\n\n/**\n * Upload a file via the Gemini Files API and wait until it is `ACTIVE`.\n *\n * Large media (notably video) must be uploaded rather than inlined as base64;\n * the Files API processes uploads asynchronously. This wraps the upload +\n * poll-until-ready loop and returns a reference you can drop into message\n * content as a `url` source (see {@link geminiVideoPart}).\n *\n * @throws if the upload has no URI, or processing fails or times out.\n */\nexport async function uploadGeminiFile(\n file: string | Blob,\n options: GeminiUploadFileOptions = {},\n): Promise<GeminiUploadedFile> {\n const {\n apiKey = getGeminiApiKeyFromEnv(),\n mimeType,\n pollIntervalMs = 5000,\n timeoutMs = 300_000,\n } = options\n\n const client = createGeminiClient({ apiKey })\n\n let uploaded = await client.files.upload({\n file,\n ...(mimeType && { config: { mimeType } }),\n })\n\n const fileName = uploaded.name\n if (!fileName) {\n throw new Error('Gemini file upload did not return a file name.')\n }\n\n const deadline = Date.now() + timeoutMs\n while (uploaded.state === FileState.PROCESSING) {\n if (Date.now() > deadline) {\n throw new Error(\n `Gemini file processing timed out after ${timeoutMs}ms (${fileName}).`,\n )\n }\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs))\n uploaded = await client.files.get({ name: fileName })\n }\n\n if (uploaded.state === FileState.FAILED) {\n throw new Error(\n `Gemini file processing failed: ${uploaded.error?.message ?? String(uploaded.state)}`,\n )\n }\n if (!uploaded.uri) {\n throw new Error('Gemini file upload did not return a URI.')\n }\n\n return {\n name: fileName,\n uri: uploaded.uri,\n mimeType: uploaded.mimeType ?? mimeType ?? 'application/octet-stream',\n }\n}\n\n/**\n * Build a TanStack AI video content part from an uploaded Gemini file.\n *\n * Pass `metadata` to control understanding — e.g.\n * `{ processing: 'agentic' }` to route through the agentic Interactions path,\n * or `{ fps, startOffset, endOffset }` for single-pass sampling controls.\n */\nexport function geminiVideoPart(\n file: GeminiUploadedFile,\n metadata?: GeminiVideoMetadata,\n): VideoPart<GeminiVideoMetadata> {\n return {\n type: 'video',\n source: { type: 'url', value: file.uri, mimeType: file.mimeType },\n ...(metadata && { metadata }),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA+CA,eAAsB,iBACpB,MACA,UAAmC,CAAC,GACP;CAC7B,MAAM,EACJ,SAAS,uBAAuB,GAChC,UACA,iBAAiB,KACjB,YAAY,QACV;CAEJ,MAAM,SAAS,mBAAmB,EAAE,OAAO,CAAC;CAE5C,IAAI,WAAW,MAAM,OAAO,MAAM,OAAO;EACvC;EACA,GAAI,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE;CACzC,CAAC;CAED,MAAM,WAAW,SAAS;CAC1B,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,gDAAgD;CAGlE,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,OAAO,SAAS,UAAU,UAAU,YAAY;EAC9C,IAAI,KAAK,IAAI,IAAI,UACf,MAAM,IAAI,MACR,0CAA0C,UAAU,MAAM,SAAS,GACrE;EAEF,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,cAAc,CAAC;EAClE,WAAW,MAAM,OAAO,MAAM,IAAI,EAAE,MAAM,SAAS,CAAC;CACtD;CAEA,IAAI,SAAS,UAAU,UAAU,QAC/B,MAAM,IAAI,MACR,kCAAkC,SAAS,OAAO,WAAW,OAAO,SAAS,KAAK,GACpF;CAEF,IAAI,CAAC,SAAS,KACZ,MAAM,IAAI,MAAM,0CAA0C;CAG5D,OAAO;EACL,MAAM;EACN,KAAK,SAAS;EACd,UAAU,SAAS,YAAY,YAAY;CAC7C;AACF;;;;;;;;AASA,SAAgB,gBACd,MACA,UACgC;CAChC,OAAO;EACL,MAAM;EACN,QAAQ;GAAE,MAAM;GAAO,OAAO,KAAK;GAAK,UAAU,KAAK;EAAS;EAChE,GAAI,YAAY,EAAE,SAAS;CAC7B;AACF"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { createGeminiSummarize, geminiSummarize, type GeminiSummarizeConfig, typ
|
|
|
3
3
|
export { GeminiImageAdapter, createGeminiImage, geminiImage, type GeminiImageConfig, } from './adapters/image.js';
|
|
4
4
|
export type { GeminiImageProviderOptions, GeminiNativeImageConfig, GeminiNativeImageProviderOptions, GeminiAnyImageProviderOptions, GeminiImageModelProviderOptionsByName, GeminiAspectRatio, GeminiImageModelSizeByName, GeminiStandardImageAspectRatio, GeminiExtendedImageAspectRatio, Gemini31FlashImageSize, Gemini31FlashLiteImageSize, Gemini3ProImageSize, Gemini25FlashImageSize, GeminiNativeImageSize, PersonGeneration, SafetyFilterLevel, ImagePromptLanguage, SafetySetting, ThinkingConfig, ImageConfig, ContentUnion, } from './image/image-provider-options.js';
|
|
5
5
|
export { HarmBlockThreshold, HarmCategory } from '@google/genai';
|
|
6
|
+
export { uploadGeminiFile, geminiVideoPart, type GeminiUploadedFile, type GeminiUploadFileOptions, } from './files/index.js';
|
|
6
7
|
export { GeminiEmbeddingAdapter, createGeminiEmbedding, geminiEmbedding, type GeminiEmbeddingConfig, } from './adapters/embedding.js';
|
|
7
8
|
export type { GeminiEmbeddingProviderOptions } from './embedding/embedding-provider-options.js';
|
|
8
9
|
/**
|
|
@@ -17,8 +18,8 @@ export { GeminiAudioAdapter, createGeminiAudio, geminiAudio, type GeminiAudioCon
|
|
|
17
18
|
* @experimental Video generation is an experimental feature and may change.
|
|
18
19
|
*/
|
|
19
20
|
export { GeminiVideoAdapter, createGeminiVideo, geminiVideo, type GeminiVideoConfig, } from './adapters/video.js';
|
|
20
|
-
export { GEMINI_VIDEO_DURATIONS, getGeminiVideoDurationOptions, isInteractionsVideoModel, } from './video/video-provider-options.js';
|
|
21
|
-
export type { GeminiInteractionsVideoModel, GeminiOmniVideoProviderOptions, GeminiVideoModel, GeminiVideoModelDurationByName, GeminiVideoModelInputModalitiesByName, GeminiVideoModelProviderOptionsByName, GeminiVideoModelSizeByName, GeminiVideoProviderOptions, GeminiVideoSize, } from './video/video-provider-options.js';
|
|
21
|
+
export { GEMINI_VIDEO_DURATIONS, getGeminiVideoDurationOptions, isInteractionsVideoModel, parseGeminiOmniVideoSize, } from './video/video-provider-options.js';
|
|
22
|
+
export type { GeminiInteractionsVideoModel, GeminiOmniVideoProviderOptions, GeminiOmniVideoResolution, GeminiOmniVideoSize, GeminiVideoModel, GeminiVideoModelDurationByName, GeminiVideoModelInputModalitiesByName, GeminiVideoModelProviderOptionsByName, GeminiVideoModelSizeByName, GeminiVideoProviderOptions, GeminiVideoSize, } from './video/video-provider-options.js';
|
|
22
23
|
export { GEMINI_MODELS, GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS, } from './model-meta.js';
|
|
23
24
|
export { GEMINI_MODELS as GeminiTextModels } from './model-meta.js';
|
|
24
25
|
export { GEMINI_IMAGE_MODELS as GeminiImageModels } from './model-meta.js';
|
|
@@ -36,7 +37,7 @@ export type { GeminiClientConfig } from './utils/client.js';
|
|
|
36
37
|
export type { GeminiChatModelProviderOptionsByName, GeminiChatModelToolCapabilitiesByName, GeminiModelInputModalitiesByName, GeminiEmbeddingModel, GeminiEmbeddingModelProviderOptionsByName, GeminiEmbeddingModelInputModalitiesByName, } from './model-meta.js';
|
|
37
38
|
export type { GeminiStructuredOutputOptions, GeminiThinkingOptions, } from './text/text-provider-options.js';
|
|
38
39
|
export type { GoogleGeminiTool } from './tools/index.js';
|
|
39
|
-
export type { GeminiTextMetadata, GeminiImageMetadata, GeminiAudioMetadata, GeminiVideoMetadata, GeminiDocumentMetadata, GeminiMessageMetadataByModality, } from './message-types.js';
|
|
40
|
+
export type { GeminiTextMetadata, GeminiImageMetadata, GeminiAudioMetadata, GeminiVideoMetadata, GeminiVideoProcessing, GeminiDocumentMetadata, GeminiMessageMetadataByModality, } from './message-types.js';
|
|
40
41
|
export type { GeminiProviderUsageDetails } from './usage.js';
|
|
41
42
|
export { geminiRealtime, geminiRealtimeToken } from './realtime/index.js';
|
|
42
43
|
export type { GeminiRealtimeModel, GeminiRealtimeOptions, GeminiRealtimeProviderOptions, GeminiRealtimeTokenOptions, GeminiRealtimeVoice, } from './realtime/index.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -3,13 +3,14 @@ import { GeminiTextAdapter, createGeminiChat, geminiText } from "./adapters/text
|
|
|
3
3
|
import { createGeminiSummarize, geminiSummarize } from "./adapters/summarize.js";
|
|
4
4
|
import { GEMINI_NATIVE_IMAGE_MODELS, isGeminiNativeImageModel } from "./image/image-provider-options.js";
|
|
5
5
|
import { GeminiImageAdapter, createGeminiImage, geminiImage } from "./adapters/image.js";
|
|
6
|
+
import { geminiVideoPart, uploadGeminiFile } from "./files/index.js";
|
|
6
7
|
import { GeminiEmbeddingAdapter, createGeminiEmbedding, geminiEmbedding } from "./adapters/embedding.js";
|
|
7
8
|
import { GeminiTTSAdapter, createGeminiSpeech, geminiSpeech } from "./adapters/tts.js";
|
|
8
9
|
import { GeminiAudioAdapter, createGeminiAudio, geminiAudio } from "./adapters/audio.js";
|
|
9
|
-
import { GEMINI_VIDEO_DURATIONS, getGeminiVideoDurationOptions, isInteractionsVideoModel } from "./video/video-provider-options.js";
|
|
10
|
+
import { GEMINI_VIDEO_DURATIONS, getGeminiVideoDurationOptions, isInteractionsVideoModel, parseGeminiOmniVideoSize } from "./video/video-provider-options.js";
|
|
10
11
|
import { GeminiVideoAdapter, createGeminiVideo, geminiVideo } from "./adapters/video.js";
|
|
11
12
|
import { geminiRealtimeToken } from "./realtime/token.js";
|
|
12
13
|
import { geminiRealtime } from "./realtime/adapter.js";
|
|
13
14
|
import "./realtime/index.js";
|
|
14
15
|
import { HarmBlockThreshold, HarmCategory } from "@google/genai";
|
|
15
|
-
export { GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS, GEMINI_EMBEDDING_MODELS, GEMINI_MODELS, GEMINI_NATIVE_IMAGE_MODELS, GEMINI_VIDEO_DURATIONS, GeminiAudioAdapter, GEMINI_AUDIO_MODELS as GeminiAudioModels, GeminiEmbeddingAdapter, GeminiImageAdapter, GEMINI_IMAGE_MODELS as GeminiImageModels, GEMINI_INTERACTIONS_VIDEO_MODELS as GeminiInteractionsVideoModels, GeminiTTSAdapter, GEMINI_TTS_MODELS as GeminiTTSModels, GEMINI_TTS_VOICES as GeminiTTSVoices, GeminiTextAdapter, GEMINI_MODELS as GeminiTextModels, GeminiVideoAdapter, GEMINI_VIDEO_MODELS as GeminiVideoModels, HarmBlockThreshold, HarmCategory, createGeminiAudio, createGeminiChat, createGeminiEmbedding, createGeminiImage, createGeminiSpeech, createGeminiSummarize, createGeminiVideo, geminiAudio, geminiEmbedding, geminiImage, geminiRealtime, geminiRealtimeToken, geminiSpeech, geminiSummarize, geminiText, geminiVideo, getGeminiVideoDurationOptions, isGeminiNativeImageModel, isInteractionsVideoModel };
|
|
16
|
+
export { GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS, GEMINI_EMBEDDING_MODELS, GEMINI_MODELS, GEMINI_NATIVE_IMAGE_MODELS, GEMINI_VIDEO_DURATIONS, GeminiAudioAdapter, GEMINI_AUDIO_MODELS as GeminiAudioModels, GeminiEmbeddingAdapter, GeminiImageAdapter, GEMINI_IMAGE_MODELS as GeminiImageModels, GEMINI_INTERACTIONS_VIDEO_MODELS as GeminiInteractionsVideoModels, GeminiTTSAdapter, GEMINI_TTS_MODELS as GeminiTTSModels, GEMINI_TTS_VOICES as GeminiTTSVoices, GeminiTextAdapter, GEMINI_MODELS as GeminiTextModels, GeminiVideoAdapter, GEMINI_VIDEO_MODELS as GeminiVideoModels, HarmBlockThreshold, HarmCategory, createGeminiAudio, createGeminiChat, createGeminiEmbedding, createGeminiImage, createGeminiSpeech, createGeminiSummarize, createGeminiVideo, geminiAudio, geminiEmbedding, geminiImage, geminiRealtime, geminiRealtimeToken, geminiSpeech, geminiSummarize, geminiText, geminiVideo, geminiVideoPart, getGeminiVideoDurationOptions, isGeminiNativeImageModel, isInteractionsVideoModel, parseGeminiOmniVideoSize, uploadGeminiFile };
|
|
@@ -48,6 +48,18 @@ export interface GeminiAudioMetadata {
|
|
|
48
48
|
*/
|
|
49
49
|
mimeType?: GeminiAudioMimeType;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* How Gemini processes a video for understanding.
|
|
53
|
+
*
|
|
54
|
+
* - `static` (default): single-pass frame sampling via `generateContent`.
|
|
55
|
+
* - `agentic`: multi-pass "agentic" video understanding, GA on the
|
|
56
|
+
* `agentic_video`-capable flash models (`gemini-3.7-flash`,
|
|
57
|
+
* `gemini-3.6-flash`, `gemini-3.5-flash-lite`). The text adapter routes the
|
|
58
|
+
* request through the Interactions API instead of `generateContent`. With
|
|
59
|
+
* `agentic`, the sampling rate is expressed in the text prompt (e.g. "watch
|
|
60
|
+
* it at 0.5 fps"), not via `fps`.
|
|
61
|
+
*/
|
|
62
|
+
export type GeminiVideoProcessing = 'agentic' | 'static';
|
|
51
63
|
/**
|
|
52
64
|
* Metadata for Gemini video content parts.
|
|
53
65
|
*/
|
|
@@ -59,6 +71,30 @@ export interface GeminiVideoMetadata {
|
|
|
59
71
|
* @see https://ai.google.dev/gemini-api/docs/vision#video-requirements
|
|
60
72
|
*/
|
|
61
73
|
mimeType?: GeminiVideoMimeType;
|
|
74
|
+
/**
|
|
75
|
+
* How the model processes this video for understanding. When set, the
|
|
76
|
+
* adapter routes the request through the Gemini Interactions API. Omit for
|
|
77
|
+
* the default single-pass `generateContent` behavior.
|
|
78
|
+
*/
|
|
79
|
+
processing?: GeminiVideoProcessing;
|
|
80
|
+
/**
|
|
81
|
+
* Frame-rate sampling density (frames per second) for single-pass
|
|
82
|
+
* (`generateContent`) understanding. Valid range (0, 24]; defaults to 1.0
|
|
83
|
+
* on the server. Ignored when `processing` is `agentic`.
|
|
84
|
+
*
|
|
85
|
+
* @see https://ai.google.dev/gemini-api/docs/vision#customize-frame-rate
|
|
86
|
+
*/
|
|
87
|
+
fps?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Clip start offset, as a decimal number of seconds with an `s` suffix
|
|
90
|
+
* (e.g. `"10.5s"`). Restricts understanding to a segment of the video.
|
|
91
|
+
*/
|
|
92
|
+
startOffset?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Clip end offset, as a decimal number of seconds with an `s` suffix
|
|
95
|
+
* (e.g. `"45s"`). Restricts understanding to a segment of the video.
|
|
96
|
+
*/
|
|
97
|
+
endOffset?: string;
|
|
62
98
|
}
|
|
63
99
|
/**
|
|
64
100
|
* Metadata for Gemini document content parts.
|
package/dist/esm/model-meta.d.ts
CHANGED
|
@@ -148,7 +148,7 @@ declare const GEMINI_3_7_FLASH: {
|
|
|
148
148
|
readonly supports: {
|
|
149
149
|
readonly input: ["text", "image", "video", "audio", "document"];
|
|
150
150
|
readonly output: ["text"];
|
|
151
|
-
readonly capabilities: ["batch_api", "caching", "function_calling", "structured_output", "thinking"];
|
|
151
|
+
readonly capabilities: ["agentic_video", "batch_api", "caching", "function_calling", "structured_output", "thinking"];
|
|
152
152
|
readonly tools: ["code_execution", "file_search", "google_search", "google_maps", "url_context", "computer_use"];
|
|
153
153
|
};
|
|
154
154
|
readonly pricing: {
|
|
@@ -169,7 +169,7 @@ declare const GEMINI_3_6_FLASH: {
|
|
|
169
169
|
readonly supports: {
|
|
170
170
|
readonly input: ["text", "image", "video", "audio", "document"];
|
|
171
171
|
readonly output: ["text"];
|
|
172
|
-
readonly capabilities: ["batch_api", "caching", "function_calling", "structured_output", "thinking"];
|
|
172
|
+
readonly capabilities: ["agentic_video", "batch_api", "caching", "function_calling", "structured_output", "thinking"];
|
|
173
173
|
readonly tools: ["code_execution", "file_search", "google_search", "google_maps", "url_context", "computer_use"];
|
|
174
174
|
};
|
|
175
175
|
readonly pricing: {
|
|
@@ -210,7 +210,7 @@ declare const GEMINI_3_5_FLASH_LITE: {
|
|
|
210
210
|
readonly supports: {
|
|
211
211
|
readonly input: ["text", "image", "video", "audio", "document"];
|
|
212
212
|
readonly output: ["text"];
|
|
213
|
-
readonly capabilities: ["batch_api", "caching", "function_calling", "structured_output", "thinking"];
|
|
213
|
+
readonly capabilities: ["agentic_video", "batch_api", "caching", "function_calling", "structured_output", "thinking"];
|
|
214
214
|
readonly tools: ["code_execution", "file_search", "google_search", "google_maps", "url_context"];
|
|
215
215
|
};
|
|
216
216
|
readonly pricing: {
|
|
@@ -270,13 +270,14 @@ export type GeminiTTSVoice = (typeof GEMINI_TTS_VOICES)[number];
|
|
|
270
270
|
* API — the video adapter routes by model.
|
|
271
271
|
* @experimental Video generation is an experimental feature and may change.
|
|
272
272
|
*/
|
|
273
|
-
export declare const GEMINI_VIDEO_MODELS: readonly ["veo-3.1-generate-preview", "veo-3.1-fast-generate-preview", "veo-3.1-lite-generate-preview", "gemini-omni-flash-preview"];
|
|
273
|
+
export declare const GEMINI_VIDEO_MODELS: readonly ["veo-3.1-generate-preview", "veo-3.1-fast-generate-preview", "veo-3.1-lite-generate-preview", "gemini-omni-1.1-flash", "gemini-omni-flash-preview"];
|
|
274
274
|
/**
|
|
275
275
|
* Video models served by the Interactions API rather than Veo's
|
|
276
|
-
* `:predictLongRunning` operations flow.
|
|
276
|
+
* `:predictLongRunning` operations flow. GA id first; the trailing
|
|
277
|
+
* `-preview` id is a shutdown alias kept so existing code compiles.
|
|
277
278
|
* @experimental Omni video generation is an experimental feature and may change.
|
|
278
279
|
*/
|
|
279
|
-
export declare const GEMINI_INTERACTIONS_VIDEO_MODELS: readonly ["gemini-omni-flash-preview"];
|
|
280
|
+
export declare const GEMINI_INTERACTIONS_VIDEO_MODELS: readonly ["gemini-omni-1.1-flash", "gemini-omni-flash-preview"];
|
|
280
281
|
/**
|
|
281
282
|
* Embedding models
|
|
282
283
|
*/
|
package/dist/esm/model-meta.js
CHANGED
|
@@ -590,11 +590,38 @@ var VEO_3_1_LITE_PREVIEW = {
|
|
|
590
590
|
}
|
|
591
591
|
};
|
|
592
592
|
/**
|
|
593
|
-
* Gemini Omni Flash —
|
|
594
|
-
* editing. Serves only the Interactions API
|
|
595
|
-
* so it routes through the
|
|
596
|
-
*
|
|
597
|
-
*
|
|
593
|
+
* Gemini Omni 1.1 Flash — GA. Multimodal video generation with
|
|
594
|
+
* conversational editing. Serves only the Interactions API
|
|
595
|
+
* (`generateContent` rejects it), so it routes through the
|
|
596
|
+
* interactions-based path of the video adapter, not Veo's
|
|
597
|
+
* `:predictLongRunning` flow. Pricing is per second of generated video
|
|
598
|
+
* ($0.10/sec). 360p / 720p (default) / 1080p / 4k at 24 FPS, 3–10 second
|
|
599
|
+
* clips (default 10s).
|
|
600
|
+
* @see https://ai.google.dev/gemini-api/docs/models/gemini-omni-flash
|
|
601
|
+
* @experimental Omni video generation is an experimental feature and may change.
|
|
602
|
+
*/
|
|
603
|
+
var GEMINI_OMNI_1_1_FLASH = {
|
|
604
|
+
name: "gemini-omni-1.1-flash",
|
|
605
|
+
max_input_tokens: 1048576,
|
|
606
|
+
max_output_tokens: 1,
|
|
607
|
+
supports: {
|
|
608
|
+
input: [
|
|
609
|
+
"text",
|
|
610
|
+
"image",
|
|
611
|
+
"video"
|
|
612
|
+
],
|
|
613
|
+
output: ["video", "audio"]
|
|
614
|
+
},
|
|
615
|
+
pricing: {
|
|
616
|
+
input: { normal: 0 },
|
|
617
|
+
output: { normal: .1 }
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
/**
|
|
621
|
+
* @deprecated `gemini-omni-flash-preview` shuts down on 2026-09-30. Use the
|
|
622
|
+
* GA id `gemini-omni-1.1-flash` instead. Kept in the model union so existing
|
|
623
|
+
* code still compiles until shutdown.
|
|
624
|
+
* @see https://ai.google.dev/gemini-api/docs/models/gemini-omni-flash
|
|
598
625
|
* @experimental Omni video generation is an experimental feature and may change.
|
|
599
626
|
*/
|
|
600
627
|
var GEMINI_OMNI_FLASH_PREVIEW = {
|
|
@@ -629,6 +656,7 @@ var GEMINI_3_7_FLASH = {
|
|
|
629
656
|
],
|
|
630
657
|
output: ["text"],
|
|
631
658
|
capabilities: [
|
|
659
|
+
"agentic_video",
|
|
632
660
|
"batch_api",
|
|
633
661
|
"caching",
|
|
634
662
|
"function_calling",
|
|
@@ -667,6 +695,7 @@ var GEMINI_3_6_FLASH = {
|
|
|
667
695
|
],
|
|
668
696
|
output: ["text"],
|
|
669
697
|
capabilities: [
|
|
698
|
+
"agentic_video",
|
|
670
699
|
"batch_api",
|
|
671
700
|
"caching",
|
|
672
701
|
"function_calling",
|
|
@@ -742,6 +771,7 @@ var GEMINI_3_5_FLASH_LITE = {
|
|
|
742
771
|
],
|
|
743
772
|
output: ["text"],
|
|
744
773
|
capabilities: [
|
|
774
|
+
"agentic_video",
|
|
745
775
|
"batch_api",
|
|
746
776
|
"caching",
|
|
747
777
|
"function_calling",
|
|
@@ -870,14 +900,16 @@ var GEMINI_VIDEO_MODELS = [
|
|
|
870
900
|
VEO_3_1_PREVIEW.name,
|
|
871
901
|
VEO_3_1_FAST_PREVIEW.name,
|
|
872
902
|
VEO_3_1_LITE_PREVIEW.name,
|
|
903
|
+
GEMINI_OMNI_1_1_FLASH.name,
|
|
873
904
|
GEMINI_OMNI_FLASH_PREVIEW.name
|
|
874
905
|
];
|
|
875
906
|
/**
|
|
876
907
|
* Video models served by the Interactions API rather than Veo's
|
|
877
|
-
* `:predictLongRunning` operations flow.
|
|
908
|
+
* `:predictLongRunning` operations flow. GA id first; the trailing
|
|
909
|
+
* `-preview` id is a shutdown alias kept so existing code compiles.
|
|
878
910
|
* @experimental Omni video generation is an experimental feature and may change.
|
|
879
911
|
*/
|
|
880
|
-
var GEMINI_INTERACTIONS_VIDEO_MODELS = [GEMINI_OMNI_FLASH_PREVIEW.name];
|
|
912
|
+
var GEMINI_INTERACTIONS_VIDEO_MODELS = [GEMINI_OMNI_1_1_FLASH.name, GEMINI_OMNI_FLASH_PREVIEW.name];
|
|
881
913
|
/**
|
|
882
914
|
* Embedding models
|
|
883
915
|
*/
|