@tanstack/ai-gemini 0.23.0 → 0.24.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/dist/esm/adapters/image.d.ts +14 -7
- package/dist/esm/adapters/image.js +27 -56
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/video.js +2 -1
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/experimental/text-interactions/adapter.js +3 -9
- package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
- package/dist/esm/image/image-provider-options.d.ts +167 -20
- package/dist/esm/image/image-provider-options.js +52 -5
- package/dist/esm/image/image-provider-options.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/model-meta.d.ts +15 -2
- package/dist/esm/model-meta.js +82 -1
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/realtime/adapter.js +1 -5
- package/dist/esm/realtime/adapter.js.map +1 -1
- package/dist/esm/realtime/client.js.map +1 -1
- package/dist/esm/realtime/token.js +3 -2
- package/dist/esm/realtime/token.js.map +1 -1
- package/dist/esm/realtime/utils.js +4 -1
- package/dist/esm/realtime/utils.js.map +1 -1
- package/dist/esm/tools/tool-converter.js +0 -1
- package/dist/esm/tools/tool-converter.js.map +1 -1
- package/dist/esm/usage.js +1 -3
- package/dist/esm/usage.js.map +1 -1
- package/package.json +5 -5
- package/src/adapters/image.ts +123 -36
- package/src/image/image-provider-options.ts +226 -25
- package/src/index.ts +28 -0
- package/src/model-meta.ts +118 -1
|
@@ -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} 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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"}
|
|
@@ -682,9 +682,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
682
682
|
timestamp
|
|
683
683
|
};
|
|
684
684
|
break;
|
|
685
|
-
default:
|
|
686
|
-
logger.provider(`gemini-text-interactions unhandled step.start`, { step });
|
|
687
|
-
break;
|
|
685
|
+
default: logger.provider(`gemini-text-interactions unhandled step.start`, { step });
|
|
688
686
|
}
|
|
689
687
|
break;
|
|
690
688
|
}
|
|
@@ -788,9 +786,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
788
786
|
};
|
|
789
787
|
break;
|
|
790
788
|
}
|
|
791
|
-
default:
|
|
792
|
-
logger.provider(`gemini-text-interactions unhandled step.delta type`, { delta });
|
|
793
|
-
break;
|
|
789
|
+
default: logger.provider(`gemini-text-interactions unhandled step.delta type`, { delta });
|
|
794
790
|
}
|
|
795
791
|
break;
|
|
796
792
|
}
|
|
@@ -883,9 +879,7 @@ async function* translateInteractionEvents(stream, model, runId, threadId, paren
|
|
|
883
879
|
};
|
|
884
880
|
return;
|
|
885
881
|
}
|
|
886
|
-
default:
|
|
887
|
-
logger.provider(`gemini-text-interactions unhandled event_type`, { event });
|
|
888
|
-
break;
|
|
882
|
+
default: logger.provider(`gemini-text-interactions unhandled event_type`, { event });
|
|
889
883
|
}
|
|
890
884
|
}
|
|
891
885
|
yield* closeOpenState();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","names":[],"sources":["../../../../src/experimental/text-interactions/adapter.ts"],"sourcesContent":["import { EventType } from '@tanstack/ai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { parse as parsePartialJSON } from 'partial-json'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../../utils/client'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type {\n GeminiChatModelToolCapabilitiesByName,\n GeminiModelInputModalitiesByName,\n GeminiModels,\n} from '../../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type { GoogleGenAI, Interactions } from '@google/genai'\nimport type {\n ContentPart,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n Tool,\n} from '@tanstack/ai'\n\nimport type {\n GeminiInteractionsCustomEvent,\n GeminiInteractionsCustomEventValue,\n GeminiInteractionsStream,\n} from './events'\nimport type { ExternalTextInteractionsProviderOptions } from './provider-options'\nimport type { GeminiMessageMetadataByModality } from '../../message-types'\nimport type { GeminiClientConfig } from '../../utils/client'\n\ntype Interaction = Interactions.Interaction\ntype InteractionSSEEvent = Interactions.InteractionSSEEvent\n\nexport type GeminiTextInteractionsConfig = GeminiClientConfig\n\nexport type GeminiTextInteractionsProviderOptions =\n ExternalTextInteractionsProviderOptions\n\ntype InteractionsTool = NonNullable<\n Interactions.CreateModelInteractionParamsStreaming['tools']\n>[number]\n\ntype ContentBlock = Interactions.Content\n\n// The Interactions API takes `input` as a list of *Steps* (not a list of\n// content blocks, and not a list of `Turn`s — the SDK's type union is\n// misleading on both counts). The live API enforces the Step envelope —\n// raw content arrays produce `invalid_request` / \"value at top-level\n// must be a list\". The wire discriminator is snake_case\n// (`user_input` / `function_result`); see\n// https://ai.google.dev/api/interactions-api for the full Step union.\ntype UserInputStep = {\n type: 'user_input'\n content: Array<ContentBlock>\n}\ntype FunctionResultStep = {\n type: 'function_result'\n call_id: string\n name?: string\n result: string\n}\ntype InteractionsStep = UserInputStep | FunctionResultStep\ntype InteractionsRequestInput = Array<InteractionsStep>\n\n// Concrete wire shape we send to `client.interactions.create`. The SDK's\n// own param union types `input` as `string | Content[] | Turn[] | ...`\n// which is wrong for the live API (see the InteractionsRequestInput\n// comment above), so we type `input` ourselves and cast just once at the\n// SDK boundary instead of casting every field through.\ntype GeminiInteractionsRequestBody = Omit<\n Interactions.CreateModelInteractionParamsStreaming,\n 'input' | 'stream'\n> & {\n input: InteractionsRequestInput\n stream?: boolean\n}\n\ntype ToolCallState = {\n name: string\n // Accumulated args as a parsed object. Kept here in object form so a\n // garbled delta can't corrupt previously-merged fragments (the prior\n // string-then-reparse pipeline replaced the whole accumulator on any\n // parse failure). Stringified only when emitting AG-UI events.\n args: Record<string, unknown>\n index: number\n started: boolean\n ended: boolean\n}\n\n// ===========================\n// Type Resolution Helpers\n// ===========================\n\n/**\n * Resolve provider options for a specific model. The Interactions API's\n * request shape is the same across all chat-capable Gemini models — the\n * SDK doesn't expose a per-model param union — so this currently falls\n * through to the flat `GeminiTextInteractionsProviderOptions` for every\n * model. The alias exists for parity with `GeminiTextAdapter`, so a\n * per-model map can be slotted in later without changing the adapter\n * signature.\n */\ntype ResolveProviderOptions = GeminiTextInteractionsProviderOptions\n\n/**\n * Resolve input modalities for a specific model. Reuses the chat-model\n * modality map from `model-meta.ts`: passing a `document` content block\n * to a model that doesn't support it is a compile error, matching the\n * sibling `GeminiTextAdapter`.\n */\ntype ResolveInputModalities<TModel extends string> =\n TModel extends keyof GeminiModelInputModalitiesByName\n ? GeminiModelInputModalitiesByName[TModel]\n : readonly ['text', 'image', 'audio', 'video', 'document']\n\n/**\n * Resolve tool capabilities for a specific model. Reuses the chat-model\n * capability map: `google_maps` / `google_search_retrieval` /\n * `mcp_server` are rejected at runtime by `convertToolsToInteractionsFormat`,\n * but per-model gating happens here at compile time.\n */\ntype ResolveToolCapabilities<TModel extends string> =\n TModel extends keyof GeminiChatModelToolCapabilitiesByName\n ? NonNullable<GeminiChatModelToolCapabilitiesByName[TModel]>\n : readonly []\n\n/**\n * Tree-shakeable adapter for Gemini's stateful Interactions API. Routes\n * through `client.interactions.create` and surfaces the server-assigned\n * `interactionId` via an AG-UI `CUSTOM` event with\n * `name: 'gemini.interactionId'` emitted just before `RUN_FINISHED`; pass\n * that id back on the next turn via `modelOptions.previous_interaction_id`\n * to continue the conversation without resending history.\n *\n * The Interactions API does NOT support stateless multi-turn replay —\n * passing more than one message in `messages` without a\n * `previous_interaction_id` throws. For a chat UI that maintains local\n * history (e.g. `useChat`), see the \"Wiring with `useChat`\" section of\n * `docs/adapters/gemini.md` for the canonical client/server pattern.\n *\n * Supports user-defined function tools and the built-in tools\n * `google_search`, `code_execution`, `url_context`, `file_search`, and\n * `computer_use`. Built-in tool *activity* for the four search/exec\n * variants is surfaced via `CUSTOM` events\n * (`gemini.googleSearchCall` / `gemini.googleSearchResult` and the\n * corresponding per-tool variants) carrying the raw Interactions delta;\n * see {@link GeminiInteractionsCustomEvent}. `computer_use` is accepted\n * in the request but the Interactions API does not currently stream\n * per-delta CUSTOM events for it. `google_search_retrieval`,\n * `google_maps`, and `mcp_server` are not supported on this adapter.\n *\n * @experimental Interactions API is in Beta per Google; shapes may change.\n * @see https://ai.google.dev/gemini-api/docs/interactions\n */\nexport class GeminiTextInteractionsAdapter<\n TModel extends GeminiModels,\n TProviderOptions extends Record<string, any> = ResolveProviderOptions,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n TToolCapabilities extends ReadonlyArray<string> =\n ResolveToolCapabilities<TModel>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n GeminiMessageMetadataByModality,\n TToolCapabilities\n> {\n override readonly kind = 'text' as const\n override readonly name = 'gemini-text-interactions' as const\n\n private readonly client: GoogleGenAI\n // Tracks the most recent server-assigned interaction id per threadId\n // so the adapter can chain follow-up calls on the same thread without\n // the caller having to thread the id manually. Two callers rely on\n // this:\n // 1. The agent loop's tool-call iterations (each iteration is a new\n // `chatStream` call with accumulated tool messages).\n // 2. The agentic-structured composition: a `chatStream` run followed\n // by `structuredOutput` on the accumulated messages.\n // Cross-request chaining is the caller's job via\n // `modelOptions.previous_interaction_id`. To keep stale ids from\n // chaining a brand-new turn, `chatStream` evicts at the START when\n // the caller signals fresh-turn intent (no caller-provided id AND a\n // single user message — anything else is a follow-up). Errors evict\n // immediately so a failed turn never chains into the next one.\n private readonly interactionIdByThread = new Map<string, string>()\n\n constructor(config: GeminiTextInteractionsConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n }\n\n async *chatStream(\n options: TextOptions<GeminiTextInteractionsProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const runId = options.runId ?? generateId(this.name)\n const threadId = options.threadId ?? generateId(this.name)\n const timestamp = Date.now()\n const { logger } = options\n\n // Fresh-turn intent: caller didn't thread an id AND only a single\n // user message is queued. Drop any stale captured id so we don't\n // silently chain off a prior turn the caller doesn't know about.\n // Multi-message inputs are follow-ups (agent-loop iteration or\n // structuredOutput composition) and keep the Map entry.\n if (\n !options.modelOptions?.previous_interaction_id &&\n options.messages.length === 1 &&\n options.messages[0]?.role === 'user'\n ) {\n this.interactionIdByThread.delete(threadId)\n }\n\n // Resolve `previous_interaction_id`. Caller-provided wins; otherwise\n // fall back to the id we captured during a prior iteration of this\n // same agent-loop run (matched by threadId).\n const effectivePreviousInteractionId =\n options.modelOptions?.previous_interaction_id ??\n this.interactionIdByThread.get(threadId)\n\n let sawTerminalEvent = false\n // Sentinel for the `.return()` abandonment path. Set to `true` only at\n // the bottom of the `try` block — so a consumer-initiated close (via\n // upstream `break` or abort) leaves it `false`, distinguishing\n // abandonment from normal completion. This is the only signal that\n // catches abandonment AFTER a `RUN_FINISHED(tool_calls)`, where\n // `sawTerminalEvent` is `true` but the in-loop deliberately kept the\n // map entry for an agent-loop iteration that will now never run.\n let completedTryBlock = false\n try {\n const request = buildInteractionsRequest({\n ...options,\n modelOptions: {\n ...options.modelOptions,\n previous_interaction_id: effectivePreviousInteractionId,\n },\n })\n logger.request(\n `activity=chat provider=gemini-text-interactions model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n {\n provider: 'gemini-text-interactions',\n model: this.model,\n request,\n },\n )\n const stream = (await this.client.interactions.create(\n { ...request, stream: true } as GeminiInteractionsRequestBody &\n Parameters<typeof this.client.interactions.create>[0],\n { signal: options.abortController?.signal },\n )) as AsyncIterable<InteractionSSEEvent>\n\n for await (const chunk of translateInteractionEvents(\n stream,\n options.model,\n runId,\n threadId,\n options.parentRunId,\n timestamp,\n this.name,\n logger,\n )) {\n // Capture the server-assigned id so the next agent-loop\n // iteration on this thread can chain off it. The CUSTOM event\n // is also yielded downstream as usual — callers consume it via\n // `onCustomEvent` for cross-request chaining.\n //\n // The yield type can't be narrowed to GeminiInteractionsStream\n // here without fighting zod-passthrough variance (StreamChunk's\n // CustomEvent variant carries `[k: string]: unknown`), so we\n // narrow via the literal `name` and trust the typed\n // construction inside `translateInteractionEvents`.\n if (\n chunk.type === EventType.CUSTOM &&\n chunk.name === 'gemini.interactionId'\n ) {\n const value =\n chunk.value as GeminiInteractionsCustomEventValue<'gemini.interactionId'>\n this.interactionIdByThread.set(threadId, value.interactionId)\n }\n if (chunk.type === EventType.RUN_FINISHED) {\n sawTerminalEvent = true\n // Keep the captured id for follow-ups (next agent-loop\n // iteration OR a structuredOutput call composing this turn).\n // The next *fresh* chatStream call evicts at the top via the\n // fresh-turn guard.\n } else if (chunk.type === EventType.RUN_ERROR) {\n sawTerminalEvent = true\n this.interactionIdByThread.delete(threadId)\n }\n yield chunk\n }\n\n if (!sawTerminalEvent) {\n // SDK stream ended without either `interaction.complete` or\n // `error` — surface the truncation rather than silently leaving\n // downstream consumers waiting on a `RUN_FINISHED` that will\n // never come.\n this.interactionIdByThread.delete(threadId)\n const message =\n 'Gemini Interactions stream ended without a terminal event (no interaction.complete or error)'\n logger.errors('gemini-text-interactions.chatStream truncated', {\n source: 'gemini-text-interactions.chatStream',\n runId,\n threadId,\n })\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: options.model,\n timestamp,\n message,\n error: { message },\n }\n }\n completedTryBlock = true\n } catch (error) {\n this.interactionIdByThread.delete(threadId)\n const message =\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during the interactions stream.'\n logger.errors('gemini-text-interactions.chatStream fatal', {\n error,\n source: 'gemini-text-interactions.chatStream',\n })\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: options.model,\n timestamp,\n message,\n error: { message },\n }\n } finally {\n // Abandonment cleanup — consumer `.return()` (upstream `break` /\n // abort) bypasses both the truncation guard and the catch handler.\n // `completedTryBlock` is the sentinel that distinguishes natural\n // completion from abandonment; on abandonment we evict so a stale\n // id from a half-finished turn can't chain into a follow-up. The\n // catch handler also lands here with the flag false; its explicit\n // delete is harmless to repeat.\n if (!completedTryBlock) {\n this.interactionIdByThread.delete(threadId)\n }\n }\n }\n\n async structuredOutput(\n options: StructuredOutputOptions<GeminiTextInteractionsProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const { logger } = chatOptions\n const threadId = chatOptions.threadId\n\n // Mirror the chatStream fallback: the agentic-structured flow runs\n // the chat loop first and then calls structuredOutput with the\n // accumulated `messages`. If any tool ran during the loop, the\n // messages include assistant/tool turns; without a chained\n // previous_interaction_id those would throw \"cannot send prior\n // conversation history on a fresh interaction\".\n const effectivePreviousInteractionId =\n chatOptions.modelOptions?.previous_interaction_id ??\n (threadId ? this.interactionIdByThread.get(threadId) : undefined)\n\n const baseRequest = buildInteractionsRequest({\n ...chatOptions,\n modelOptions: {\n ...chatOptions.modelOptions,\n previous_interaction_id: effectivePreviousInteractionId,\n },\n })\n\n // SDK 2.x: `response_mime_type` has been removed and `response_format`\n // is now polymorphic — each entry has a `type` discriminator and the\n // mime type lives inside the entry. See:\n // https://ai.google.dev/gemini-api/docs/interactions-breaking-changes-may-2026\n const request: GeminiInteractionsRequestBody = {\n ...baseRequest,\n response_format: {\n type: 'text',\n mime_type: 'application/json',\n schema: outputSchema,\n },\n }\n\n try {\n logger.request(\n `activity=chat provider=gemini-text-interactions model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,\n {\n provider: 'gemini-text-interactions',\n model: this.model,\n request,\n },\n )\n const result = (await this.client.interactions.create(\n request as Parameters<typeof this.client.interactions.create>[0],\n { signal: chatOptions.abortController?.signal },\n )) as Interaction\n\n const rawText = extractTextFromInteraction(result)\n\n if (!rawText) {\n throw new Error(\n `Gemini Interactions returned no text output for structured-output request (status: ${result.status}). The model may have produced only tool calls or non-text content.`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n return { data: parsed, rawText }\n } catch (error) {\n logger.errors('gemini-text-interactions.structuredOutput fatal', {\n error,\n source: 'gemini-text-interactions.structuredOutput',\n })\n // Preserve the original error as `cause` so the stack trace and any\n // SDK-attached status/code/headers survive for Sentry dedup.\n throw new Error(\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during structured output generation.',\n { cause: error },\n )\n }\n }\n}\n\n/** @experimental Interactions API is in Beta. */\nexport function createGeminiTextInteractions<TModel extends GeminiModels>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiTextInteractionsConfig, 'apiKey'>,\n): GeminiTextInteractionsAdapter<\n TModel,\n ResolveProviderOptions,\n ResolveInputModalities<TModel>,\n ResolveToolCapabilities<TModel>\n> {\n return new GeminiTextInteractionsAdapter({ apiKey, ...config }, model)\n}\n\n/** @experimental Interactions API is in Beta. */\nexport function geminiTextInteractions<TModel extends GeminiModels>(\n model: TModel,\n config?: Omit<GeminiTextInteractionsConfig, 'apiKey'>,\n): GeminiTextInteractionsAdapter<\n TModel,\n ResolveProviderOptions,\n ResolveInputModalities<TModel>,\n ResolveToolCapabilities<TModel>\n> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiTextInteractions(model, apiKey, config)\n}\n\nfunction buildInteractionsRequest(\n options: TextOptions<GeminiTextInteractionsProviderOptions>,\n): GeminiInteractionsRequestBody {\n const modelOpts = options.modelOptions\n\n const systemInstruction =\n modelOpts?.system_instruction ?? options.systemPrompts?.join('\\n')\n\n const generationConfig: Interactions.GenerationConfig = {\n ...modelOpts?.generation_config,\n }\n\n const hasGenerationConfig = Object.keys(generationConfig).length > 0\n\n const input = convertMessagesToInteractionsInput(\n options.messages,\n modelOpts?.previous_interaction_id !== undefined,\n )\n\n return {\n model: options.model,\n input,\n previous_interaction_id: modelOpts?.previous_interaction_id,\n system_instruction: systemInstruction,\n tools: convertToolsToInteractionsFormat(options.tools),\n generation_config: hasGenerationConfig ? generationConfig : undefined,\n store: modelOpts?.store,\n background: modelOpts?.background,\n response_modalities: modelOpts?.response_modalities,\n response_format: modelOpts?.response_format,\n }\n}\n\n// Google's Interactions API takes `input` as `Array<Step>`. Each Step\n// is `{type: 'user_input' | 'function_result' | ..., ...}` — content\n// blocks (text/image/etc.) live nested inside a Step's `content` array,\n// they are NOT valid at the top level. Sending raw `Array<Content>`\n// produces `invalid_request` / \"value at top-level must be a list\",\n// because the API is looking for a Step list at the top level and\n// gets content objects instead. The SDK's type union\n// (`string | Array<Content> | Array<Turn> | ...`) is misleading; see\n// https://ai.google.dev/api/interactions-api for the real Step union.\n//\n// When `hasPreviousInteraction` is true the server holds the transcript\n// up through the last assistant turn, so we only send the steps that\n// come after it (a new `user_input`, one or more `function_result`s\n// continuing a tool call, etc.). Otherwise the conversation is fresh\n// and only the latest user turn is supported — multi-turn replay\n// without `previous_interaction_id` is not part of the API contract.\nfunction convertMessagesToInteractionsInput(\n messages: Array<ModelMessage>,\n hasPreviousInteraction: boolean,\n): InteractionsRequestInput {\n const toolCallIdToName = new Map<string, string>()\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.toolCalls) {\n for (const tc of msg.toolCalls) {\n toolCallIdToName.set(tc.id, tc.function.name)\n }\n }\n }\n\n const source = hasPreviousInteraction\n ? messagesAfterLastAssistant(messages)\n : messages\n\n if (hasPreviousInteraction && source.length === 0) {\n throw new Error(\n 'Gemini Interactions adapter: modelOptions.previous_interaction_id was provided but no new messages were found after the last assistant turn. Append at least one user or tool message before chaining.',\n )\n }\n\n if (!hasPreviousInteraction) {\n const [only, ...rest] = source\n if (!only) {\n throw new Error('Gemini Interactions adapter: no messages to send.')\n }\n if (rest.length > 0) {\n throw new Error(\n 'Gemini Interactions adapter: cannot send prior conversation history on a fresh interaction. Either set modelOptions.previous_interaction_id to chain prior turns server-side, or trim the message list to a single new user turn. See docs/adapters/gemini.md (\"Wiring with useChat\") for the canonical client/server pattern.',\n )\n }\n if (only.role !== 'user') {\n throw new Error(\n `Gemini Interactions adapter: the first message of a fresh interaction must be a user turn (got role=\"${only.role}\"). Set modelOptions.previous_interaction_id to continue an existing interaction.`,\n )\n }\n const content = messageToContentBlocks(only)\n if (content.length === 0) {\n throw new Error(\n 'Gemini Interactions adapter: the user message produced no content blocks to send.',\n )\n }\n return [{ type: 'user_input', content }]\n }\n\n // Chained path: each post-assistant message becomes one Step. A user\n // reply maps to `user_input`; a tool reply maps to `function_result`.\n // Assistant turns shouldn't appear here (sliced off above) — if one\n // somehow does we skip it rather than letting it shape the wire.\n const steps: Array<InteractionsStep> = []\n for (const msg of source) {\n if (msg.role === 'tool' && msg.toolCallId) {\n const result = serializeToolResultContent(msg.content)\n steps.push({\n type: 'function_result',\n call_id: msg.toolCallId,\n name: toolCallIdToName.get(msg.toolCallId),\n result,\n })\n } else if (msg.role === 'user') {\n const content = messageToContentBlocks(msg)\n if (content.length > 0) {\n steps.push({ type: 'user_input', content })\n }\n }\n }\n if (steps.length === 0) {\n throw new Error(\n 'Gemini Interactions adapter: messages after the last assistant turn produced no steps to send.',\n )\n }\n return steps\n}\n\n// The Interactions API's `function_result.result` field is a string. We\n// fail loudly on non-string tool content rather than silently coercing\n// to `''` — silent coercion meant the model lost the entire tool\n// output for callers that returned content as an array (e.g. image +\n// text) or `null`. If you need to send structured tool output, encode\n// it yourself before passing.\nfunction serializeToolResultContent(\n content: ModelMessage['content'] | undefined,\n): string {\n if (typeof content === 'string') return content\n if (content === null || content === undefined) {\n throw new Error(\n 'Gemini Interactions adapter: tool message has no content. The Interactions API requires a string `result` on function_result steps — return a string from your tool implementation (encode JSON/multimodal output yourself).',\n )\n }\n throw new Error(\n 'Gemini Interactions adapter: tool message content must be a string (got an array of content parts). The Interactions API requires a string `result` on function_result steps — stringify multimodal tool output before returning it from your tool.',\n )\n}\n\n// Extracts the content blocks (text / image / audio / video / document)\n// from a single message. Tool calls and tool results live one level up\n// as Steps, not as content, so they are NOT emitted here.\nfunction messageToContentBlocks(msg: ModelMessage): Array<ContentBlock> {\n const blocks: Array<ContentBlock> = []\n\n if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n blocks.push(contentPartToBlock(part))\n }\n } else if (\n typeof msg.content === 'string' &&\n msg.content &&\n msg.role !== 'tool'\n ) {\n blocks.push({ type: 'text', text: msg.content })\n }\n\n return blocks\n}\n\nfunction messagesAfterLastAssistant(\n messages: Array<ModelMessage>,\n): Array<ModelMessage> {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i]?.role === 'assistant') {\n return messages.slice(i + 1)\n }\n }\n return messages\n}\n\n// Leniently parse the *accumulated* streamed tool-call argument buffer.\n// Streamed `arguments_delta` fragments are individually incomplete JSON, so\n// a strict `JSON.parse` would throw (and log noise) on every fragment until\n// the final one. `partial-json` recovers a best-effort object from a\n// truncated buffer instead. Returns `undefined` when nothing usable could be\n// parsed, so callers can keep the last good value rather than clobber\n// previously-merged args with `{}`.\nfunction parsePartialToolArguments(\n raw: string,\n): Record<string, unknown> | undefined {\n if (!raw) return undefined\n try {\n const parsed = parsePartialJSON(raw)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : undefined\n } catch {\n return undefined\n }\n}\n\n// `satisfies` pins these arrays to the SDK's narrow mime-type unions: if\n// Google removes a format the build breaks, and if they add one ours keeps\n// working (we just won't accept the new one until added here).\nconst IMAGE_MIME_TYPES = [\n 'image/png',\n 'image/jpeg',\n 'image/webp',\n 'image/heic',\n 'image/heif',\n] as const satisfies ReadonlyArray<\n NonNullable<Interactions.ImageContent['mime_type']>\n>\n\nconst AUDIO_MIME_TYPES = [\n 'audio/wav',\n 'audio/mp3',\n 'audio/aiff',\n 'audio/aac',\n 'audio/ogg',\n 'audio/flac',\n] as const satisfies ReadonlyArray<\n NonNullable<Interactions.AudioContent['mime_type']>\n>\n\nconst VIDEO_MIME_TYPES = [\n 'video/mp4',\n 'video/mpeg',\n 'video/mpg',\n 'video/mov',\n 'video/avi',\n 'video/x-flv',\n 'video/webm',\n 'video/wmv',\n 'video/3gpp',\n] as const satisfies ReadonlyArray<\n NonNullable<Interactions.VideoContent['mime_type']>\n>\n\nconst DOCUMENT_MIME_TYPES = [\n 'application/pdf',\n] as const satisfies ReadonlyArray<\n NonNullable<Interactions.DocumentContent['mime_type']>\n>\n\nfunction validateMime<T extends string>(\n allowed: ReadonlyArray<T>,\n value: string | undefined,\n kind: string,\n): T | undefined {\n if (value === undefined) return undefined\n if ((allowed as ReadonlyArray<string>).includes(value)) {\n return value as T\n }\n throw new Error(\n `Unsupported ${kind} mime type \"${value}\" for the Gemini Interactions API. Allowed: ${allowed.join(', ')}.`,\n )\n}\n\nfunction contentPartToBlock(part: ContentPart): ContentBlock {\n if (part.type === 'text') {\n return { type: 'text', text: part.content }\n }\n const isData = part.source.type === 'data'\n switch (part.type) {\n case 'image': {\n const mime_type = validateMime(\n IMAGE_MIME_TYPES,\n part.source.mimeType,\n 'image',\n )\n return isData\n ? { type: 'image', data: part.source.value, mime_type }\n : { type: 'image', uri: part.source.value, mime_type }\n }\n case 'audio': {\n const mime_type = validateMime(\n AUDIO_MIME_TYPES,\n part.source.mimeType,\n 'audio',\n )\n return isData\n ? { type: 'audio', data: part.source.value, mime_type }\n : { type: 'audio', uri: part.source.value, mime_type }\n }\n case 'video': {\n const mime_type = validateMime(\n VIDEO_MIME_TYPES,\n part.source.mimeType,\n 'video',\n )\n return isData\n ? { type: 'video', data: part.source.value, mime_type }\n : { type: 'video', uri: part.source.value, mime_type }\n }\n case 'document': {\n const mime_type = validateMime(\n DOCUMENT_MIME_TYPES,\n part.source.mimeType,\n 'document',\n )\n return isData\n ? { type: 'document', data: part.source.value, mime_type }\n : { type: 'document', uri: part.source.value, mime_type }\n }\n }\n}\n\n// Built-in Gemini tools use snake_case field names in the Interactions API\n// that differ from the camelCase fields used on `client.models.generateContent`\n// (e.g. `fileSearchStoreNames` vs `file_search_store_names`). Translate\n// explicitly so callers keep using the same tool factories across adapters.\nfunction convertToolsToInteractionsFormat<TTool extends Tool>(\n tools: Array<TTool> | undefined,\n): Array<InteractionsTool> | undefined {\n if (!tools || tools.length === 0) return undefined\n\n const result: Array<InteractionsTool> = []\n\n for (const tool of tools) {\n switch (tool.name) {\n case 'google_search': {\n const metadata = (tool.metadata ?? {}) as {\n search_types?: Array<'web_search' | 'image_search'>\n }\n result.push({\n type: 'google_search',\n ...(metadata.search_types\n ? { search_types: metadata.search_types }\n : {}),\n })\n break\n }\n case 'code_execution': {\n result.push({ type: 'code_execution' })\n break\n }\n case 'url_context': {\n result.push({ type: 'url_context' })\n break\n }\n case 'file_search': {\n const metadata = (tool.metadata ?? {}) as {\n fileSearchStoreNames?: Array<string>\n topK?: number\n metadataFilter?: string\n }\n result.push({\n type: 'file_search',\n ...(metadata.fileSearchStoreNames\n ? { file_search_store_names: metadata.fileSearchStoreNames }\n : {}),\n ...(metadata.topK !== undefined ? { top_k: metadata.topK } : {}),\n ...(metadata.metadataFilter !== undefined\n ? { metadata_filter: metadata.metadataFilter }\n : {}),\n })\n break\n }\n case 'computer_use': {\n const metadata = (tool.metadata ?? {}) as {\n environment?: string\n excludedPredefinedFunctions?: Array<string>\n }\n if (metadata.environment && metadata.environment !== 'browser') {\n throw new Error(\n `computer_use environment \"${metadata.environment}\" is not supported on the Gemini Interactions API. Only \"browser\" is accepted.`,\n )\n }\n result.push({\n type: 'computer_use',\n ...(metadata.environment\n ? { environment: metadata.environment as 'browser' }\n : {}),\n ...(metadata.excludedPredefinedFunctions\n ? {\n excludedPredefinedFunctions:\n metadata.excludedPredefinedFunctions,\n }\n : {}),\n })\n break\n }\n case 'google_search_retrieval':\n throw new Error(\n '`google_search_retrieval` is not supported on the Gemini Interactions API. Use `googleSearchTool()` (`google_search`) with `geminiTextInteractions()`, or call `geminiText()` for the legacy retrieval tool.',\n )\n case 'google_maps':\n throw new Error(\n '`google_maps` is not yet supported on the Gemini Interactions API. Use `geminiText()` for Google Maps grounding.',\n )\n case 'mcp_server':\n throw new Error(\n '`mcp_server` is not yet supported on the `geminiTextInteractions()` adapter.',\n )\n default: {\n if (!tool.description) {\n throw new Error(\n `Tool ${tool.name} requires a description for the Gemini Interactions adapter`,\n )\n }\n result.push({\n type: 'function',\n name: tool.name,\n description: tool.description,\n parameters: sanitizeToolParameters(\n tool.inputSchema ?? { type: 'object', properties: {} },\n ),\n })\n }\n }\n }\n\n return result\n}\n\n// Map of API-level status values onto the AG-UI `finishReason` field.\n// `requires_action` is the Interactions API's signal that the model\n// produced one or more function calls and is waiting for results — we\n// always map that to 'tool_calls' regardless of whether deltas were\n// observed (a function_call may have arrived in a single delta with no\n// other content). `incomplete` is the truncation signal (max_tokens\n// exceeded etc.) — map to 'length'. `completed` is normal stop.\nfunction statusToFinishReason(\n status: Interaction['status'] | undefined,\n sawFunctionCall: boolean,\n): 'stop' | 'length' | 'tool_calls' | null {\n if (status === 'requires_action') return 'tool_calls'\n if (status === 'incomplete') return 'length'\n if (sawFunctionCall) return 'tool_calls'\n return 'stop'\n}\n\n// Statuses that mean the interaction did not produce a usable result.\n// `failed`/`cancelled` map to RUN_ERROR. `incomplete` is the model\n// hitting a stop condition (max_tokens etc.) and is reported via\n// `finishReason: 'length'` on RUN_FINISHED so callers can decide how to\n// react without it looking like a hard error.\nfunction statusIsError(\n status: Interaction['status'] | undefined,\n): status is 'failed' | 'cancelled' {\n return status === 'failed' || status === 'cancelled'\n}\n\nasync function* translateInteractionEvents(\n stream: AsyncIterable<InteractionSSEEvent>,\n model: string,\n runId: string,\n threadId: string,\n parentRunId: string | undefined,\n timestamp: number,\n adapterName: string,\n logger: InternalLogger,\n): AsyncIterable<StreamChunk> {\n const messageId = generateId(adapterName)\n let hasEmittedRunStarted = false\n let hasEmittedTextMessageStart = false\n let textAccumulated = ''\n let interactionId: string | undefined\n let sawFunctionCall = false\n const toolCalls = new Map<string, ToolCallState>()\n let nextToolIndex = 0\n let thinkingStepId: string | null = null\n let thinkingAccumulated = ''\n let reasoningMessageId: string | null = null\n let hasClosedReasoning = false\n // SDK 2.x routes events by step `index`, not by content id. We need to\n // map the index of an in-flight `function_call` step back to the tool\n // call id so subsequent `step.delta` (arguments_delta) and `step.stop`\n // events can update / close the right TOOL_CALL_*.\n const indexToToolCallId = new Map<number, string>()\n // Function-call arguments now stream as partial JSON fragments\n // (`StepDelta.ArgumentsDelta.arguments`) rather than as pre-parsed\n // object deltas. Buffer the raw strings per tool call so we can\n // attempt one JSON parse per delta and recover gracefully if the\n // fragment isn't yet syntactically complete.\n const argStringByToolCallId = new Map<string, string>()\n\n const closeReasoningIfNeeded = function* (): Generator<StreamChunk> {\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model,\n timestamp,\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model,\n timestamp,\n }\n // Reset so that a later `thought_summary` delta (the API\n // interleaves text → thought → text on some models) opens a\n // fresh reasoning block instead of re-using an already-ended\n // messageId, which would violate AG-UI ordering.\n thinkingStepId = null\n reasoningMessageId = null\n hasClosedReasoning = false\n }\n }\n\n // Seals any in-flight messages and tool calls. Called both on the\n // normal terminal path (`interaction.complete`) and on the error path\n // (`error` SSE event + premature EOF) so the StreamProcessor never\n // sees orphan TEXT_MESSAGE_START / TOOL_CALL_START / REASONING_*\n // events on RUN_ERROR.\n const closeOpenState = function* (): Generator<StreamChunk> {\n yield* closeReasoningIfNeeded()\n for (const [toolCallId, state] of toolCalls) {\n if (state.ended) continue\n state.ended = true\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolName: state.name,\n model,\n timestamp,\n input: state.args,\n }\n }\n if (hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = false\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model,\n timestamp,\n }\n }\n }\n\n const emitRunStartedIfNeeded = function* (): Generator<StreamChunk> {\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp,\n parentRunId,\n }\n }\n }\n\n for await (const event of stream) {\n logger.provider(`provider=gemini-text-interactions`, { event })\n switch (event.event_type) {\n case 'interaction.created': {\n interactionId = event.interaction.id\n yield* emitRunStartedIfNeeded()\n break\n }\n\n case 'step.start': {\n yield* emitRunStartedIfNeeded()\n const step = event.step\n const index = event.index\n switch (step.type) {\n case 'function_call': {\n yield* closeReasoningIfNeeded()\n sawFunctionCall = true\n const toolCallId = step.id\n indexToToolCallId.set(index, toolCallId)\n // `step.arguments` is required on FunctionCallStep but may\n // be an empty `{}` placeholder when streaming, where the\n // real args arrive as `arguments_delta` events. Treat both\n // uniformly: stash whatever we got, stringify once.\n const initialArgs = step.arguments\n const state: ToolCallState = {\n name: step.name,\n args: { ...initialArgs },\n index: nextToolIndex++,\n started: true,\n ended: false,\n }\n toolCalls.set(toolCallId, state)\n argStringByToolCallId.set(\n toolCallId,\n Object.keys(initialArgs).length > 0\n ? JSON.stringify(initialArgs)\n : '',\n )\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName: state.name,\n toolName: state.name,\n // Bind the tool call to the same assistant message id the\n // eventual TEXT_MESSAGE_START uses so the message id stays\n // stable when a function_call arrives before any text (#477).\n parentMessageId: messageId,\n model,\n timestamp,\n index: state.index,\n }\n if (Object.keys(initialArgs).length > 0) {\n const argsJson = JSON.stringify(initialArgs)\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId,\n model,\n timestamp,\n delta: argsJson,\n args: argsJson,\n }\n }\n break\n }\n case 'thought': {\n // Open the reasoning block lazily — content lands here via\n // `step.delta { thought_summary }` events. If the server\n // ships a non-empty `summary` array up-front (rare, unary\n // responses), surface it immediately.\n if (thinkingStepId === null || reasoningMessageId === null) {\n thinkingStepId = generateId(adapterName)\n reasoningMessageId = generateId(adapterName)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model,\n timestamp,\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning',\n model,\n timestamp,\n }\n yield {\n type: EventType.STEP_STARTED,\n stepName: thinkingStepId,\n stepId: thinkingStepId,\n model,\n timestamp,\n stepType: 'thinking',\n }\n }\n for (const part of step.summary ?? []) {\n if (part.type !== 'text' || !part.text) continue\n thinkingAccumulated += part.text\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: part.text,\n model,\n timestamp,\n }\n yield {\n type: EventType.STEP_FINISHED,\n stepName: thinkingStepId,\n stepId: thinkingStepId,\n model,\n timestamp,\n delta: part.text,\n content: thinkingAccumulated,\n }\n }\n break\n }\n case 'model_output': {\n yield* closeReasoningIfNeeded()\n // Some servers ship an initial `content` array on\n // `step.start` (notably non-streaming and ahead-of-stream\n // unary completions). Treat any prefilled text content the\n // same way a `text` step.delta would.\n for (const part of step.content ?? []) {\n if (part.type !== 'text' || !part.text) continue\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp,\n role: 'assistant',\n }\n }\n textAccumulated += part.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp,\n delta: part.text,\n content: textAccumulated,\n }\n }\n break\n }\n case 'google_search_call': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.googleSearchCall',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'google_search_result': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.googleSearchResult',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'code_execution_call': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.codeExecutionCall',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'code_execution_result': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.codeExecutionResult',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'url_context_call': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.urlContextCall',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'url_context_result': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.urlContextResult',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'file_search_call': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.fileSearchCall',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'file_search_result': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.fileSearchResult',\n value: step,\n model,\n timestamp,\n }\n break\n }\n // Unhandled step types (user_input on GET timelines,\n // mcp_server_*, google_maps_*, function_result) fall through\n // to the observability default so SDK drift is visible.\n case 'user_input':\n case 'mcp_server_tool_call':\n case 'mcp_server_tool_result':\n case 'google_maps_call':\n case 'google_maps_result':\n case 'function_result':\n default:\n logger.provider(`gemini-text-interactions unhandled step.start`, {\n step,\n })\n break\n }\n break\n }\n\n case 'step.delta': {\n yield* emitRunStartedIfNeeded()\n const delta = event.delta\n const index = event.index\n switch (delta.type) {\n case 'text': {\n yield* closeReasoningIfNeeded()\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp,\n role: 'assistant',\n }\n }\n textAccumulated += delta.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp,\n delta: delta.text,\n content: textAccumulated,\n }\n break\n }\n case 'arguments_delta': {\n // Streamed function-call arguments. Identity (id, name) was\n // delivered on the matching `step.start` and recorded in\n // indexToToolCallId.\n const toolCallId = indexToToolCallId.get(index)\n if (!toolCallId) {\n logger.provider(\n `gemini-text-interactions arguments_delta for unknown step index`,\n { index, delta },\n )\n break\n }\n const state = toolCalls.get(toolCallId)\n if (!state) break\n const fragment = delta.arguments ?? ''\n const buffer =\n (argStringByToolCallId.get(toolCallId) ?? '') + fragment\n argStringByToolCallId.set(toolCallId, buffer)\n // Parse the accumulated buffer leniently: streamed arg fragments\n // are individually incomplete JSON, so use a partial-JSON parser\n // that tolerates truncation rather than logging a parse error per\n // fragment. Only overwrite `state.args` when we actually recovered\n // an object, so a momentarily-unparseable fragment can't reset\n // previously-merged args back to `{}`.\n const parsed = parsePartialToolArguments(buffer)\n if (parsed) state.args = parsed\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId,\n model,\n timestamp,\n delta: fragment,\n args: buffer,\n }\n break\n }\n case 'thought_summary': {\n const thoughtText =\n delta.content && 'text' in delta.content ? delta.content.text : ''\n if (!thoughtText) break\n if (thinkingStepId === null || reasoningMessageId === null) {\n thinkingStepId = generateId(adapterName)\n reasoningMessageId = generateId(adapterName)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model,\n timestamp,\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning',\n model,\n timestamp,\n }\n yield {\n type: EventType.STEP_STARTED,\n stepName: thinkingStepId,\n stepId: thinkingStepId,\n model,\n timestamp,\n stepType: 'thinking',\n }\n }\n thinkingAccumulated += thoughtText\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: thoughtText,\n model,\n timestamp,\n }\n yield {\n type: EventType.STEP_FINISHED,\n stepName: thinkingStepId,\n stepId: thinkingStepId,\n model,\n timestamp,\n delta: thoughtText,\n content: thinkingAccumulated,\n }\n break\n }\n // The remaining StepDelta variants (image/audio/video/document\n // for output modalities a text adapter shouldn't see, tool\n // call/result deltas which are surfaced via step.start in this\n // adapter, thought_signature, annotation deltas, mcp/google\n // maps variants) fall through to the observability default.\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n case 'thought_signature':\n case 'text_annotation_delta':\n case 'code_execution_call':\n case 'code_execution_result':\n case 'url_context_call':\n case 'url_context_result':\n case 'google_search_call':\n case 'google_search_result':\n case 'file_search_call':\n case 'file_search_result':\n case 'mcp_server_tool_call':\n case 'mcp_server_tool_result':\n case 'google_maps_call':\n case 'google_maps_result':\n case 'function_result':\n default:\n logger.provider(\n `gemini-text-interactions unhandled step.delta type`,\n { delta },\n )\n break\n }\n break\n }\n\n case 'step.stop': {\n // Close any open function_call so downstream consumers get the\n // matching TOOL_CALL_END once the arguments are complete. Other\n // step types don't carry adapter-level open state.\n const toolCallId = indexToToolCallId.get(event.index)\n if (toolCallId) {\n const state = toolCalls.get(toolCallId)\n if (state && !state.ended) {\n state.ended = true\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolName: state.name,\n model,\n timestamp,\n input: state.args,\n }\n }\n indexToToolCallId.delete(event.index)\n }\n break\n }\n\n case 'interaction.status_update': {\n break\n }\n\n case 'interaction.completed': {\n if (event.interaction.id) {\n interactionId = event.interaction.id\n }\n\n yield* closeOpenState()\n\n const status = event.interaction.status\n if (statusIsError(status)) {\n const message = `Gemini Interactions ${status}: the interaction ended without a usable response.`\n logger.errors(\n 'gemini-text-interactions.translateInteractionEvents non-success status',\n {\n source: 'gemini-text-interactions.chatStream',\n status,\n interactionId,\n },\n )\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model,\n timestamp,\n message,\n code: status,\n error: { message, code: status },\n }\n return\n }\n\n const usage = event.interaction.usage\n const finishReason = statusToFinishReason(status, sawFunctionCall)\n\n if (interactionId) {\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.interactionId',\n value: { interactionId },\n model,\n timestamp,\n }\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp,\n finishReason,\n usage: usage\n ? {\n promptTokens: usage.total_input_tokens ?? 0,\n completionTokens: usage.total_output_tokens ?? 0,\n totalTokens: usage.total_tokens ?? 0,\n }\n : undefined,\n }\n return\n }\n\n case 'error': {\n // Close any in-flight TEXT_MESSAGE_START / TOOL_CALL_START /\n // REASONING_* so downstream consumers don't see orphan open\n // state after RUN_ERROR.\n yield* closeOpenState()\n const rawMessage = event.error?.message\n const message =\n typeof rawMessage === 'string' && rawMessage.length > 0\n ? rawMessage\n : `Gemini Interactions error (no message): ${JSON.stringify(event.error ?? {})}`\n const rawCode = event.error?.code\n const code =\n typeof rawCode === 'string' || typeof rawCode === 'number'\n ? String(rawCode)\n : undefined\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model,\n timestamp,\n message,\n code,\n error: { message, code },\n }\n return\n }\n\n default:\n logger.provider(`gemini-text-interactions unhandled event_type`, {\n event,\n })\n break\n }\n }\n\n // Stream ended without `interaction.complete` or `error` (both `return`\n // out of the loop). Seal any in-flight TEXT/TOOL/REASONING blocks here\n // so the truncation-fallback RUN_ERROR yielded by `chatStream` doesn't\n // leave orphan `*_START` events open downstream.\n yield* closeOpenState()\n}\n\nfunction extractTextFromInteraction(interaction: Interaction): string {\n // SDK 2.x: the response carries a `steps` array; `output_text` is a\n // convenience the SDK derives from the last model output. Prefer the\n // SDK sugar when it's populated, then fall back to walking\n // model_output steps for adapters / responses that don't get the\n // sugar (e.g. older SDK builds).\n if (typeof interaction.output_text === 'string' && interaction.output_text) {\n return interaction.output_text\n }\n let text = ''\n for (const step of interaction.steps ?? []) {\n if (step.type !== 'model_output' || !step.content) continue\n for (const part of step.content) {\n if (part.type === 'text') {\n text += part.text\n }\n }\n }\n return text\n}\n\n// The live Interactions API rejects tool parameter schemas that contain\n// an empty `required: []` array with the misleading top-level error\n// `\"value at top-level must be a list\"`. Empty `properties: {}` and\n// `parameters: {}` are both fine — only the empty `required` array is\n// poison. The Zod -> JSON Schema converter (and many hand-written\n// schemas) emit `required: []` whenever a tool has zero required\n// parameters, so we strip those instances recursively before sending.\n// Non-empty `required` arrays are passed through unchanged.\nfunction sanitizeToolParameters(schema: unknown): unknown {\n if (!schema || typeof schema !== 'object') return schema\n if (Array.isArray(schema)) return schema.map(sanitizeToolParameters)\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(schema)) {\n if (key === 'required' && Array.isArray(value) && value.length === 0) {\n continue\n }\n out[key] = sanitizeToolParameters(value)\n }\n return out\n}\n\n// Re-export the stream type so consumers can import it alongside the\n// adapter from a single path: `import type { GeminiInteractionsStream }\n// from '@tanstack/ai-gemini/experimental'`.\nexport type { GeminiInteractionsStream }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter.js","names":[],"sources":["../../../../src/experimental/text-interactions/adapter.ts"],"sourcesContent":["import { EventType } from '@tanstack/ai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { parse as parsePartialJSON } from 'partial-json'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../../utils/client'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type {\n GeminiChatModelToolCapabilitiesByName,\n GeminiModelInputModalitiesByName,\n GeminiModels,\n} from '../../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type { GoogleGenAI, Interactions } from '@google/genai'\nimport type {\n ContentPart,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n Tool,\n} from '@tanstack/ai'\n\nimport type {\n GeminiInteractionsCustomEvent,\n GeminiInteractionsCustomEventValue,\n GeminiInteractionsStream,\n} from './events'\nimport type { ExternalTextInteractionsProviderOptions } from './provider-options'\nimport type { GeminiMessageMetadataByModality } from '../../message-types'\nimport type { GeminiClientConfig } from '../../utils/client'\n\ntype Interaction = Interactions.Interaction\ntype InteractionSSEEvent = Interactions.InteractionSSEEvent\n\nexport type GeminiTextInteractionsConfig = GeminiClientConfig\n\nexport type GeminiTextInteractionsProviderOptions =\n ExternalTextInteractionsProviderOptions\n\ntype InteractionsTool = NonNullable<\n Interactions.CreateModelInteractionParamsStreaming['tools']\n>[number]\n\ntype ContentBlock = Interactions.Content\n\n// The Interactions API takes `input` as a list of *Steps* (not a list of\n// content blocks, and not a list of `Turn`s — the SDK's type union is\n// misleading on both counts). The live API enforces the Step envelope —\n// raw content arrays produce `invalid_request` / \"value at top-level\n// must be a list\". The wire discriminator is snake_case\n// (`user_input` / `function_result`); see\n// https://ai.google.dev/api/interactions-api for the full Step union.\ntype UserInputStep = {\n type: 'user_input'\n content: Array<ContentBlock>\n}\ntype FunctionResultStep = {\n type: 'function_result'\n call_id: string\n name?: string\n result: string\n}\ntype InteractionsStep = UserInputStep | FunctionResultStep\ntype InteractionsRequestInput = Array<InteractionsStep>\n\n// Concrete wire shape we send to `client.interactions.create`. The SDK's\n// own param union types `input` as `string | Content[] | Turn[] | ...`\n// which is wrong for the live API (see the InteractionsRequestInput\n// comment above), so we type `input` ourselves and cast just once at the\n// SDK boundary instead of casting every field through.\ntype GeminiInteractionsRequestBody = Omit<\n Interactions.CreateModelInteractionParamsStreaming,\n 'input' | 'stream'\n> & {\n input: InteractionsRequestInput\n stream?: boolean\n}\n\ntype ToolCallState = {\n name: string\n // Accumulated args as a parsed object. Kept here in object form so a\n // garbled delta can't corrupt previously-merged fragments (the prior\n // string-then-reparse pipeline replaced the whole accumulator on any\n // parse failure). Stringified only when emitting AG-UI events.\n args: Record<string, unknown>\n index: number\n started: boolean\n ended: boolean\n}\n\n// ===========================\n// Type Resolution Helpers\n// ===========================\n\n/**\n * Resolve provider options for a specific model. The Interactions API's\n * request shape is the same across all chat-capable Gemini models — the\n * SDK doesn't expose a per-model param union — so this currently falls\n * through to the flat `GeminiTextInteractionsProviderOptions` for every\n * model. The alias exists for parity with `GeminiTextAdapter`, so a\n * per-model map can be slotted in later without changing the adapter\n * signature.\n */\ntype ResolveProviderOptions = GeminiTextInteractionsProviderOptions\n\n/**\n * Resolve input modalities for a specific model. Reuses the chat-model\n * modality map from `model-meta.ts`: passing a `document` content block\n * to a model that doesn't support it is a compile error, matching the\n * sibling `GeminiTextAdapter`.\n */\ntype ResolveInputModalities<TModel extends string> =\n TModel extends keyof GeminiModelInputModalitiesByName\n ? GeminiModelInputModalitiesByName[TModel]\n : readonly ['text', 'image', 'audio', 'video', 'document']\n\n/**\n * Resolve tool capabilities for a specific model. Reuses the chat-model\n * capability map: `google_maps` / `google_search_retrieval` /\n * `mcp_server` are rejected at runtime by `convertToolsToInteractionsFormat`,\n * but per-model gating happens here at compile time.\n */\ntype ResolveToolCapabilities<TModel extends string> =\n TModel extends keyof GeminiChatModelToolCapabilitiesByName\n ? NonNullable<GeminiChatModelToolCapabilitiesByName[TModel]>\n : readonly []\n\n/**\n * Tree-shakeable adapter for Gemini's stateful Interactions API. Routes\n * through `client.interactions.create` and surfaces the server-assigned\n * `interactionId` via an AG-UI `CUSTOM` event with\n * `name: 'gemini.interactionId'` emitted just before `RUN_FINISHED`; pass\n * that id back on the next turn via `modelOptions.previous_interaction_id`\n * to continue the conversation without resending history.\n *\n * The Interactions API does NOT support stateless multi-turn replay —\n * passing more than one message in `messages` without a\n * `previous_interaction_id` throws. For a chat UI that maintains local\n * history (e.g. `useChat`), see the \"Wiring with `useChat`\" section of\n * `docs/adapters/gemini.md` for the canonical client/server pattern.\n *\n * Supports user-defined function tools and the built-in tools\n * `google_search`, `code_execution`, `url_context`, `file_search`, and\n * `computer_use`. Built-in tool *activity* for the four search/exec\n * variants is surfaced via `CUSTOM` events\n * (`gemini.googleSearchCall` / `gemini.googleSearchResult` and the\n * corresponding per-tool variants) carrying the raw Interactions delta;\n * see {@link GeminiInteractionsCustomEvent}. `computer_use` is accepted\n * in the request but the Interactions API does not currently stream\n * per-delta CUSTOM events for it. `google_search_retrieval`,\n * `google_maps`, and `mcp_server` are not supported on this adapter.\n *\n * @experimental Interactions API is in Beta per Google; shapes may change.\n * @see https://ai.google.dev/gemini-api/docs/interactions\n */\nexport class GeminiTextInteractionsAdapter<\n TModel extends GeminiModels,\n TProviderOptions extends Record<string, any> = ResolveProviderOptions,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n TToolCapabilities extends ReadonlyArray<string> =\n ResolveToolCapabilities<TModel>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n GeminiMessageMetadataByModality,\n TToolCapabilities\n> {\n override readonly kind = 'text' as const\n override readonly name = 'gemini-text-interactions' as const\n\n private readonly client: GoogleGenAI\n // Tracks the most recent server-assigned interaction id per threadId\n // so the adapter can chain follow-up calls on the same thread without\n // the caller having to thread the id manually. Two callers rely on\n // this:\n // 1. The agent loop's tool-call iterations (each iteration is a new\n // `chatStream` call with accumulated tool messages).\n // 2. The agentic-structured composition: a `chatStream` run followed\n // by `structuredOutput` on the accumulated messages.\n // Cross-request chaining is the caller's job via\n // `modelOptions.previous_interaction_id`. To keep stale ids from\n // chaining a brand-new turn, `chatStream` evicts at the START when\n // the caller signals fresh-turn intent (no caller-provided id AND a\n // single user message — anything else is a follow-up). Errors evict\n // immediately so a failed turn never chains into the next one.\n private readonly interactionIdByThread = new Map<string, string>()\n\n constructor(config: GeminiTextInteractionsConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n }\n\n async *chatStream(\n options: TextOptions<GeminiTextInteractionsProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const runId = options.runId ?? generateId(this.name)\n const threadId = options.threadId ?? generateId(this.name)\n const timestamp = Date.now()\n const { logger } = options\n\n // Fresh-turn intent: caller didn't thread an id AND only a single\n // user message is queued. Drop any stale captured id so we don't\n // silently chain off a prior turn the caller doesn't know about.\n // Multi-message inputs are follow-ups (agent-loop iteration or\n // structuredOutput composition) and keep the Map entry.\n if (\n !options.modelOptions?.previous_interaction_id &&\n options.messages.length === 1 &&\n options.messages[0]?.role === 'user'\n ) {\n this.interactionIdByThread.delete(threadId)\n }\n\n // Resolve `previous_interaction_id`. Caller-provided wins; otherwise\n // fall back to the id we captured during a prior iteration of this\n // same agent-loop run (matched by threadId).\n const effectivePreviousInteractionId =\n options.modelOptions?.previous_interaction_id ??\n this.interactionIdByThread.get(threadId)\n\n let sawTerminalEvent = false\n // Sentinel for the `.return()` abandonment path. Set to `true` only at\n // the bottom of the `try` block — so a consumer-initiated close (via\n // upstream `break` or abort) leaves it `false`, distinguishing\n // abandonment from normal completion. This is the only signal that\n // catches abandonment AFTER a `RUN_FINISHED(tool_calls)`, where\n // `sawTerminalEvent` is `true` but the in-loop deliberately kept the\n // map entry for an agent-loop iteration that will now never run.\n let completedTryBlock = false\n try {\n const request = buildInteractionsRequest({\n ...options,\n modelOptions: {\n ...options.modelOptions,\n previous_interaction_id: effectivePreviousInteractionId,\n },\n })\n logger.request(\n `activity=chat provider=gemini-text-interactions model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n {\n provider: 'gemini-text-interactions',\n model: this.model,\n request,\n },\n )\n const stream = (await this.client.interactions.create(\n { ...request, stream: true } as GeminiInteractionsRequestBody &\n Parameters<typeof this.client.interactions.create>[0],\n { signal: options.abortController?.signal },\n )) as AsyncIterable<InteractionSSEEvent>\n\n for await (const chunk of translateInteractionEvents(\n stream,\n options.model,\n runId,\n threadId,\n options.parentRunId,\n timestamp,\n this.name,\n logger,\n )) {\n // Capture the server-assigned id so the next agent-loop\n // iteration on this thread can chain off it. The CUSTOM event\n // is also yielded downstream as usual — callers consume it via\n // `onCustomEvent` for cross-request chaining.\n //\n // The yield type can't be narrowed to GeminiInteractionsStream\n // here without fighting zod-passthrough variance (StreamChunk's\n // CustomEvent variant carries `[k: string]: unknown`), so we\n // narrow via the literal `name` and trust the typed\n // construction inside `translateInteractionEvents`.\n if (\n chunk.type === EventType.CUSTOM &&\n chunk.name === 'gemini.interactionId'\n ) {\n const value =\n chunk.value as GeminiInteractionsCustomEventValue<'gemini.interactionId'>\n this.interactionIdByThread.set(threadId, value.interactionId)\n }\n if (chunk.type === EventType.RUN_FINISHED) {\n sawTerminalEvent = true\n // Keep the captured id for follow-ups (next agent-loop\n // iteration OR a structuredOutput call composing this turn).\n // The next *fresh* chatStream call evicts at the top via the\n // fresh-turn guard.\n } else if (chunk.type === EventType.RUN_ERROR) {\n sawTerminalEvent = true\n this.interactionIdByThread.delete(threadId)\n }\n yield chunk\n }\n\n if (!sawTerminalEvent) {\n // SDK stream ended without either `interaction.complete` or\n // `error` — surface the truncation rather than silently leaving\n // downstream consumers waiting on a `RUN_FINISHED` that will\n // never come.\n this.interactionIdByThread.delete(threadId)\n const message =\n 'Gemini Interactions stream ended without a terminal event (no interaction.complete or error)'\n logger.errors('gemini-text-interactions.chatStream truncated', {\n source: 'gemini-text-interactions.chatStream',\n runId,\n threadId,\n })\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: options.model,\n timestamp,\n message,\n error: { message },\n }\n }\n completedTryBlock = true\n } catch (error) {\n this.interactionIdByThread.delete(threadId)\n const message =\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during the interactions stream.'\n logger.errors('gemini-text-interactions.chatStream fatal', {\n error,\n source: 'gemini-text-interactions.chatStream',\n })\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model: options.model,\n timestamp,\n message,\n error: { message },\n }\n } finally {\n // Abandonment cleanup — consumer `.return()` (upstream `break` /\n // abort) bypasses both the truncation guard and the catch handler.\n // `completedTryBlock` is the sentinel that distinguishes natural\n // completion from abandonment; on abandonment we evict so a stale\n // id from a half-finished turn can't chain into a follow-up. The\n // catch handler also lands here with the flag false; its explicit\n // delete is harmless to repeat.\n if (!completedTryBlock) {\n this.interactionIdByThread.delete(threadId)\n }\n }\n }\n\n async structuredOutput(\n options: StructuredOutputOptions<GeminiTextInteractionsProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const { logger } = chatOptions\n const threadId = chatOptions.threadId\n\n // Mirror the chatStream fallback: the agentic-structured flow runs\n // the chat loop first and then calls structuredOutput with the\n // accumulated `messages`. If any tool ran during the loop, the\n // messages include assistant/tool turns; without a chained\n // previous_interaction_id those would throw \"cannot send prior\n // conversation history on a fresh interaction\".\n const effectivePreviousInteractionId =\n chatOptions.modelOptions?.previous_interaction_id ??\n (threadId ? this.interactionIdByThread.get(threadId) : undefined)\n\n const baseRequest = buildInteractionsRequest({\n ...chatOptions,\n modelOptions: {\n ...chatOptions.modelOptions,\n previous_interaction_id: effectivePreviousInteractionId,\n },\n })\n\n // SDK 2.x: `response_mime_type` has been removed and `response_format`\n // is now polymorphic — each entry has a `type` discriminator and the\n // mime type lives inside the entry. See:\n // https://ai.google.dev/gemini-api/docs/interactions-breaking-changes-may-2026\n const request: GeminiInteractionsRequestBody = {\n ...baseRequest,\n response_format: {\n type: 'text',\n mime_type: 'application/json',\n schema: outputSchema,\n },\n }\n\n try {\n logger.request(\n `activity=chat provider=gemini-text-interactions model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,\n {\n provider: 'gemini-text-interactions',\n model: this.model,\n request,\n },\n )\n const result = (await this.client.interactions.create(\n request as Parameters<typeof this.client.interactions.create>[0],\n { signal: chatOptions.abortController?.signal },\n )) as Interaction\n\n const rawText = extractTextFromInteraction(result)\n\n if (!rawText) {\n throw new Error(\n `Gemini Interactions returned no text output for structured-output request (status: ${result.status}). The model may have produced only tool calls or non-text content.`,\n )\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n return { data: parsed, rawText }\n } catch (error) {\n logger.errors('gemini-text-interactions.structuredOutput fatal', {\n error,\n source: 'gemini-text-interactions.structuredOutput',\n })\n // Preserve the original error as `cause` so the stack trace and any\n // SDK-attached status/code/headers survive for Sentry dedup.\n throw new Error(\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during structured output generation.',\n { cause: error },\n )\n }\n }\n}\n\n/** @experimental Interactions API is in Beta. */\nexport function createGeminiTextInteractions<TModel extends GeminiModels>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiTextInteractionsConfig, 'apiKey'>,\n): GeminiTextInteractionsAdapter<\n TModel,\n ResolveProviderOptions,\n ResolveInputModalities<TModel>,\n ResolveToolCapabilities<TModel>\n> {\n return new GeminiTextInteractionsAdapter({ apiKey, ...config }, model)\n}\n\n/** @experimental Interactions API is in Beta. */\nexport function geminiTextInteractions<TModel extends GeminiModels>(\n model: TModel,\n config?: Omit<GeminiTextInteractionsConfig, 'apiKey'>,\n): GeminiTextInteractionsAdapter<\n TModel,\n ResolveProviderOptions,\n ResolveInputModalities<TModel>,\n ResolveToolCapabilities<TModel>\n> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiTextInteractions(model, apiKey, config)\n}\n\nfunction buildInteractionsRequest(\n options: TextOptions<GeminiTextInteractionsProviderOptions>,\n): GeminiInteractionsRequestBody {\n const modelOpts = options.modelOptions\n\n const systemInstruction =\n modelOpts?.system_instruction ?? options.systemPrompts?.join('\\n')\n\n const generationConfig: Interactions.GenerationConfig = {\n ...modelOpts?.generation_config,\n }\n\n const hasGenerationConfig = Object.keys(generationConfig).length > 0\n\n const input = convertMessagesToInteractionsInput(\n options.messages,\n modelOpts?.previous_interaction_id !== undefined,\n )\n\n return {\n model: options.model,\n input,\n previous_interaction_id: modelOpts?.previous_interaction_id,\n system_instruction: systemInstruction,\n tools: convertToolsToInteractionsFormat(options.tools),\n generation_config: hasGenerationConfig ? generationConfig : undefined,\n store: modelOpts?.store,\n background: modelOpts?.background,\n response_modalities: modelOpts?.response_modalities,\n response_format: modelOpts?.response_format,\n }\n}\n\n// Google's Interactions API takes `input` as `Array<Step>`. Each Step\n// is `{type: 'user_input' | 'function_result' | ..., ...}` — content\n// blocks (text/image/etc.) live nested inside a Step's `content` array,\n// they are NOT valid at the top level. Sending raw `Array<Content>`\n// produces `invalid_request` / \"value at top-level must be a list\",\n// because the API is looking for a Step list at the top level and\n// gets content objects instead. The SDK's type union\n// (`string | Array<Content> | Array<Turn> | ...`) is misleading; see\n// https://ai.google.dev/api/interactions-api for the real Step union.\n//\n// When `hasPreviousInteraction` is true the server holds the transcript\n// up through the last assistant turn, so we only send the steps that\n// come after it (a new `user_input`, one or more `function_result`s\n// continuing a tool call, etc.). Otherwise the conversation is fresh\n// and only the latest user turn is supported — multi-turn replay\n// without `previous_interaction_id` is not part of the API contract.\nfunction convertMessagesToInteractionsInput(\n messages: Array<ModelMessage>,\n hasPreviousInteraction: boolean,\n): InteractionsRequestInput {\n const toolCallIdToName = new Map<string, string>()\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.toolCalls) {\n for (const tc of msg.toolCalls) {\n toolCallIdToName.set(tc.id, tc.function.name)\n }\n }\n }\n\n const source = hasPreviousInteraction\n ? messagesAfterLastAssistant(messages)\n : messages\n\n if (hasPreviousInteraction && source.length === 0) {\n throw new Error(\n 'Gemini Interactions adapter: modelOptions.previous_interaction_id was provided but no new messages were found after the last assistant turn. Append at least one user or tool message before chaining.',\n )\n }\n\n if (!hasPreviousInteraction) {\n const [only, ...rest] = source\n if (!only) {\n throw new Error('Gemini Interactions adapter: no messages to send.')\n }\n if (rest.length > 0) {\n throw new Error(\n 'Gemini Interactions adapter: cannot send prior conversation history on a fresh interaction. Either set modelOptions.previous_interaction_id to chain prior turns server-side, or trim the message list to a single new user turn. See docs/adapters/gemini.md (\"Wiring with useChat\") for the canonical client/server pattern.',\n )\n }\n if (only.role !== 'user') {\n throw new Error(\n `Gemini Interactions adapter: the first message of a fresh interaction must be a user turn (got role=\"${only.role}\"). Set modelOptions.previous_interaction_id to continue an existing interaction.`,\n )\n }\n const content = messageToContentBlocks(only)\n if (content.length === 0) {\n throw new Error(\n 'Gemini Interactions adapter: the user message produced no content blocks to send.',\n )\n }\n return [{ type: 'user_input', content }]\n }\n\n // Chained path: each post-assistant message becomes one Step. A user\n // reply maps to `user_input`; a tool reply maps to `function_result`.\n // Assistant turns shouldn't appear here (sliced off above) — if one\n // somehow does we skip it rather than letting it shape the wire.\n const steps: Array<InteractionsStep> = []\n for (const msg of source) {\n if (msg.role === 'tool' && msg.toolCallId) {\n const result = serializeToolResultContent(msg.content)\n steps.push({\n type: 'function_result',\n call_id: msg.toolCallId,\n name: toolCallIdToName.get(msg.toolCallId),\n result,\n })\n } else if (msg.role === 'user') {\n const content = messageToContentBlocks(msg)\n if (content.length > 0) {\n steps.push({ type: 'user_input', content })\n }\n }\n }\n if (steps.length === 0) {\n throw new Error(\n 'Gemini Interactions adapter: messages after the last assistant turn produced no steps to send.',\n )\n }\n return steps\n}\n\n// The Interactions API's `function_result.result` field is a string. We\n// fail loudly on non-string tool content rather than silently coercing\n// to `''` — silent coercion meant the model lost the entire tool\n// output for callers that returned content as an array (e.g. image +\n// text) or `null`. If you need to send structured tool output, encode\n// it yourself before passing.\nfunction serializeToolResultContent(\n content: ModelMessage['content'] | undefined,\n): string {\n if (typeof content === 'string') return content\n if (content === null || content === undefined) {\n throw new Error(\n 'Gemini Interactions adapter: tool message has no content. The Interactions API requires a string `result` on function_result steps — return a string from your tool implementation (encode JSON/multimodal output yourself).',\n )\n }\n throw new Error(\n 'Gemini Interactions adapter: tool message content must be a string (got an array of content parts). The Interactions API requires a string `result` on function_result steps — stringify multimodal tool output before returning it from your tool.',\n )\n}\n\n// Extracts the content blocks (text / image / audio / video / document)\n// from a single message. Tool calls and tool results live one level up\n// as Steps, not as content, so they are NOT emitted here.\nfunction messageToContentBlocks(msg: ModelMessage): Array<ContentBlock> {\n const blocks: Array<ContentBlock> = []\n\n if (Array.isArray(msg.content)) {\n for (const part of msg.content) {\n blocks.push(contentPartToBlock(part))\n }\n } else if (\n typeof msg.content === 'string' &&\n msg.content &&\n msg.role !== 'tool'\n ) {\n blocks.push({ type: 'text', text: msg.content })\n }\n\n return blocks\n}\n\nfunction messagesAfterLastAssistant(\n messages: Array<ModelMessage>,\n): Array<ModelMessage> {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i]?.role === 'assistant') {\n return messages.slice(i + 1)\n }\n }\n return messages\n}\n\n// Leniently parse the *accumulated* streamed tool-call argument buffer.\n// Streamed `arguments_delta` fragments are individually incomplete JSON, so\n// a strict `JSON.parse` would throw (and log noise) on every fragment until\n// the final one. `partial-json` recovers a best-effort object from a\n// truncated buffer instead. Returns `undefined` when nothing usable could be\n// parsed, so callers can keep the last good value rather than clobber\n// previously-merged args with `{}`.\nfunction parsePartialToolArguments(\n raw: string,\n): Record<string, unknown> | undefined {\n if (!raw) return undefined\n try {\n const parsed = parsePartialJSON(raw)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : undefined\n } catch {\n return undefined\n }\n}\n\n// `satisfies` pins these arrays to the SDK's narrow mime-type unions: if\n// Google removes a format the build breaks, and if they add one ours keeps\n// working (we just won't accept the new one until added here).\nconst IMAGE_MIME_TYPES = [\n 'image/png',\n 'image/jpeg',\n 'image/webp',\n 'image/heic',\n 'image/heif',\n] as const satisfies ReadonlyArray<\n NonNullable<Interactions.ImageContent['mime_type']>\n>\n\nconst AUDIO_MIME_TYPES = [\n 'audio/wav',\n 'audio/mp3',\n 'audio/aiff',\n 'audio/aac',\n 'audio/ogg',\n 'audio/flac',\n] as const satisfies ReadonlyArray<\n NonNullable<Interactions.AudioContent['mime_type']>\n>\n\nconst VIDEO_MIME_TYPES = [\n 'video/mp4',\n 'video/mpeg',\n 'video/mpg',\n 'video/mov',\n 'video/avi',\n 'video/x-flv',\n 'video/webm',\n 'video/wmv',\n 'video/3gpp',\n] as const satisfies ReadonlyArray<\n NonNullable<Interactions.VideoContent['mime_type']>\n>\n\nconst DOCUMENT_MIME_TYPES = [\n 'application/pdf',\n] as const satisfies ReadonlyArray<\n NonNullable<Interactions.DocumentContent['mime_type']>\n>\n\nfunction validateMime<T extends string>(\n allowed: ReadonlyArray<T>,\n value: string | undefined,\n kind: string,\n): T | undefined {\n if (value === undefined) return undefined\n if ((allowed as ReadonlyArray<string>).includes(value)) {\n return value as T\n }\n throw new Error(\n `Unsupported ${kind} mime type \"${value}\" for the Gemini Interactions API. Allowed: ${allowed.join(', ')}.`,\n )\n}\n\nfunction contentPartToBlock(part: ContentPart): ContentBlock {\n if (part.type === 'text') {\n return { type: 'text', text: part.content }\n }\n const isData = part.source.type === 'data'\n switch (part.type) {\n case 'image': {\n const mime_type = validateMime(\n IMAGE_MIME_TYPES,\n part.source.mimeType,\n 'image',\n )\n return isData\n ? { type: 'image', data: part.source.value, mime_type }\n : { type: 'image', uri: part.source.value, mime_type }\n }\n case 'audio': {\n const mime_type = validateMime(\n AUDIO_MIME_TYPES,\n part.source.mimeType,\n 'audio',\n )\n return isData\n ? { type: 'audio', data: part.source.value, mime_type }\n : { type: 'audio', uri: part.source.value, mime_type }\n }\n case 'video': {\n const mime_type = validateMime(\n VIDEO_MIME_TYPES,\n part.source.mimeType,\n 'video',\n )\n return isData\n ? { type: 'video', data: part.source.value, mime_type }\n : { type: 'video', uri: part.source.value, mime_type }\n }\n case 'document': {\n const mime_type = validateMime(\n DOCUMENT_MIME_TYPES,\n part.source.mimeType,\n 'document',\n )\n return isData\n ? { type: 'document', data: part.source.value, mime_type }\n : { type: 'document', uri: part.source.value, mime_type }\n }\n }\n}\n\n// Built-in Gemini tools use snake_case field names in the Interactions API\n// that differ from the camelCase fields used on `client.models.generateContent`\n// (e.g. `fileSearchStoreNames` vs `file_search_store_names`). Translate\n// explicitly so callers keep using the same tool factories across adapters.\nfunction convertToolsToInteractionsFormat<TTool extends Tool>(\n tools: Array<TTool> | undefined,\n): Array<InteractionsTool> | undefined {\n if (!tools || tools.length === 0) return undefined\n\n const result: Array<InteractionsTool> = []\n\n for (const tool of tools) {\n switch (tool.name) {\n case 'google_search': {\n const metadata = (tool.metadata ?? {}) as {\n search_types?: Array<'web_search' | 'image_search'>\n }\n result.push({\n type: 'google_search',\n ...(metadata.search_types\n ? { search_types: metadata.search_types }\n : {}),\n })\n break\n }\n case 'code_execution': {\n result.push({ type: 'code_execution' })\n break\n }\n case 'url_context': {\n result.push({ type: 'url_context' })\n break\n }\n case 'file_search': {\n const metadata = (tool.metadata ?? {}) as {\n fileSearchStoreNames?: Array<string>\n topK?: number\n metadataFilter?: string\n }\n result.push({\n type: 'file_search',\n ...(metadata.fileSearchStoreNames\n ? { file_search_store_names: metadata.fileSearchStoreNames }\n : {}),\n ...(metadata.topK !== undefined ? { top_k: metadata.topK } : {}),\n ...(metadata.metadataFilter !== undefined\n ? { metadata_filter: metadata.metadataFilter }\n : {}),\n })\n break\n }\n case 'computer_use': {\n const metadata = (tool.metadata ?? {}) as {\n environment?: string\n excludedPredefinedFunctions?: Array<string>\n }\n if (metadata.environment && metadata.environment !== 'browser') {\n throw new Error(\n `computer_use environment \"${metadata.environment}\" is not supported on the Gemini Interactions API. Only \"browser\" is accepted.`,\n )\n }\n result.push({\n type: 'computer_use',\n ...(metadata.environment\n ? { environment: metadata.environment as 'browser' }\n : {}),\n ...(metadata.excludedPredefinedFunctions\n ? {\n excludedPredefinedFunctions:\n metadata.excludedPredefinedFunctions,\n }\n : {}),\n })\n break\n }\n case 'google_search_retrieval':\n throw new Error(\n '`google_search_retrieval` is not supported on the Gemini Interactions API. Use `googleSearchTool()` (`google_search`) with `geminiTextInteractions()`, or call `geminiText()` for the legacy retrieval tool.',\n )\n case 'google_maps':\n throw new Error(\n '`google_maps` is not yet supported on the Gemini Interactions API. Use `geminiText()` for Google Maps grounding.',\n )\n case 'mcp_server':\n throw new Error(\n '`mcp_server` is not yet supported on the `geminiTextInteractions()` adapter.',\n )\n default: {\n if (!tool.description) {\n throw new Error(\n `Tool ${tool.name} requires a description for the Gemini Interactions adapter`,\n )\n }\n result.push({\n type: 'function',\n name: tool.name,\n description: tool.description,\n parameters: sanitizeToolParameters(\n tool.inputSchema ?? { type: 'object', properties: {} },\n ),\n })\n }\n }\n }\n\n return result\n}\n\n// Map of API-level status values onto the AG-UI `finishReason` field.\n// `requires_action` is the Interactions API's signal that the model\n// produced one or more function calls and is waiting for results — we\n// always map that to 'tool_calls' regardless of whether deltas were\n// observed (a function_call may have arrived in a single delta with no\n// other content). `incomplete` is the truncation signal (max_tokens\n// exceeded etc.) — map to 'length'. `completed` is normal stop.\nfunction statusToFinishReason(\n status: Interaction['status'] | undefined,\n sawFunctionCall: boolean,\n): 'stop' | 'length' | 'tool_calls' | null {\n if (status === 'requires_action') return 'tool_calls'\n if (status === 'incomplete') return 'length'\n if (sawFunctionCall) return 'tool_calls'\n return 'stop'\n}\n\n// Statuses that mean the interaction did not produce a usable result.\n// `failed`/`cancelled` map to RUN_ERROR. `incomplete` is the model\n// hitting a stop condition (max_tokens etc.) and is reported via\n// `finishReason: 'length'` on RUN_FINISHED so callers can decide how to\n// react without it looking like a hard error.\nfunction statusIsError(\n status: Interaction['status'] | undefined,\n): status is 'failed' | 'cancelled' {\n return status === 'failed' || status === 'cancelled'\n}\n\nasync function* translateInteractionEvents(\n stream: AsyncIterable<InteractionSSEEvent>,\n model: string,\n runId: string,\n threadId: string,\n parentRunId: string | undefined,\n timestamp: number,\n adapterName: string,\n logger: InternalLogger,\n): AsyncIterable<StreamChunk> {\n const messageId = generateId(adapterName)\n let hasEmittedRunStarted = false\n let hasEmittedTextMessageStart = false\n let textAccumulated = ''\n let interactionId: string | undefined\n let sawFunctionCall = false\n const toolCalls = new Map<string, ToolCallState>()\n let nextToolIndex = 0\n let thinkingStepId: string | null = null\n let thinkingAccumulated = ''\n let reasoningMessageId: string | null = null\n let hasClosedReasoning = false\n // SDK 2.x routes events by step `index`, not by content id. We need to\n // map the index of an in-flight `function_call` step back to the tool\n // call id so subsequent `step.delta` (arguments_delta) and `step.stop`\n // events can update / close the right TOOL_CALL_*.\n const indexToToolCallId = new Map<number, string>()\n // Function-call arguments now stream as partial JSON fragments\n // (`StepDelta.ArgumentsDelta.arguments`) rather than as pre-parsed\n // object deltas. Buffer the raw strings per tool call so we can\n // attempt one JSON parse per delta and recover gracefully if the\n // fragment isn't yet syntactically complete.\n const argStringByToolCallId = new Map<string, string>()\n\n const closeReasoningIfNeeded = function* (): Generator<StreamChunk> {\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model,\n timestamp,\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model,\n timestamp,\n }\n // Reset so that a later `thought_summary` delta (the API\n // interleaves text → thought → text on some models) opens a\n // fresh reasoning block instead of re-using an already-ended\n // messageId, which would violate AG-UI ordering.\n thinkingStepId = null\n reasoningMessageId = null\n hasClosedReasoning = false\n }\n }\n\n // Seals any in-flight messages and tool calls. Called both on the\n // normal terminal path (`interaction.complete`) and on the error path\n // (`error` SSE event + premature EOF) so the StreamProcessor never\n // sees orphan TEXT_MESSAGE_START / TOOL_CALL_START / REASONING_*\n // events on RUN_ERROR.\n const closeOpenState = function* (): Generator<StreamChunk> {\n yield* closeReasoningIfNeeded()\n for (const [toolCallId, state] of toolCalls) {\n if (state.ended) continue\n state.ended = true\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolName: state.name,\n model,\n timestamp,\n input: state.args,\n }\n }\n if (hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = false\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model,\n timestamp,\n }\n }\n }\n\n const emitRunStartedIfNeeded = function* (): Generator<StreamChunk> {\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp,\n parentRunId,\n }\n }\n }\n\n for await (const event of stream) {\n logger.provider(`provider=gemini-text-interactions`, { event })\n switch (event.event_type) {\n case 'interaction.created': {\n interactionId = event.interaction.id\n yield* emitRunStartedIfNeeded()\n break\n }\n\n case 'step.start': {\n yield* emitRunStartedIfNeeded()\n const step = event.step\n const index = event.index\n switch (step.type) {\n case 'function_call': {\n yield* closeReasoningIfNeeded()\n sawFunctionCall = true\n const toolCallId = step.id\n indexToToolCallId.set(index, toolCallId)\n // `step.arguments` is required on FunctionCallStep but may\n // be an empty `{}` placeholder when streaming, where the\n // real args arrive as `arguments_delta` events. Treat both\n // uniformly: stash whatever we got, stringify once.\n const initialArgs = step.arguments\n const state: ToolCallState = {\n name: step.name,\n args: { ...initialArgs },\n index: nextToolIndex++,\n started: true,\n ended: false,\n }\n toolCalls.set(toolCallId, state)\n argStringByToolCallId.set(\n toolCallId,\n Object.keys(initialArgs).length > 0\n ? JSON.stringify(initialArgs)\n : '',\n )\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName: state.name,\n toolName: state.name,\n // Bind the tool call to the same assistant message id the\n // eventual TEXT_MESSAGE_START uses so the message id stays\n // stable when a function_call arrives before any text (#477).\n parentMessageId: messageId,\n model,\n timestamp,\n index: state.index,\n }\n if (Object.keys(initialArgs).length > 0) {\n const argsJson = JSON.stringify(initialArgs)\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId,\n model,\n timestamp,\n delta: argsJson,\n args: argsJson,\n }\n }\n break\n }\n case 'thought': {\n // Open the reasoning block lazily — content lands here via\n // `step.delta { thought_summary }` events. If the server\n // ships a non-empty `summary` array up-front (rare, unary\n // responses), surface it immediately.\n if (thinkingStepId === null || reasoningMessageId === null) {\n thinkingStepId = generateId(adapterName)\n reasoningMessageId = generateId(adapterName)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model,\n timestamp,\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning',\n model,\n timestamp,\n }\n yield {\n type: EventType.STEP_STARTED,\n stepName: thinkingStepId,\n stepId: thinkingStepId,\n model,\n timestamp,\n stepType: 'thinking',\n }\n }\n for (const part of step.summary ?? []) {\n if (part.type !== 'text' || !part.text) continue\n thinkingAccumulated += part.text\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: part.text,\n model,\n timestamp,\n }\n yield {\n type: EventType.STEP_FINISHED,\n stepName: thinkingStepId,\n stepId: thinkingStepId,\n model,\n timestamp,\n delta: part.text,\n content: thinkingAccumulated,\n }\n }\n break\n }\n case 'model_output': {\n yield* closeReasoningIfNeeded()\n // Some servers ship an initial `content` array on\n // `step.start` (notably non-streaming and ahead-of-stream\n // unary completions). Treat any prefilled text content the\n // same way a `text` step.delta would.\n for (const part of step.content ?? []) {\n if (part.type !== 'text' || !part.text) continue\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp,\n role: 'assistant',\n }\n }\n textAccumulated += part.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp,\n delta: part.text,\n content: textAccumulated,\n }\n }\n break\n }\n case 'google_search_call': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.googleSearchCall',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'google_search_result': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.googleSearchResult',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'code_execution_call': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.codeExecutionCall',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'code_execution_result': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.codeExecutionResult',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'url_context_call': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.urlContextCall',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'url_context_result': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.urlContextResult',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'file_search_call': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.fileSearchCall',\n value: step,\n model,\n timestamp,\n }\n break\n }\n case 'file_search_result': {\n yield* closeReasoningIfNeeded()\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.fileSearchResult',\n value: step,\n model,\n timestamp,\n }\n break\n }\n // Unhandled step types (user_input on GET timelines,\n // mcp_server_*, google_maps_*, function_result) fall through\n // to the observability default so SDK drift is visible.\n case 'user_input':\n case 'mcp_server_tool_call':\n case 'mcp_server_tool_result':\n case 'google_maps_call':\n case 'google_maps_result':\n case 'function_result':\n default:\n logger.provider(`gemini-text-interactions unhandled step.start`, {\n step,\n })\n break\n }\n break\n }\n\n case 'step.delta': {\n yield* emitRunStartedIfNeeded()\n const delta = event.delta\n const index = event.index\n switch (delta.type) {\n case 'text': {\n yield* closeReasoningIfNeeded()\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp,\n role: 'assistant',\n }\n }\n textAccumulated += delta.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp,\n delta: delta.text,\n content: textAccumulated,\n }\n break\n }\n case 'arguments_delta': {\n // Streamed function-call arguments. Identity (id, name) was\n // delivered on the matching `step.start` and recorded in\n // indexToToolCallId.\n const toolCallId = indexToToolCallId.get(index)\n if (!toolCallId) {\n logger.provider(\n `gemini-text-interactions arguments_delta for unknown step index`,\n { index, delta },\n )\n break\n }\n const state = toolCalls.get(toolCallId)\n if (!state) break\n const fragment = delta.arguments ?? ''\n const buffer =\n (argStringByToolCallId.get(toolCallId) ?? '') + fragment\n argStringByToolCallId.set(toolCallId, buffer)\n // Parse the accumulated buffer leniently: streamed arg fragments\n // are individually incomplete JSON, so use a partial-JSON parser\n // that tolerates truncation rather than logging a parse error per\n // fragment. Only overwrite `state.args` when we actually recovered\n // an object, so a momentarily-unparseable fragment can't reset\n // previously-merged args back to `{}`.\n const parsed = parsePartialToolArguments(buffer)\n if (parsed) state.args = parsed\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId,\n model,\n timestamp,\n delta: fragment,\n args: buffer,\n }\n break\n }\n case 'thought_summary': {\n const thoughtText =\n delta.content && 'text' in delta.content ? delta.content.text : ''\n if (!thoughtText) break\n if (thinkingStepId === null || reasoningMessageId === null) {\n thinkingStepId = generateId(adapterName)\n reasoningMessageId = generateId(adapterName)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model,\n timestamp,\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning',\n model,\n timestamp,\n }\n yield {\n type: EventType.STEP_STARTED,\n stepName: thinkingStepId,\n stepId: thinkingStepId,\n model,\n timestamp,\n stepType: 'thinking',\n }\n }\n thinkingAccumulated += thoughtText\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: thoughtText,\n model,\n timestamp,\n }\n yield {\n type: EventType.STEP_FINISHED,\n stepName: thinkingStepId,\n stepId: thinkingStepId,\n model,\n timestamp,\n delta: thoughtText,\n content: thinkingAccumulated,\n }\n break\n }\n // The remaining StepDelta variants (image/audio/video/document\n // for output modalities a text adapter shouldn't see, tool\n // call/result deltas which are surfaced via step.start in this\n // adapter, thought_signature, annotation deltas, mcp/google\n // maps variants) fall through to the observability default.\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n case 'thought_signature':\n case 'text_annotation_delta':\n case 'code_execution_call':\n case 'code_execution_result':\n case 'url_context_call':\n case 'url_context_result':\n case 'google_search_call':\n case 'google_search_result':\n case 'file_search_call':\n case 'file_search_result':\n case 'mcp_server_tool_call':\n case 'mcp_server_tool_result':\n case 'google_maps_call':\n case 'google_maps_result':\n case 'function_result':\n default:\n logger.provider(\n `gemini-text-interactions unhandled step.delta type`,\n { delta },\n )\n break\n }\n break\n }\n\n case 'step.stop': {\n // Close any open function_call so downstream consumers get the\n // matching TOOL_CALL_END once the arguments are complete. Other\n // step types don't carry adapter-level open state.\n const toolCallId = indexToToolCallId.get(event.index)\n if (toolCallId) {\n const state = toolCalls.get(toolCallId)\n if (state && !state.ended) {\n state.ended = true\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolName: state.name,\n model,\n timestamp,\n input: state.args,\n }\n }\n indexToToolCallId.delete(event.index)\n }\n break\n }\n\n case 'interaction.status_update': {\n break\n }\n\n case 'interaction.completed': {\n if (event.interaction.id) {\n interactionId = event.interaction.id\n }\n\n yield* closeOpenState()\n\n const status = event.interaction.status\n if (statusIsError(status)) {\n const message = `Gemini Interactions ${status}: the interaction ended without a usable response.`\n logger.errors(\n 'gemini-text-interactions.translateInteractionEvents non-success status',\n {\n source: 'gemini-text-interactions.chatStream',\n status,\n interactionId,\n },\n )\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model,\n timestamp,\n message,\n code: status,\n error: { message, code: status },\n }\n return\n }\n\n const usage = event.interaction.usage\n const finishReason = statusToFinishReason(status, sawFunctionCall)\n\n if (interactionId) {\n yield {\n type: EventType.CUSTOM,\n name: 'gemini.interactionId',\n value: { interactionId },\n model,\n timestamp,\n }\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp,\n finishReason,\n usage: usage\n ? {\n promptTokens: usage.total_input_tokens ?? 0,\n completionTokens: usage.total_output_tokens ?? 0,\n totalTokens: usage.total_tokens ?? 0,\n }\n : undefined,\n }\n return\n }\n\n case 'error': {\n // Close any in-flight TEXT_MESSAGE_START / TOOL_CALL_START /\n // REASONING_* so downstream consumers don't see orphan open\n // state after RUN_ERROR.\n yield* closeOpenState()\n const rawMessage = event.error?.message\n const message =\n typeof rawMessage === 'string' && rawMessage.length > 0\n ? rawMessage\n : `Gemini Interactions error (no message): ${JSON.stringify(event.error ?? {})}`\n const rawCode = event.error?.code\n const code =\n typeof rawCode === 'string' || typeof rawCode === 'number'\n ? String(rawCode)\n : undefined\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model,\n timestamp,\n message,\n code,\n error: { message, code },\n }\n return\n }\n\n default:\n logger.provider(`gemini-text-interactions unhandled event_type`, {\n event,\n })\n break\n }\n }\n\n // Stream ended without `interaction.complete` or `error` (both `return`\n // out of the loop). Seal any in-flight TEXT/TOOL/REASONING blocks here\n // so the truncation-fallback RUN_ERROR yielded by `chatStream` doesn't\n // leave orphan `*_START` events open downstream.\n yield* closeOpenState()\n}\n\nfunction extractTextFromInteraction(interaction: Interaction): string {\n // SDK 2.x: the response carries a `steps` array; `output_text` is a\n // convenience the SDK derives from the last model output. Prefer the\n // SDK sugar when it's populated, then fall back to walking\n // model_output steps for adapters / responses that don't get the\n // sugar (e.g. older SDK builds).\n if (typeof interaction.output_text === 'string' && interaction.output_text) {\n return interaction.output_text\n }\n let text = ''\n for (const step of interaction.steps ?? []) {\n if (step.type !== 'model_output' || !step.content) continue\n for (const part of step.content) {\n if (part.type === 'text') {\n text += part.text\n }\n }\n }\n return text\n}\n\n// The live Interactions API rejects tool parameter schemas that contain\n// an empty `required: []` array with the misleading top-level error\n// `\"value at top-level must be a list\"`. Empty `properties: {}` and\n// `parameters: {}` are both fine — only the empty `required` array is\n// poison. The Zod -> JSON Schema converter (and many hand-written\n// schemas) emit `required: []` whenever a tool has zero required\n// parameters, so we strip those instances recursively before sending.\n// Non-empty `required` arrays are passed through unchanged.\nfunction sanitizeToolParameters(schema: unknown): unknown {\n if (!schema || typeof schema !== 'object') return schema\n if (Array.isArray(schema)) return schema.map(sanitizeToolParameters)\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(schema)) {\n if (key === 'required' && Array.isArray(value) && value.length === 0) {\n continue\n }\n out[key] = sanitizeToolParameters(value)\n }\n return out\n}\n\n// Re-export the stream type so consumers can import it alongside the\n// adapter from a single path: `import type { GeminiInteractionsStream }\n// from '@tanstack/ai-gemini/experimental'`.\nexport type { GeminiInteractionsStream }\n"],"mappings":"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"}
|