@tanstack/ai-gemini 0.18.3 → 0.19.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 +2 -2
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/experimental/text-interactions/adapter.js +1 -1
- package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
- package/dist/esm/image/image-provider-options.d.ts +1 -1
- package/dist/esm/image/image-provider-options.js +0 -1
- package/dist/esm/image/image-provider-options.js.map +1 -1
- package/dist/esm/model-meta.d.ts +2 -2
- package/dist/esm/model-meta.js +7 -15
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/video/video-provider-options.d.ts +1 -3
- package/dist/esm/video/video-provider-options.js +1 -3
- package/dist/esm/video/video-provider-options.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/image.ts +2 -2
- package/src/experimental/text-interactions/adapter.ts +1 -1
- package/src/image/image-provider-options.ts +5 -5
- package/src/model-meta.ts +33 -73
- package/src/video/video-provider-options.ts +2 -6
|
@@ -58,14 +58,14 @@ export declare class GeminiImageAdapter<TModel extends GeminiImageModel> extends
|
|
|
58
58
|
* Creates a Gemini image adapter with explicit API key.
|
|
59
59
|
* Type resolution happens here at the call site.
|
|
60
60
|
*
|
|
61
|
-
* @param model - The model name (e.g., 'imagen-
|
|
61
|
+
* @param model - The model name (e.g., 'imagen-4.0-generate-001')
|
|
62
62
|
* @param apiKey - Your Google API key
|
|
63
63
|
* @param config - Optional additional configuration
|
|
64
64
|
* @returns Configured Gemini image adapter instance with resolved types
|
|
65
65
|
*
|
|
66
66
|
* @example
|
|
67
67
|
* ```typescript
|
|
68
|
-
* const adapter = createGeminiImage('imagen-
|
|
68
|
+
* const adapter = createGeminiImage('imagen-4.0-generate-001', "your-api-key");
|
|
69
69
|
*
|
|
70
70
|
* const result = await generateImage({
|
|
71
71
|
* adapter,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport { arrayBufferToBase64 } from '@tanstack/ai-utils'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport { buildGeminiUsage } from '../usage'\nimport {\n parseNativeImageSize,\n sizeToAspectRatio,\n validateImageSize,\n validateNumberOfImages,\n validatePrompt,\n} from '../image/image-provider-options'\nimport type { GEMINI_IMAGE_MODELS } from '../model-meta'\nimport type {\n GeminiImageModelInputModalitiesByName,\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName,\n GeminiImageProviderOptions,\n} from '../image/image-provider-options'\nimport type {\n GeneratedImage,\n ImageGenerationOptions,\n ImageGenerationResult,\n ImagePart,\n MediaInputMetadata,\n ResolvedMediaPrompt,\n} from '@tanstack/ai'\nimport type {\n Content,\n GenerateContentConfig,\n GenerateContentResponse,\n GenerateImagesConfig,\n GenerateImagesResponse,\n GoogleGenAI,\n Part,\n} from '@google/genai'\nimport type { GeminiClientConfig } from '../utils'\n\n/**\n * Configuration for Gemini image adapter\n */\nexport interface GeminiImageConfig extends GeminiClientConfig {}\n\n/** Model type for Gemini Image */\nexport type GeminiImageModel = (typeof GEMINI_IMAGE_MODELS)[number]\n\n/**\n * Gemini Image Generation Adapter\n *\n * Tree-shakeable adapter for Gemini image generation functionality.\n * Supports Imagen 3/4 models (via generateImages API) and Gemini native\n * image models like Nano Banana 2 (via generateContent API).\n *\n * Features:\n * - Aspect ratio-based image sizing\n * - Person generation controls\n * - Safety filtering\n * - Watermark options\n * - Extended resolution tiers (Nano Banana 2)\n */\nexport class GeminiImageAdapter<\n TModel extends GeminiImageModel,\n> extends BaseImageAdapter<\n TModel,\n GeminiImageProviderOptions,\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName,\n GeminiImageModelInputModalitiesByName\n> {\n override readonly kind = 'image' as const\n readonly name = 'gemini' as const\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: GeminiImageProviderOptions\n modelProviderOptionsByName: GeminiImageModelProviderOptionsByName\n modelSizeByName: GeminiImageModelSizeByName\n modelInputModalitiesByName: GeminiImageModelInputModalitiesByName\n }\n\n private readonly client: GoogleGenAI\n\n constructor(config: GeminiImageConfig, model: TModel) {\n super(model, config)\n this.client = createGeminiClient(config)\n }\n\n async generateImages(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): Promise<ImageGenerationResult> {\n const { model, logger } = options\n\n logger.request(\n `activity=generateImage provider=gemini model=${this.model}`,\n {\n provider: 'gemini',\n model: this.model,\n },\n )\n\n try {\n const resolved = resolveMediaPrompt(options.prompt)\n\n // Image-only prompts are allowed (the image inputs carry the intent);\n // a prompt with neither text nor images is always an error.\n if (resolved.images.length === 0) {\n validatePrompt({ prompt: resolved.text, model })\n }\n\n if (resolved.videos.length > 0) {\n throw new Error(\n `${this.name}.generateImages does not support video prompt parts (model: ${model}).`,\n )\n }\n if (resolved.audios.length > 0) {\n throw new Error(\n `${this.name}.generateImages does not support audio prompt parts (model: ${model}).`,\n )\n }\n\n if (this.isGeminiImageModel(model)) {\n return await this.generateWithGeminiApi(options, resolved)\n }\n\n // Imagen does not accept image inputs — it's strictly text-to-image.\n if (resolved.images.length > 0) {\n throw new Error(\n `${this.name}: model \"${model}\" (Imagen) does not support image prompt parts. ` +\n `Use a Gemini-native image model (e.g. gemini-2.5-flash-image, \"nano-banana\") for image-conditioned generation.`,\n )\n }\n\n // Imagen models path (generateImages API)\n validateImageSize(model, options.size)\n validateNumberOfImages(model, options.numberOfImages)\n\n const config = this.buildImagenConfig(options)\n\n const response = await this.client.models.generateImages({\n model,\n prompt: resolved.text,\n config,\n })\n\n return this.transformImagenResponse(model, response)\n } catch (error) {\n logger.errors('gemini.generateImage fatal', {\n error,\n source: 'gemini.generateImage',\n })\n throw error\n }\n }\n\n private isGeminiImageModel(model: string): boolean {\n return model.startsWith('gemini-')\n }\n\n private async generateWithGeminiApi(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n resolved: ResolvedMediaPrompt,\n ): Promise<ImageGenerationResult> {\n const { model, size, numberOfImages, modelOptions } = options\n\n const parsedSize = size ? parseNativeImageSize(size) : undefined\n\n // GeminiImageProviderOptions is Imagen-shaped — most fields\n // (personGeneration, safetyFilterLevel, addWatermark, outputMimeType,\n // outputCompressionQuality, guidanceScale, enhancePrompt,\n // includeSafetyAttributes, includeRaiReason, outputGcsUri, labels,\n // negativePrompt, language) are only valid on GenerateImagesConfig and\n // would be rejected by the Gemini-native generateContent path. Pick only\n // the fields that are valid on GenerateContentConfig instead of spreading\n // the whole options object.\n const nativeConfig: GenerateContentConfig = {}\n if (modelOptions?.seed !== undefined) {\n nativeConfig.seed = modelOptions.seed\n }\n\n const config: GenerateContentConfig = {\n ...nativeConfig,\n // Include TEXT so the model can interleave descriptions between images.\n // IMPORTANT: responseModalities is a protected default — set it AFTER\n // nativeConfig so nothing can silently disable image output.\n responseModalities: ['TEXT', 'IMAGE'],\n ...(parsedSize && {\n imageConfig: {\n ...(parsedSize.aspectRatio && {\n aspectRatio: parsedSize.aspectRatio,\n }),\n ...(parsedSize.resolution && {\n imageSize: parsedSize.resolution,\n }),\n },\n }),\n }\n\n const contents = await this.buildContents(resolved, numberOfImages)\n\n const response = await this.client.models.generateContent({\n model,\n contents,\n config,\n })\n\n return this.transformGeminiResponse(model, response)\n }\n\n /**\n * Build the multimodal `contents` payload. Text-only prompts pass through\n * as a plain string (the SDK accepts it directly); prompts with image\n * parts become a single user `Content` whose `parts` mirror the prompt's\n * interleaved order — position is meaningful to Gemini (\"not like this\n * *(image)*, more like this *(image)*\").\n *\n * The generateContent API has no numberOfImages parameter, so when more\n * than one image is requested a trailing instruction is appended.\n */\n private async buildContents(\n resolved: ResolvedMediaPrompt,\n numberOfImages: number | undefined,\n ): Promise<string | Array<Content>> {\n const countInstruction =\n numberOfImages && numberOfImages > 1\n ? `Generate ${numberOfImages} distinct images.`\n : undefined\n\n if (resolved.images.length === 0) {\n return countInstruction\n ? `${resolved.text} ${countInstruction}`\n : resolved.text\n }\n\n const parts: Array<Part> = await Promise.all(\n resolved.parts.map((part) => {\n if (part.type === 'text') {\n return Promise.resolve<Part>({ text: part.content })\n }\n if (part.type === 'image') {\n return this.imagePartToGeminiPart(part)\n }\n // Video / audio parts were rejected in generateImages above.\n throw new Error(\n `gemini: unsupported prompt part type \"${part.type}\" in image generation.`,\n )\n }),\n )\n if (countInstruction) {\n parts.push({ text: countInstruction })\n }\n return [{ role: 'user', parts }]\n }\n\n private async imagePartToGeminiPart(\n part: ImagePart<MediaInputMetadata>,\n ): Promise<Part> {\n if (part.source.type === 'data') {\n return {\n inlineData: {\n mimeType: part.source.mimeType || 'image/png',\n data: part.source.value,\n },\n }\n }\n // For URL sources, prefer passing the URL through as `fileData` when it\n // looks like a Google Files API URI; otherwise fetch and inline as base64.\n if (\n part.source.value.startsWith('gs://') ||\n /^https?:\\/\\/generativelanguage\\.googleapis\\.com\\//.test(\n part.source.value,\n )\n ) {\n return {\n fileData: {\n fileUri: part.source.value,\n ...(part.source.mimeType && { mimeType: part.source.mimeType }),\n },\n }\n }\n const response = await fetch(part.source.value)\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image input (${response.status} ${response.statusText}): ${part.source.value}`,\n )\n }\n const blob = await response.blob()\n const buffer = await blob.arrayBuffer()\n const base64 = arrayBufferToBase64(buffer)\n return {\n inlineData: {\n mimeType: part.source.mimeType || blob.type || 'image/png',\n data: base64,\n },\n }\n }\n\n private transformGeminiResponse(\n model: string,\n response: GenerateContentResponse,\n ): ImageGenerationResult {\n const images: Array<GeneratedImage> = []\n const textParts: Array<string> = []\n const parts = response.candidates?.[0]?.content?.parts ?? []\n\n for (const part of parts) {\n if (\n part.inlineData?.data &&\n typeof part.inlineData.data === 'string' &&\n part.inlineData.data.length > 0\n ) {\n images.push({ b64Json: part.inlineData.data })\n } else if (typeof part.text === 'string' && part.text.length > 0) {\n textParts.push(part.text)\n }\n }\n\n // If the model returned only text parts (for example a safety refusal\n // or a \"can't do that\" message), surface the text instead of silently\n // resolving to an empty images array — otherwise callers can't tell a\n // generation failure apart from a genuine empty response.\n if (images.length === 0) {\n const reason =\n textParts.length > 0\n ? `: ${textParts.join(' ').trim()}`\n : ' (no inline image or text parts were returned).'\n throw new Error(`Gemini ${model} returned no images${reason}`)\n }\n\n return {\n id: generateId(this.name),\n model,\n images,\n // Surface token usage (with per-modality breakdown) when the model\n // reports it (e.g. Nano Banana via generateContent). Conditionally spread\n // to satisfy exactOptionalPropertyTypes — only include usage when\n // present. See #330.\n ...(response.usageMetadata\n ? { usage: buildGeminiUsage(response.usageMetadata) }\n : {}),\n }\n }\n\n private buildImagenConfig(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): GenerateImagesConfig {\n const { size, numberOfImages, modelOptions } = options\n\n // Build with conditional spreads — under exactOptionalPropertyTypes the\n // vendor `GenerateImagesConfig` fields are `field?: T` (no `| undefined`),\n // so we can only assign the property when we actually have a value.\n const sizeAspectRatio = size ? sizeToAspectRatio(size) : undefined\n return {\n numberOfImages: numberOfImages ?? 1,\n // Map size to aspect ratio if provided (modelOptions.aspectRatio will override)\n ...(sizeAspectRatio !== undefined && { aspectRatio: sizeAspectRatio }),\n ...modelOptions,\n }\n }\n\n private transformImagenResponse(\n model: string,\n response: GenerateImagesResponse,\n ): ImageGenerationResult {\n const entries = response.generatedImages ?? []\n const images: Array<GeneratedImage> = []\n const filterReasons: Array<string> = []\n\n for (const item of entries) {\n const b64Json = item.image?.imageBytes\n if (b64Json) {\n images.push({\n b64Json,\n ...(item.enhancedPrompt !== undefined && {\n revisedPrompt: item.enhancedPrompt,\n }),\n })\n continue\n }\n // Imagen can drop individual entries with a raiFilteredReason when\n // Responsible-AI filters fire. Preserve the reason so callers can\n // surface it instead of silently getting back fewer images.\n const reason = (item as { raiFilteredReason?: string }).raiFilteredReason\n if (reason) {\n filterReasons.push(reason)\n }\n }\n\n // Every entry was filtered — no usable images to return. Throw rather\n // than resolve to an empty array so the caller is forced to handle the\n // failure mode explicitly.\n if (entries.length > 0 && images.length === 0) {\n const joined = filterReasons.length > 0 ? filterReasons.join('; ') : ''\n throw new Error(\n `Imagen ${model} returned no images: all ${entries.length} generated image(s) were filtered by Responsible-AI${joined ? ` (${joined})` : ''}.`,\n )\n }\n\n // Partial filter: surface via console.warn since ImageGenerationResult\n // has no warnings field. Callers that care can still inspect the count\n // mismatch between requested and returned images.\n if (filterReasons.length > 0 && typeof console !== 'undefined') {\n console.warn(\n `[gemini-image] ${filterReasons.length} of ${entries.length} images from ${model} were filtered by Responsible-AI: ${filterReasons.join('; ')}`,\n )\n }\n\n return {\n id: generateId(this.name),\n model,\n images,\n }\n }\n}\n\n/**\n * Creates a Gemini image adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'imagen-3.0-generate-002')\n * @param apiKey - Your Google API key\n * @param config - Optional additional configuration\n * @returns Configured Gemini image adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGeminiImage('imagen-3.0-generate-002', \"your-api-key\");\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A cute baby sea otter'\n * });\n * ```\n */\nexport function createGeminiImage<TModel extends GeminiImageModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<TModel> {\n return new GeminiImageAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini image 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 * @param model - The model name (e.g., 'imagen-4.0-generate-001')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Gemini image 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 = geminiImage('imagen-4.0-generate-001');\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A beautiful sunset over mountains'\n * });\n * ```\n */\nexport function geminiImage<TModel extends GeminiImageModel>(\n model: TModel,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<TModel> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiImage(model, apiKey, config)\n}\n"],"names":[],"mappings":";;;;;;AAgEO,MAAM,2BAEH,iBAMR;AAAA,EACkB,OAAO;AAAA,EAChB,OAAO;AAAA,EAUC;AAAA,EAEjB,YAAY,QAA2B,OAAe;AACpD,UAAM,OAAO,MAAM;AACnB,SAAK,SAAS,mBAAmB,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,eACJ,SACgC;AAChC,UAAM,EAAE,OAAO,OAAA,IAAW;AAE1B,WAAO;AAAA,MACL,gDAAgD,KAAK,KAAK;AAAA,MAC1D;AAAA,QACE,UAAU;AAAA,QACV,OAAO,KAAK;AAAA,MAAA;AAAA,IACd;AAGF,QAAI;AACF,YAAM,WAAW,mBAAmB,QAAQ,MAAM;AAIlD,UAAI,SAAS,OAAO,WAAW,GAAG;AAChC,uBAAe,EAAE,QAAQ,SAAS,MAAM,OAAO;AAAA,MACjD;AAEA,UAAI,SAAS,OAAO,SAAS,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,GAAG,KAAK,IAAI,+DAA+D,KAAK;AAAA,QAAA;AAAA,MAEpF;AACA,UAAI,SAAS,OAAO,SAAS,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,GAAG,KAAK,IAAI,+DAA+D,KAAK;AAAA,QAAA;AAAA,MAEpF;AAEA,UAAI,KAAK,mBAAmB,KAAK,GAAG;AAClC,eAAO,MAAM,KAAK,sBAAsB,SAAS,QAAQ;AAAA,MAC3D;AAGA,UAAI,SAAS,OAAO,SAAS,GAAG;AAC9B,cAAM,IAAI;AAAA,UACR,GAAG,KAAK,IAAI,YAAY,KAAK;AAAA,QAAA;AAAA,MAGjC;AAGA,wBAAkB,OAAO,QAAQ,IAAI;AACrC,6BAAuB,OAAO,QAAQ,cAAc;AAEpD,YAAM,SAAS,KAAK,kBAAkB,OAAO;AAE7C,YAAM,WAAW,MAAM,KAAK,OAAO,OAAO,eAAe;AAAA,QACvD;AAAA,QACA,QAAQ,SAAS;AAAA,QACjB;AAAA,MAAA,CACD;AAED,aAAO,KAAK,wBAAwB,OAAO,QAAQ;AAAA,IACrD,SAAS,OAAO;AACd,aAAO,OAAO,8BAA8B;AAAA,QAC1C;AAAA,QACA,QAAQ;AAAA,MAAA,CACT;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,mBAAmB,OAAwB;AACjD,WAAO,MAAM,WAAW,SAAS;AAAA,EACnC;AAAA,EAEA,MAAc,sBACZ,SACA,UACgC;AAChC,UAAM,EAAE,OAAO,MAAM,gBAAgB,iBAAiB;AAEtD,UAAM,aAAa,OAAO,qBAAqB,IAAI,IAAI;AAUvD,UAAM,eAAsC,CAAA;AAC5C,QAAI,cAAc,SAAS,QAAW;AACpC,mBAAa,OAAO,aAAa;AAAA,IACnC;AAEA,UAAM,SAAgC;AAAA,MACpC,GAAG;AAAA;AAAA;AAAA;AAAA,MAIH,oBAAoB,CAAC,QAAQ,OAAO;AAAA,MACpC,GAAI,cAAc;AAAA,QAChB,aAAa;AAAA,UACX,GAAI,WAAW,eAAe;AAAA,YAC5B,aAAa,WAAW;AAAA,UAAA;AAAA,UAE1B,GAAI,WAAW,cAAc;AAAA,YAC3B,WAAW,WAAW;AAAA,UAAA;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAGF,UAAM,WAAW,MAAM,KAAK,cAAc,UAAU,cAAc;AAElE,UAAM,WAAW,MAAM,KAAK,OAAO,OAAO,gBAAgB;AAAA,MACxD;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAED,WAAO,KAAK,wBAAwB,OAAO,QAAQ;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,cACZ,UACA,gBACkC;AAClC,UAAM,mBACJ,kBAAkB,iBAAiB,IAC/B,YAAY,cAAc,sBAC1B;AAEN,QAAI,SAAS,OAAO,WAAW,GAAG;AAChC,aAAO,mBACH,GAAG,SAAS,IAAI,IAAI,gBAAgB,KACpC,SAAS;AAAA,IACf;AAEA,UAAM,QAAqB,MAAM,QAAQ;AAAA,MACvC,SAAS,MAAM,IAAI,CAAC,SAAS;AAC3B,YAAI,KAAK,SAAS,QAAQ;AACxB,iBAAO,QAAQ,QAAc,EAAE,MAAM,KAAK,SAAS;AAAA,QACrD;AACA,YAAI,KAAK,SAAS,SAAS;AACzB,iBAAO,KAAK,sBAAsB,IAAI;AAAA,QACxC;AAEA,cAAM,IAAI;AAAA,UACR,yCAAyC,KAAK,IAAI;AAAA,QAAA;AAAA,MAEtD,CAAC;AAAA,IAAA;AAEH,QAAI,kBAAkB;AACpB,YAAM,KAAK,EAAE,MAAM,iBAAA,CAAkB;AAAA,IACvC;AACA,WAAO,CAAC,EAAE,MAAM,QAAQ,OAAO;AAAA,EACjC;AAAA,EAEA,MAAc,sBACZ,MACe;AACf,QAAI,KAAK,OAAO,SAAS,QAAQ;AAC/B,aAAO;AAAA,QACL,YAAY;AAAA,UACV,UAAU,KAAK,OAAO,YAAY;AAAA,UAClC,MAAM,KAAK,OAAO;AAAA,QAAA;AAAA,MACpB;AAAA,IAEJ;AAGA,QACE,KAAK,OAAO,MAAM,WAAW,OAAO,KACpC,oDAAoD;AAAA,MAClD,KAAK,OAAO;AAAA,IAAA,GAEd;AACA,aAAO;AAAA,QACL,UAAU;AAAA,UACR,SAAS,KAAK,OAAO;AAAA,UACrB,GAAI,KAAK,OAAO,YAAY,EAAE,UAAU,KAAK,OAAO,SAAA;AAAA,QAAS;AAAA,MAC/D;AAAA,IAEJ;AACA,UAAM,WAAW,MAAM,MAAM,KAAK,OAAO,KAAK;AAC9C,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,gCAAgC,SAAS,MAAM,IAAI,SAAS,UAAU,MAAM,KAAK,OAAO,KAAK;AAAA,MAAA;AAAA,IAEjG;AACA,UAAM,OAAO,MAAM,SAAS,KAAA;AAC5B,UAAM,SAAS,MAAM,KAAK,YAAA;AAC1B,UAAM,SAAS,oBAAoB,MAAM;AACzC,WAAO;AAAA,MACL,YAAY;AAAA,QACV,UAAU,KAAK,OAAO,YAAY,KAAK,QAAQ;AAAA,QAC/C,MAAM;AAAA,MAAA;AAAA,IACR;AAAA,EAEJ;AAAA,EAEQ,wBACN,OACA,UACuB;AACvB,UAAM,SAAgC,CAAA;AACtC,UAAM,YAA2B,CAAA;AACjC,UAAM,QAAQ,SAAS,aAAa,CAAC,GAAG,SAAS,SAAS,CAAA;AAE1D,eAAW,QAAQ,OAAO;AACxB,UACE,KAAK,YAAY,QACjB,OAAO,KAAK,WAAW,SAAS,YAChC,KAAK,WAAW,KAAK,SAAS,GAC9B;AACA,eAAO,KAAK,EAAE,SAAS,KAAK,WAAW,MAAM;AAAA,MAC/C,WAAW,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,GAAG;AAChE,kBAAU,KAAK,KAAK,IAAI;AAAA,MAC1B;AAAA,IACF;AAMA,QAAI,OAAO,WAAW,GAAG;AACvB,YAAM,SACJ,UAAU,SAAS,IACf,KAAK,UAAU,KAAK,GAAG,EAAE,KAAA,CAAM,KAC/B;AACN,YAAM,IAAI,MAAM,UAAU,KAAK,sBAAsB,MAAM,EAAE;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,IAAI,WAAW,KAAK,IAAI;AAAA,MACxB;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,GAAI,SAAS,gBACT,EAAE,OAAO,iBAAiB,SAAS,aAAa,MAChD,CAAA;AAAA,IAAC;AAAA,EAET;AAAA,EAEQ,kBACN,SACsB;AACtB,UAAM,EAAE,MAAM,gBAAgB,aAAA,IAAiB;AAK/C,UAAM,kBAAkB,OAAO,kBAAkB,IAAI,IAAI;AACzD,WAAO;AAAA,MACL,gBAAgB,kBAAkB;AAAA;AAAA,MAElC,GAAI,oBAAoB,UAAa,EAAE,aAAa,gBAAA;AAAA,MACpD,GAAG;AAAA,IAAA;AAAA,EAEP;AAAA,EAEQ,wBACN,OACA,UACuB;AACvB,UAAM,UAAU,SAAS,mBAAmB,CAAA;AAC5C,UAAM,SAAgC,CAAA;AACtC,UAAM,gBAA+B,CAAA;AAErC,eAAW,QAAQ,SAAS;AAC1B,YAAM,UAAU,KAAK,OAAO;AAC5B,UAAI,SAAS;AACX,eAAO,KAAK;AAAA,UACV;AAAA,UACA,GAAI,KAAK,mBAAmB,UAAa;AAAA,YACvC,eAAe,KAAK;AAAA,UAAA;AAAA,QACtB,CACD;AACD;AAAA,MACF;AAIA,YAAM,SAAU,KAAwC;AACxD,UAAI,QAAQ;AACV,sBAAc,KAAK,MAAM;AAAA,MAC3B;AAAA,IACF;AAKA,QAAI,QAAQ,SAAS,KAAK,OAAO,WAAW,GAAG;AAC7C,YAAM,SAAS,cAAc,SAAS,IAAI,cAAc,KAAK,IAAI,IAAI;AACrE,YAAM,IAAI;AAAA,QACR,UAAU,KAAK,4BAA4B,QAAQ,MAAM,sDAAsD,SAAS,KAAK,MAAM,MAAM,EAAE;AAAA,MAAA;AAAA,IAE/I;AAKA,QAAI,cAAc,SAAS,KAAK,OAAO,YAAY,aAAa;AAC9D,cAAQ;AAAA,QACN,kBAAkB,cAAc,MAAM,OAAO,QAAQ,MAAM,gBAAgB,KAAK,qCAAqC,cAAc,KAAK,IAAI,CAAC;AAAA,MAAA;AAAA,IAEjJ;AAEA,WAAO;AAAA,MACL,IAAI,WAAW,KAAK,IAAI;AAAA,MACxB;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AACF;AAqBO,SAAS,kBACd,OACA,QACA,QAC4B;AAC5B,SAAO,IAAI,mBAAmB,EAAE,QAAQ,GAAG,OAAA,GAAU,KAAK;AAC5D;AA0BO,SAAS,YACd,OACA,QAC4B;AAC5B,QAAM,SAAS,uBAAA;AACf,SAAO,kBAAkB,OAAO,QAAQ,MAAM;AAChD;"}
|
|
1
|
+
{"version":3,"file":"image.js","sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport { arrayBufferToBase64 } from '@tanstack/ai-utils'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport { buildGeminiUsage } from '../usage'\nimport {\n parseNativeImageSize,\n sizeToAspectRatio,\n validateImageSize,\n validateNumberOfImages,\n validatePrompt,\n} from '../image/image-provider-options'\nimport type { GEMINI_IMAGE_MODELS } from '../model-meta'\nimport type {\n GeminiImageModelInputModalitiesByName,\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName,\n GeminiImageProviderOptions,\n} from '../image/image-provider-options'\nimport type {\n GeneratedImage,\n ImageGenerationOptions,\n ImageGenerationResult,\n ImagePart,\n MediaInputMetadata,\n ResolvedMediaPrompt,\n} from '@tanstack/ai'\nimport type {\n Content,\n GenerateContentConfig,\n GenerateContentResponse,\n GenerateImagesConfig,\n GenerateImagesResponse,\n GoogleGenAI,\n Part,\n} from '@google/genai'\nimport type { GeminiClientConfig } from '../utils'\n\n/**\n * Configuration for Gemini image adapter\n */\nexport interface GeminiImageConfig extends GeminiClientConfig {}\n\n/** Model type for Gemini Image */\nexport type GeminiImageModel = (typeof GEMINI_IMAGE_MODELS)[number]\n\n/**\n * Gemini Image Generation Adapter\n *\n * Tree-shakeable adapter for Gemini image generation functionality.\n * Supports Imagen 3/4 models (via generateImages API) and Gemini native\n * image models like Nano Banana 2 (via generateContent API).\n *\n * Features:\n * - Aspect ratio-based image sizing\n * - Person generation controls\n * - Safety filtering\n * - Watermark options\n * - Extended resolution tiers (Nano Banana 2)\n */\nexport class GeminiImageAdapter<\n TModel extends GeminiImageModel,\n> extends BaseImageAdapter<\n TModel,\n GeminiImageProviderOptions,\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName,\n GeminiImageModelInputModalitiesByName\n> {\n override readonly kind = 'image' as const\n readonly name = 'gemini' as const\n\n // Type-only property - never assigned at runtime\n declare '~types': {\n providerOptions: GeminiImageProviderOptions\n modelProviderOptionsByName: GeminiImageModelProviderOptionsByName\n modelSizeByName: GeminiImageModelSizeByName\n modelInputModalitiesByName: GeminiImageModelInputModalitiesByName\n }\n\n private readonly client: GoogleGenAI\n\n constructor(config: GeminiImageConfig, model: TModel) {\n super(model, config)\n this.client = createGeminiClient(config)\n }\n\n async generateImages(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): Promise<ImageGenerationResult> {\n const { model, logger } = options\n\n logger.request(\n `activity=generateImage provider=gemini model=${this.model}`,\n {\n provider: 'gemini',\n model: this.model,\n },\n )\n\n try {\n const resolved = resolveMediaPrompt(options.prompt)\n\n // Image-only prompts are allowed (the image inputs carry the intent);\n // a prompt with neither text nor images is always an error.\n if (resolved.images.length === 0) {\n validatePrompt({ prompt: resolved.text, model })\n }\n\n if (resolved.videos.length > 0) {\n throw new Error(\n `${this.name}.generateImages does not support video prompt parts (model: ${model}).`,\n )\n }\n if (resolved.audios.length > 0) {\n throw new Error(\n `${this.name}.generateImages does not support audio prompt parts (model: ${model}).`,\n )\n }\n\n if (this.isGeminiImageModel(model)) {\n return await this.generateWithGeminiApi(options, resolved)\n }\n\n // Imagen does not accept image inputs — it's strictly text-to-image.\n if (resolved.images.length > 0) {\n throw new Error(\n `${this.name}: model \"${model}\" (Imagen) does not support image prompt parts. ` +\n `Use a Gemini-native image model (e.g. gemini-2.5-flash-image, \"nano-banana\") for image-conditioned generation.`,\n )\n }\n\n // Imagen models path (generateImages API)\n validateImageSize(model, options.size)\n validateNumberOfImages(model, options.numberOfImages)\n\n const config = this.buildImagenConfig(options)\n\n const response = await this.client.models.generateImages({\n model,\n prompt: resolved.text,\n config,\n })\n\n return this.transformImagenResponse(model, response)\n } catch (error) {\n logger.errors('gemini.generateImage fatal', {\n error,\n source: 'gemini.generateImage',\n })\n throw error\n }\n }\n\n private isGeminiImageModel(model: string): boolean {\n return model.startsWith('gemini-')\n }\n\n private async generateWithGeminiApi(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n resolved: ResolvedMediaPrompt,\n ): Promise<ImageGenerationResult> {\n const { model, size, numberOfImages, modelOptions } = options\n\n const parsedSize = size ? parseNativeImageSize(size) : undefined\n\n // GeminiImageProviderOptions is Imagen-shaped — most fields\n // (personGeneration, safetyFilterLevel, addWatermark, outputMimeType,\n // outputCompressionQuality, guidanceScale, enhancePrompt,\n // includeSafetyAttributes, includeRaiReason, outputGcsUri, labels,\n // negativePrompt, language) are only valid on GenerateImagesConfig and\n // would be rejected by the Gemini-native generateContent path. Pick only\n // the fields that are valid on GenerateContentConfig instead of spreading\n // the whole options object.\n const nativeConfig: GenerateContentConfig = {}\n if (modelOptions?.seed !== undefined) {\n nativeConfig.seed = modelOptions.seed\n }\n\n const config: GenerateContentConfig = {\n ...nativeConfig,\n // Include TEXT so the model can interleave descriptions between images.\n // IMPORTANT: responseModalities is a protected default — set it AFTER\n // nativeConfig so nothing can silently disable image output.\n responseModalities: ['TEXT', 'IMAGE'],\n ...(parsedSize && {\n imageConfig: {\n ...(parsedSize.aspectRatio && {\n aspectRatio: parsedSize.aspectRatio,\n }),\n ...(parsedSize.resolution && {\n imageSize: parsedSize.resolution,\n }),\n },\n }),\n }\n\n const contents = await this.buildContents(resolved, numberOfImages)\n\n const response = await this.client.models.generateContent({\n model,\n contents,\n config,\n })\n\n return this.transformGeminiResponse(model, response)\n }\n\n /**\n * Build the multimodal `contents` payload. Text-only prompts pass through\n * as a plain string (the SDK accepts it directly); prompts with image\n * parts become a single user `Content` whose `parts` mirror the prompt's\n * interleaved order — position is meaningful to Gemini (\"not like this\n * *(image)*, more like this *(image)*\").\n *\n * The generateContent API has no numberOfImages parameter, so when more\n * than one image is requested a trailing instruction is appended.\n */\n private async buildContents(\n resolved: ResolvedMediaPrompt,\n numberOfImages: number | undefined,\n ): Promise<string | Array<Content>> {\n const countInstruction =\n numberOfImages && numberOfImages > 1\n ? `Generate ${numberOfImages} distinct images.`\n : undefined\n\n if (resolved.images.length === 0) {\n return countInstruction\n ? `${resolved.text} ${countInstruction}`\n : resolved.text\n }\n\n const parts: Array<Part> = await Promise.all(\n resolved.parts.map((part) => {\n if (part.type === 'text') {\n return Promise.resolve<Part>({ text: part.content })\n }\n if (part.type === 'image') {\n return this.imagePartToGeminiPart(part)\n }\n // Video / audio parts were rejected in generateImages above.\n throw new Error(\n `gemini: unsupported prompt part type \"${part.type}\" in image generation.`,\n )\n }),\n )\n if (countInstruction) {\n parts.push({ text: countInstruction })\n }\n return [{ role: 'user', parts }]\n }\n\n private async imagePartToGeminiPart(\n part: ImagePart<MediaInputMetadata>,\n ): Promise<Part> {\n if (part.source.type === 'data') {\n return {\n inlineData: {\n mimeType: part.source.mimeType || 'image/png',\n data: part.source.value,\n },\n }\n }\n // For URL sources, prefer passing the URL through as `fileData` when it\n // looks like a Google Files API URI; otherwise fetch and inline as base64.\n if (\n part.source.value.startsWith('gs://') ||\n /^https?:\\/\\/generativelanguage\\.googleapis\\.com\\//.test(\n part.source.value,\n )\n ) {\n return {\n fileData: {\n fileUri: part.source.value,\n ...(part.source.mimeType && { mimeType: part.source.mimeType }),\n },\n }\n }\n const response = await fetch(part.source.value)\n if (!response.ok) {\n throw new Error(\n `Failed to fetch image input (${response.status} ${response.statusText}): ${part.source.value}`,\n )\n }\n const blob = await response.blob()\n const buffer = await blob.arrayBuffer()\n const base64 = arrayBufferToBase64(buffer)\n return {\n inlineData: {\n mimeType: part.source.mimeType || blob.type || 'image/png',\n data: base64,\n },\n }\n }\n\n private transformGeminiResponse(\n model: string,\n response: GenerateContentResponse,\n ): ImageGenerationResult {\n const images: Array<GeneratedImage> = []\n const textParts: Array<string> = []\n const parts = response.candidates?.[0]?.content?.parts ?? []\n\n for (const part of parts) {\n if (\n part.inlineData?.data &&\n typeof part.inlineData.data === 'string' &&\n part.inlineData.data.length > 0\n ) {\n images.push({ b64Json: part.inlineData.data })\n } else if (typeof part.text === 'string' && part.text.length > 0) {\n textParts.push(part.text)\n }\n }\n\n // If the model returned only text parts (for example a safety refusal\n // or a \"can't do that\" message), surface the text instead of silently\n // resolving to an empty images array — otherwise callers can't tell a\n // generation failure apart from a genuine empty response.\n if (images.length === 0) {\n const reason =\n textParts.length > 0\n ? `: ${textParts.join(' ').trim()}`\n : ' (no inline image or text parts were returned).'\n throw new Error(`Gemini ${model} returned no images${reason}`)\n }\n\n return {\n id: generateId(this.name),\n model,\n images,\n // Surface token usage (with per-modality breakdown) when the model\n // reports it (e.g. Nano Banana via generateContent). Conditionally spread\n // to satisfy exactOptionalPropertyTypes — only include usage when\n // present. See #330.\n ...(response.usageMetadata\n ? { usage: buildGeminiUsage(response.usageMetadata) }\n : {}),\n }\n }\n\n private buildImagenConfig(\n options: ImageGenerationOptions<GeminiImageProviderOptions>,\n ): GenerateImagesConfig {\n const { size, numberOfImages, modelOptions } = options\n\n // Build with conditional spreads — under exactOptionalPropertyTypes the\n // vendor `GenerateImagesConfig` fields are `field?: T` (no `| undefined`),\n // so we can only assign the property when we actually have a value.\n const sizeAspectRatio = size ? sizeToAspectRatio(size) : undefined\n return {\n numberOfImages: numberOfImages ?? 1,\n // Map size to aspect ratio if provided (modelOptions.aspectRatio will override)\n ...(sizeAspectRatio !== undefined && { aspectRatio: sizeAspectRatio }),\n ...modelOptions,\n }\n }\n\n private transformImagenResponse(\n model: string,\n response: GenerateImagesResponse,\n ): ImageGenerationResult {\n const entries = response.generatedImages ?? []\n const images: Array<GeneratedImage> = []\n const filterReasons: Array<string> = []\n\n for (const item of entries) {\n const b64Json = item.image?.imageBytes\n if (b64Json) {\n images.push({\n b64Json,\n ...(item.enhancedPrompt !== undefined && {\n revisedPrompt: item.enhancedPrompt,\n }),\n })\n continue\n }\n // Imagen can drop individual entries with a raiFilteredReason when\n // Responsible-AI filters fire. Preserve the reason so callers can\n // surface it instead of silently getting back fewer images.\n const reason = (item as { raiFilteredReason?: string }).raiFilteredReason\n if (reason) {\n filterReasons.push(reason)\n }\n }\n\n // Every entry was filtered — no usable images to return. Throw rather\n // than resolve to an empty array so the caller is forced to handle the\n // failure mode explicitly.\n if (entries.length > 0 && images.length === 0) {\n const joined = filterReasons.length > 0 ? filterReasons.join('; ') : ''\n throw new Error(\n `Imagen ${model} returned no images: all ${entries.length} generated image(s) were filtered by Responsible-AI${joined ? ` (${joined})` : ''}.`,\n )\n }\n\n // Partial filter: surface via console.warn since ImageGenerationResult\n // has no warnings field. Callers that care can still inspect the count\n // mismatch between requested and returned images.\n if (filterReasons.length > 0 && typeof console !== 'undefined') {\n console.warn(\n `[gemini-image] ${filterReasons.length} of ${entries.length} images from ${model} were filtered by Responsible-AI: ${filterReasons.join('; ')}`,\n )\n }\n\n return {\n id: generateId(this.name),\n model,\n images,\n }\n }\n}\n\n/**\n * Creates a Gemini image adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'imagen-4.0-generate-001')\n * @param apiKey - Your Google API key\n * @param config - Optional additional configuration\n * @returns Configured Gemini image adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGeminiImage('imagen-4.0-generate-001', \"your-api-key\");\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A cute baby sea otter'\n * });\n * ```\n */\nexport function createGeminiImage<TModel extends GeminiImageModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<TModel> {\n return new GeminiImageAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini image 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 * @param model - The model name (e.g., 'imagen-4.0-generate-001')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Gemini image 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 = geminiImage('imagen-4.0-generate-001');\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A beautiful sunset over mountains'\n * });\n * ```\n */\nexport function geminiImage<TModel extends GeminiImageModel>(\n model: TModel,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<TModel> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiImage(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1022,7 +1022,7 @@ function extractTextFromInteraction(interaction) {
|
|
|
1022
1022
|
return interaction.output_text;
|
|
1023
1023
|
}
|
|
1024
1024
|
let text = "";
|
|
1025
|
-
for (const step of interaction.steps) {
|
|
1025
|
+
for (const step of interaction.steps ?? []) {
|
|
1026
1026
|
if (step.type !== "model_output" || !step.content) continue;
|
|
1027
1027
|
for (const part of step.content) {
|
|
1028
1028
|
if (part.type === "text") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","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'\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'\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"],"names":["parsePartialJSON"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter.js","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'\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'\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"],"names":["parsePartialJSON"],"mappings":"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"}
|
|
@@ -120,7 +120,7 @@ export type GeminiNativeImageSize = `${GeminiNativeImageAspectRatio}_${GeminiNat
|
|
|
120
120
|
* Gemini native image models that use the generateContent API path.
|
|
121
121
|
* These models support template literal sizes (aspectRatio_resolution).
|
|
122
122
|
*/
|
|
123
|
-
export type GeminiNativeImageModels = 'gemini-3.1-flash-image-preview' | 'gemini-3-pro-image-preview' | 'gemini-2.5-flash-image';
|
|
123
|
+
export type GeminiNativeImageModels = 'gemini-3.1-flash-image-preview' | 'gemini-3.1-flash-lite-image' | 'gemini-3-pro-image-preview' | 'gemini-2.5-flash-image';
|
|
124
124
|
/**
|
|
125
125
|
* Model-specific size options mapping.
|
|
126
126
|
* Gemini native image models use template literal sizes, Imagen models use pixel sizes.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-provider-options.js","sources":["../../../src/image/image-provider-options.ts"],"sourcesContent":["import type { GeminiImageModels } from '../model-meta'\nimport type {\n ImagePromptLanguage,\n PersonGeneration,\n SafetyFilterLevel,\n} from '@google/genai'\n\n// Re-export SDK types so users can use them directly\nexport type { ImagePromptLanguage, PersonGeneration, SafetyFilterLevel }\n\n/**\n * Gemini Imagen aspect ratio options\n * Controls the aspect ratio of generated images\n */\nexport type GeminiAspectRatio =\n | '1:1'\n | '3:4'\n | '4:3'\n | '9:16'\n | '16:9'\n | '9:21'\n | '21:9'\n\n/**\n * Provider options for Gemini image generation\n * These options match the @google/genai GenerateImagesConfig interface\n * and can be spread directly into the API request.\n */\nexport interface GeminiImageProviderOptions {\n /**\n * The aspect ratio of generated images\n * @default '1:1'\n */\n aspectRatio?: GeminiAspectRatio\n\n /**\n * Controls whether people can appear in generated images\n * Use PersonGeneration enum values: DONT_ALLOW, ALLOW_ADULT, ALLOW_ALL\n * @default 'ALLOW_ADULT'\n */\n personGeneration?: PersonGeneration\n\n /**\n * Safety filter level for content filtering\n * Use SafetyFilterLevel enum values\n */\n safetyFilterLevel?: SafetyFilterLevel\n\n /**\n * Optional seed for reproducible image generation\n * When the same seed is used with the same prompt and settings,\n * you should get similar (though not identical) results\n */\n seed?: number\n\n /**\n * Whether to add a SynthID watermark to generated images\n * SynthID helps identify AI-generated content\n * @default true\n */\n addWatermark?: boolean\n\n /**\n * Language of the prompt\n * Use ImagePromptLanguage enum values\n */\n language?: ImagePromptLanguage\n\n /**\n * Negative prompt - what to avoid in the generated image\n * Not all models support negative prompts\n */\n negativePrompt?: string\n\n /**\n * Output MIME type for the generated image\n * @default 'image/png'\n */\n outputMimeType?: 'image/png' | 'image/jpeg' | 'image/webp'\n\n /**\n * Compression quality for JPEG outputs (0-100)\n * Higher values mean better quality but larger file sizes\n * @default 75\n */\n outputCompressionQuality?: number\n\n /**\n * Controls how much the model adheres to the text prompt\n * Large values increase output and prompt alignment,\n * but may compromise image quality\n */\n guidanceScale?: number\n\n /**\n * Whether to use the prompt rewriting logic\n */\n enhancePrompt?: boolean\n\n /**\n * Whether to report the safety scores of each generated image\n * and the positive prompt in the response\n */\n includeSafetyAttributes?: boolean\n\n /**\n * Whether to include the Responsible AI filter reason\n * if the image is filtered out of the response\n */\n includeRaiReason?: boolean\n\n /**\n * Cloud Storage URI used to store the generated images\n */\n outputGcsUri?: string\n\n /**\n * User specified labels to track billing usage\n */\n labels?: Record<string, string>\n}\n\n/**\n * Model-specific provider options mapping\n * Currently all Imagen models use the same options structure\n */\nexport type GeminiImageModelProviderOptionsByName = {\n [K in GeminiImageModels]: GeminiImageProviderOptions\n}\n\n/**\n * Supported size strings for Gemini Imagen models\n * These map to aspect ratios internally\n */\nexport type GeminiImageSize =\n | '1024x1024'\n | '512x512'\n | '1024x768'\n | '1536x1024'\n | '1792x1024'\n | '1920x1080'\n | '768x1024'\n | '1024x1536'\n | '1024x1792'\n | '1080x1920'\n\n/**\n * Aspect ratios supported by Gemini native image models (via generateContent API).\n * Matches the SDK's ImageConfig.aspectRatio values.\n */\nexport type GeminiNativeImageAspectRatio =\n | '1:1'\n | '2:3'\n | '3:2'\n | '3:4'\n | '4:3'\n | '9:16'\n | '16:9'\n | '21:9'\n\n/**\n * Resolution tiers for Gemini native image models.\n * Matches the SDK's ImageConfig.imageSize values.\n */\nexport type GeminiNativeImageResolution = '1K' | '2K' | '4K'\n\n/**\n * Template literal size type for Gemini native image models: \"16:9_4K\", \"1:1_2K\", etc.\n */\nexport type GeminiNativeImageSize =\n `${GeminiNativeImageAspectRatio}_${GeminiNativeImageResolution}`\n\n/**\n * Gemini native image models that use the generateContent API path.\n * These models support template literal sizes (aspectRatio_resolution).\n */\nexport type GeminiNativeImageModels =\n | 'gemini-3.1-flash-image-preview'\n | 'gemini-3-pro-image-preview'\n | 'gemini-2.5-flash-image'\n\n/**\n * Model-specific size options mapping.\n * Gemini native image models use template literal sizes, Imagen models use pixel sizes.\n */\nexport type GeminiImageModelSizeByName = {\n [K in GeminiNativeImageModels]: GeminiNativeImageSize\n} & {\n [K in Exclude<GeminiImageModels, GeminiNativeImageModels>]: GeminiImageSize\n}\n\n/**\n * Per-model prompt input modalities. Gemini-native image models accept image\n * parts in the multimodal prompt (image-conditioned generation via\n * generateContent); Imagen models are strictly text-to-image, so their\n * `prompt` is constrained to text at compile time.\n */\nexport type GeminiImageModelInputModalitiesByName = {\n [K in GeminiNativeImageModels]: readonly ['image']\n} & {\n [K in Exclude<GeminiImageModels, GeminiNativeImageModels>]: readonly []\n}\n\n/**\n * Valid sizes for Gemini Imagen models\n * Gemini uses aspect ratios, but we map common WIDTHxHEIGHT formats to aspect ratios\n * These are approximate mappings based on common image dimensions\n */\nexport const GEMINI_SIZE_TO_ASPECT_RATIO: Record<string, GeminiAspectRatio> = {\n // Square\n '1024x1024': '1:1',\n '512x512': '1:1',\n // Landscape\n '1024x768': '4:3',\n '1536x1024': '4:3',\n '1792x1024': '16:9',\n '1920x1080': '16:9',\n // Portrait\n '768x1024': '3:4',\n '1024x1536': '3:4', // Inverted\n '1024x1792': '9:16',\n '1080x1920': '9:16',\n}\n\n/**\n * Maps a WIDTHxHEIGHT size string to a Gemini aspect ratio\n * Returns undefined if the size cannot be mapped\n */\nexport function sizeToAspectRatio(\n size: string | undefined,\n): GeminiAspectRatio | undefined {\n if (!size) return undefined\n return GEMINI_SIZE_TO_ASPECT_RATIO[size]\n}\n\n/**\n * Validates that the provided size can be mapped to an aspect ratio\n * Throws an error if the size is invalid\n */\nexport function validateImageSize(\n model: string,\n size: string | undefined,\n): void {\n if (!size) return\n\n const aspectRatio = sizeToAspectRatio(size)\n if (!aspectRatio) {\n const validSizes = Object.keys(GEMINI_SIZE_TO_ASPECT_RATIO)\n throw new Error(\n `Invalid size \"${size}\" for model \"${model}\". ` +\n `Gemini Imagen uses aspect ratios. Valid sizes that map to aspect ratios: ${validSizes.join(', ')}. ` +\n `Alternatively, use providerOptions.aspectRatio directly with values: 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9`,\n )\n }\n}\n\n/**\n * Per-model caps on images per request.\n * Imagen 3 and the Imagen 4 family all support up to 4 images per request\n * via the Gemini API (the rumored 8-image tier is Vertex-only and isn't\n * reachable through @google/genai today). Unknown models fall through to\n * the shared cap defined below.\n *\n * @see https://ai.google.dev/gemini-api/docs/imagen\n */\nconst IMAGEN_MAX_IMAGES_BY_MODEL: Record<string, number> = {\n 'imagen-3.0-generate-002': 4,\n 'imagen-4.0-generate-001': 4,\n 'imagen-4.0-ultra-generate-001': 4,\n 'imagen-4.0-fast-generate-001': 4,\n}\n\nconst DEFAULT_IMAGEN_MAX_IMAGES = 4\n\n/**\n * Validates the number of images requested against the model's known cap.\n * Uses a per-model table where available and falls back to the shared\n * default otherwise — no more \"some support up to 8\" comments that don't\n * match the error message.\n */\nexport function validateNumberOfImages(\n model: string,\n numberOfImages: number | undefined,\n): void {\n if (numberOfImages === undefined) return\n\n const maxImages =\n IMAGEN_MAX_IMAGES_BY_MODEL[model] ?? DEFAULT_IMAGEN_MAX_IMAGES\n if (numberOfImages < 1 || numberOfImages > maxImages) {\n throw new Error(\n `Invalid numberOfImages \"${numberOfImages}\" for model \"${model}\". ` +\n `Must be between 1 and ${maxImages}.`,\n )\n }\n}\n\n/**\n * Validates the prompt is not empty\n */\nexport function validatePrompt(options: {\n prompt: string\n model: string\n}): void {\n const { prompt, model } = options\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(`Prompt cannot be empty for model \"${model}\".`)\n }\n}\n\n/**\n * Parses a Gemini native image size string into its components.\n * Format: \"aspectRatio_resolution\" e.g. \"16:9_4K\" → { aspectRatio: \"16:9\", resolution: \"4K\" }\n */\nexport function parseNativeImageSize(\n size: string,\n): { aspectRatio: string; resolution: string } | undefined {\n const match = size.match(/^(\\d+:\\d+)_(.+)$/)\n const [, aspectRatio, resolution] = match ?? []\n if (aspectRatio === undefined || resolution === undefined) return undefined\n return { aspectRatio, resolution }\n}\n"],"names":[],"mappings":"AAgNO,MAAM,8BAAiE;AAAA;AAAA,EAE5E,aAAa;AAAA,EACb,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAEb,YAAY;AAAA,EACZ,aAAa;AAAA;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf;AAMO,SAAS,kBACd,MAC+B;AAC/B,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,4BAA4B,IAAI;AACzC;AAMO,SAAS,kBACd,OACA,MACM;AACN,MAAI,CAAC,KAAM;AAEX,QAAM,cAAc,kBAAkB,IAAI;AAC1C,MAAI,CAAC,aAAa;AAChB,UAAM,aAAa,OAAO,KAAK,2BAA2B;AAC1D,UAAM,IAAI;AAAA,MACR,iBAAiB,IAAI,gBAAgB,KAAK,+EACoC,WAAW,KAAK,IAAI,CAAC;AAAA,IAAA;AAAA,EAGvG;AACF;AAWA,MAAM,6BAAqD;AAAA,EACzD,2BAA2B;AAAA,EAC3B,2BAA2B;AAAA,EAC3B,iCAAiC;AAAA,EACjC,gCAAgC;AAClC;AAEA,MAAM,4BAA4B;AAQ3B,SAAS,uBACd,OACA,gBACM;AACN,MAAI,mBAAmB,OAAW;AAElC,QAAM,YACJ,2BAA2B,KAAK,KAAK;AACvC,MAAI,iBAAiB,KAAK,iBAAiB,WAAW;AACpD,UAAM,IAAI;AAAA,MACR,2BAA2B,cAAc,gBAAgB,KAAK,4BACnC,SAAS;AAAA,IAAA;AAAA,EAExC;AACF;AAKO,SAAS,eAAe,SAGtB;AACP,QAAM,EAAE,QAAQ,MAAA,IAAU;AAC1B,MAAI,CAAC,UAAU,OAAO,KAAA,EAAO,WAAW,GAAG;AACzC,UAAM,IAAI,MAAM,qCAAqC,KAAK,IAAI;AAAA,EAChE;AACF;AAMO,SAAS,qBACd,MACyD;AACzD,QAAM,QAAQ,KAAK,MAAM,kBAAkB;AAC3C,QAAM,GAAG,aAAa,UAAU,IAAI,SAAS,CAAA;AAC7C,MAAI,gBAAgB,UAAa,eAAe,OAAW,QAAO;AAClE,SAAO,EAAE,aAAa,WAAA;AACxB;"}
|
|
1
|
+
{"version":3,"file":"image-provider-options.js","sources":["../../../src/image/image-provider-options.ts"],"sourcesContent":["import type { GeminiImageModels } from '../model-meta'\nimport type {\n ImagePromptLanguage,\n PersonGeneration,\n SafetyFilterLevel,\n} from '@google/genai'\n\n// Re-export SDK types so users can use them directly\nexport type { ImagePromptLanguage, PersonGeneration, SafetyFilterLevel }\n\n/**\n * Gemini Imagen aspect ratio options\n * Controls the aspect ratio of generated images\n */\nexport type GeminiAspectRatio =\n | '1:1'\n | '3:4'\n | '4:3'\n | '9:16'\n | '16:9'\n | '9:21'\n | '21:9'\n\n/**\n * Provider options for Gemini image generation\n * These options match the @google/genai GenerateImagesConfig interface\n * and can be spread directly into the API request.\n */\nexport interface GeminiImageProviderOptions {\n /**\n * The aspect ratio of generated images\n * @default '1:1'\n */\n aspectRatio?: GeminiAspectRatio\n\n /**\n * Controls whether people can appear in generated images\n * Use PersonGeneration enum values: DONT_ALLOW, ALLOW_ADULT, ALLOW_ALL\n * @default 'ALLOW_ADULT'\n */\n personGeneration?: PersonGeneration\n\n /**\n * Safety filter level for content filtering\n * Use SafetyFilterLevel enum values\n */\n safetyFilterLevel?: SafetyFilterLevel\n\n /**\n * Optional seed for reproducible image generation\n * When the same seed is used with the same prompt and settings,\n * you should get similar (though not identical) results\n */\n seed?: number\n\n /**\n * Whether to add a SynthID watermark to generated images\n * SynthID helps identify AI-generated content\n * @default true\n */\n addWatermark?: boolean\n\n /**\n * Language of the prompt\n * Use ImagePromptLanguage enum values\n */\n language?: ImagePromptLanguage\n\n /**\n * Negative prompt - what to avoid in the generated image\n * Not all models support negative prompts\n */\n negativePrompt?: string\n\n /**\n * Output MIME type for the generated image\n * @default 'image/png'\n */\n outputMimeType?: 'image/png' | 'image/jpeg' | 'image/webp'\n\n /**\n * Compression quality for JPEG outputs (0-100)\n * Higher values mean better quality but larger file sizes\n * @default 75\n */\n outputCompressionQuality?: number\n\n /**\n * Controls how much the model adheres to the text prompt\n * Large values increase output and prompt alignment,\n * but may compromise image quality\n */\n guidanceScale?: number\n\n /**\n * Whether to use the prompt rewriting logic\n */\n enhancePrompt?: boolean\n\n /**\n * Whether to report the safety scores of each generated image\n * and the positive prompt in the response\n */\n includeSafetyAttributes?: boolean\n\n /**\n * Whether to include the Responsible AI filter reason\n * if the image is filtered out of the response\n */\n includeRaiReason?: boolean\n\n /**\n * Cloud Storage URI used to store the generated images\n */\n outputGcsUri?: string\n\n /**\n * User specified labels to track billing usage\n */\n labels?: Record<string, string>\n}\n\n/**\n * Model-specific provider options mapping\n * Currently all Imagen models use the same options structure\n */\nexport type GeminiImageModelProviderOptionsByName = {\n [K in GeminiImageModels]: GeminiImageProviderOptions\n}\n\n/**\n * Supported size strings for Gemini Imagen models\n * These map to aspect ratios internally\n */\nexport type GeminiImageSize =\n | '1024x1024'\n | '512x512'\n | '1024x768'\n | '1536x1024'\n | '1792x1024'\n | '1920x1080'\n | '768x1024'\n | '1024x1536'\n | '1024x1792'\n | '1080x1920'\n\n/**\n * Aspect ratios supported by Gemini native image models (via generateContent API).\n * Matches the SDK's ImageConfig.aspectRatio values.\n */\nexport type GeminiNativeImageAspectRatio =\n | '1:1'\n | '2:3'\n | '3:2'\n | '3:4'\n | '4:3'\n | '9:16'\n | '16:9'\n | '21:9'\n\n/**\n * Resolution tiers for Gemini native image models.\n * Matches the SDK's ImageConfig.imageSize values.\n */\nexport type GeminiNativeImageResolution = '1K' | '2K' | '4K'\n\n/**\n * Template literal size type for Gemini native image models: \"16:9_4K\", \"1:1_2K\", etc.\n */\nexport type GeminiNativeImageSize =\n `${GeminiNativeImageAspectRatio}_${GeminiNativeImageResolution}`\n\n/**\n * Gemini native image models that use the generateContent API path.\n * These models support template literal sizes (aspectRatio_resolution).\n */\nexport type GeminiNativeImageModels =\n | 'gemini-3.1-flash-image-preview'\n | 'gemini-3.1-flash-lite-image'\n | 'gemini-3-pro-image-preview'\n | 'gemini-2.5-flash-image'\n\n/**\n * Model-specific size options mapping.\n * Gemini native image models use template literal sizes, Imagen models use pixel sizes.\n */\nexport type GeminiImageModelSizeByName = {\n [K in GeminiNativeImageModels]: GeminiNativeImageSize\n} & {\n [K in Exclude<GeminiImageModels, GeminiNativeImageModels>]: GeminiImageSize\n}\n\n/**\n * Per-model prompt input modalities. Gemini-native image models accept image\n * parts in the multimodal prompt (image-conditioned generation via\n * generateContent); Imagen models are strictly text-to-image, so their\n * `prompt` is constrained to text at compile time.\n */\nexport type GeminiImageModelInputModalitiesByName = {\n [K in GeminiNativeImageModels]: readonly ['image']\n} & {\n [K in Exclude<GeminiImageModels, GeminiNativeImageModels>]: readonly []\n}\n\n/**\n * Valid sizes for Gemini Imagen models\n * Gemini uses aspect ratios, but we map common WIDTHxHEIGHT formats to aspect ratios\n * These are approximate mappings based on common image dimensions\n */\nexport const GEMINI_SIZE_TO_ASPECT_RATIO: Record<string, GeminiAspectRatio> = {\n // Square\n '1024x1024': '1:1',\n '512x512': '1:1',\n // Landscape\n '1024x768': '4:3',\n '1536x1024': '4:3',\n '1792x1024': '16:9',\n '1920x1080': '16:9',\n // Portrait\n '768x1024': '3:4',\n '1024x1536': '3:4', // Inverted\n '1024x1792': '9:16',\n '1080x1920': '9:16',\n}\n\n/**\n * Maps a WIDTHxHEIGHT size string to a Gemini aspect ratio\n * Returns undefined if the size cannot be mapped\n */\nexport function sizeToAspectRatio(\n size: string | undefined,\n): GeminiAspectRatio | undefined {\n if (!size) return undefined\n return GEMINI_SIZE_TO_ASPECT_RATIO[size]\n}\n\n/**\n * Validates that the provided size can be mapped to an aspect ratio\n * Throws an error if the size is invalid\n */\nexport function validateImageSize(\n model: string,\n size: string | undefined,\n): void {\n if (!size) return\n\n const aspectRatio = sizeToAspectRatio(size)\n if (!aspectRatio) {\n const validSizes = Object.keys(GEMINI_SIZE_TO_ASPECT_RATIO)\n throw new Error(\n `Invalid size \"${size}\" for model \"${model}\". ` +\n `Gemini Imagen uses aspect ratios. Valid sizes that map to aspect ratios: ${validSizes.join(', ')}. ` +\n `Alternatively, use providerOptions.aspectRatio directly with values: 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9`,\n )\n }\n}\n\n/**\n * Per-model caps on images per request.\n * The Imagen 4 family all support up to 4 images per request via the Gemini\n * API (the rumored 8-image tier is Vertex-only and isn't reachable through\n * @google/genai today). Unknown models fall through to the shared cap\n * defined below.\n *\n * @see https://ai.google.dev/gemini-api/docs/imagen\n */\nconst IMAGEN_MAX_IMAGES_BY_MODEL: Record<string, number> = {\n 'imagen-4.0-generate-001': 4,\n 'imagen-4.0-ultra-generate-001': 4,\n 'imagen-4.0-fast-generate-001': 4,\n}\n\nconst DEFAULT_IMAGEN_MAX_IMAGES = 4\n\n/**\n * Validates the number of images requested against the model's known cap.\n * Uses a per-model table where available and falls back to the shared\n * default otherwise — no more \"some support up to 8\" comments that don't\n * match the error message.\n */\nexport function validateNumberOfImages(\n model: string,\n numberOfImages: number | undefined,\n): void {\n if (numberOfImages === undefined) return\n\n const maxImages =\n IMAGEN_MAX_IMAGES_BY_MODEL[model] ?? DEFAULT_IMAGEN_MAX_IMAGES\n if (numberOfImages < 1 || numberOfImages > maxImages) {\n throw new Error(\n `Invalid numberOfImages \"${numberOfImages}\" for model \"${model}\". ` +\n `Must be between 1 and ${maxImages}.`,\n )\n }\n}\n\n/**\n * Validates the prompt is not empty\n */\nexport function validatePrompt(options: {\n prompt: string\n model: string\n}): void {\n const { prompt, model } = options\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(`Prompt cannot be empty for model \"${model}\".`)\n }\n}\n\n/**\n * Parses a Gemini native image size string into its components.\n * Format: \"aspectRatio_resolution\" e.g. \"16:9_4K\" → { aspectRatio: \"16:9\", resolution: \"4K\" }\n */\nexport function parseNativeImageSize(\n size: string,\n): { aspectRatio: string; resolution: string } | undefined {\n const match = size.match(/^(\\d+:\\d+)_(.+)$/)\n const [, aspectRatio, resolution] = match ?? []\n if (aspectRatio === undefined || resolution === undefined) return undefined\n return { aspectRatio, resolution }\n}\n"],"names":[],"mappings":"AAiNO,MAAM,8BAAiE;AAAA;AAAA,EAE5E,aAAa;AAAA,EACb,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAEb,YAAY;AAAA,EACZ,aAAa;AAAA;AAAA,EACb,aAAa;AAAA,EACb,aAAa;AACf;AAMO,SAAS,kBACd,MAC+B;AAC/B,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,4BAA4B,IAAI;AACzC;AAMO,SAAS,kBACd,OACA,MACM;AACN,MAAI,CAAC,KAAM;AAEX,QAAM,cAAc,kBAAkB,IAAI;AAC1C,MAAI,CAAC,aAAa;AAChB,UAAM,aAAa,OAAO,KAAK,2BAA2B;AAC1D,UAAM,IAAI;AAAA,MACR,iBAAiB,IAAI,gBAAgB,KAAK,+EACoC,WAAW,KAAK,IAAI,CAAC;AAAA,IAAA;AAAA,EAGvG;AACF;AAWA,MAAM,6BAAqD;AAAA,EACzD,2BAA2B;AAAA,EAC3B,iCAAiC;AAAA,EACjC,gCAAgC;AAClC;AAEA,MAAM,4BAA4B;AAQ3B,SAAS,uBACd,OACA,gBACM;AACN,MAAI,mBAAmB,OAAW;AAElC,QAAM,YACJ,2BAA2B,KAAK,KAAK;AACvC,MAAI,iBAAiB,KAAK,iBAAiB,WAAW;AACpD,UAAM,IAAI;AAAA,MACR,2BAA2B,cAAc,gBAAgB,KAAK,4BACnC,SAAS;AAAA,IAAA;AAAA,EAExC;AACF;AAKO,SAAS,eAAe,SAGtB;AACP,QAAM,EAAE,QAAQ,MAAA,IAAU;AAC1B,MAAI,CAAC,UAAU,OAAO,KAAA,EAAO,WAAW,GAAG;AACzC,UAAM,IAAI,MAAM,qCAAqC,KAAK,IAAI;AAAA,EAChE;AACF;AAMO,SAAS,qBACd,MACyD;AACzD,QAAM,QAAQ,KAAK,MAAM,kBAAkB;AAC3C,QAAM,GAAG,aAAa,UAAU,IAAI,SAAS,CAAA;AAC7C,MAAI,gBAAgB,UAAa,eAAe,OAAW,QAAO;AAClE,SAAO,EAAE,aAAa,WAAA;AACxB;"}
|
package/dist/esm/model-meta.d.ts
CHANGED
|
@@ -170,7 +170,7 @@ export declare const GEMINI_MODELS: readonly ["gemini-3.5-flash", "gemini-3.1-pr
|
|
|
170
170
|
export declare const GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS: Set<string>;
|
|
171
171
|
export type GeminiModels = (typeof GEMINI_MODELS)[number];
|
|
172
172
|
export type GeminiImageModels = (typeof GEMINI_IMAGE_MODELS)[number];
|
|
173
|
-
export declare const GEMINI_IMAGE_MODELS: readonly ["gemini-3.1-flash-image-preview", "gemini-3-
|
|
173
|
+
export declare const GEMINI_IMAGE_MODELS: readonly ["gemini-3.1-flash-image-preview", "gemini-3.1-flash-lite-image", "gemini-3-pro-image-preview", "gemini-2.5-flash-image", "imagen-4.0-generate-001", "imagen-4.0-fast-generate-001", "imagen-4.0-ultra-generate-001"];
|
|
174
174
|
/**
|
|
175
175
|
* Text-to-speech models
|
|
176
176
|
* @experimental Gemini TTS is an experimental feature and may change.
|
|
@@ -191,7 +191,7 @@ export type GeminiTTSVoice = (typeof GEMINI_TTS_VOICES)[number];
|
|
|
191
191
|
* Veo video generation models.
|
|
192
192
|
* @experimental Veo video generation is an experimental feature and may change.
|
|
193
193
|
*/
|
|
194
|
-
export declare const GEMINI_VIDEO_MODELS: readonly ["veo-3.1-generate-preview", "veo-3.1-fast-generate-preview", "veo-3.
|
|
194
|
+
export declare const GEMINI_VIDEO_MODELS: readonly ["veo-3.1-generate-preview", "veo-3.1-fast-generate-preview", "veo-3.1-lite-generate-preview"];
|
|
195
195
|
export type GeminiChatModelProviderOptionsByName = {
|
|
196
196
|
[GEMINI_3_1_PRO.name]: GeminiToolConfigOptions & GeminiSafetyOptions & GeminiCommonConfigOptions & GeminiCachedContentOptions & GeminiStructuredOutputOptions & GeminiThinkingOptions;
|
|
197
197
|
[GEMINI_3_FLASH.name]: GeminiToolConfigOptions & GeminiSafetyOptions & GeminiCommonConfigOptions & GeminiCachedContentOptions & GeminiStructuredOutputOptions & GeminiThinkingOptions;
|
package/dist/esm/model-meta.js
CHANGED
|
@@ -10,6 +10,9 @@ const GEMINI_3_PRO_IMAGE = {
|
|
|
10
10
|
const GEMINI_3_1_FLASH_IMAGE = {
|
|
11
11
|
name: "gemini-3.1-flash-image-preview"
|
|
12
12
|
};
|
|
13
|
+
const GEMINI_3_1_FLASH_LITE_IMAGE = {
|
|
14
|
+
name: "gemini-3.1-flash-lite-image"
|
|
15
|
+
};
|
|
13
16
|
const GEMINI_3_1_FLASH_LITE = {
|
|
14
17
|
name: "gemini-3.1-flash-lite"
|
|
15
18
|
};
|
|
@@ -52,23 +55,14 @@ const IMAGEN_4_GENERATE_ULTRA = {
|
|
|
52
55
|
const IMAGEN_4_GENERATE_FAST = {
|
|
53
56
|
name: "imagen-4.0-fast-generate-001"
|
|
54
57
|
};
|
|
55
|
-
const IMAGEN_3 = {
|
|
56
|
-
name: "imagen-3.0-generate-002"
|
|
57
|
-
};
|
|
58
58
|
const VEO_3_1_PREVIEW = {
|
|
59
59
|
name: "veo-3.1-generate-preview"
|
|
60
60
|
};
|
|
61
61
|
const VEO_3_1_FAST_PREVIEW = {
|
|
62
62
|
name: "veo-3.1-fast-generate-preview"
|
|
63
63
|
};
|
|
64
|
-
const
|
|
65
|
-
name: "veo-3.
|
|
66
|
-
};
|
|
67
|
-
const VEO_3_FAST = {
|
|
68
|
-
name: "veo-3.0-fast-generate-001"
|
|
69
|
-
};
|
|
70
|
-
const VEO_2 = {
|
|
71
|
-
name: "veo-2.0-generate-001"
|
|
64
|
+
const VEO_3_1_LITE_PREVIEW = {
|
|
65
|
+
name: "veo-3.1-lite-generate-preview"
|
|
72
66
|
};
|
|
73
67
|
const GEMINI_3_5_FLASH = {
|
|
74
68
|
name: "gemini-3.5-flash"
|
|
@@ -92,9 +86,9 @@ const GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS = /* @__PURE__ */ new Set([
|
|
|
92
86
|
]);
|
|
93
87
|
const GEMINI_IMAGE_MODELS = [
|
|
94
88
|
GEMINI_3_1_FLASH_IMAGE.name,
|
|
89
|
+
GEMINI_3_1_FLASH_LITE_IMAGE.name,
|
|
95
90
|
GEMINI_3_PRO_IMAGE.name,
|
|
96
91
|
GEMINI_2_5_FLASH_IMAGE.name,
|
|
97
|
-
IMAGEN_3.name,
|
|
98
92
|
IMAGEN_4_GENERATE.name,
|
|
99
93
|
IMAGEN_4_GENERATE_FAST.name,
|
|
100
94
|
IMAGEN_4_GENERATE_ULTRA.name
|
|
@@ -143,9 +137,7 @@ const GEMINI_TTS_VOICES = [
|
|
|
143
137
|
const GEMINI_VIDEO_MODELS = [
|
|
144
138
|
VEO_3_1_PREVIEW.name,
|
|
145
139
|
VEO_3_1_FAST_PREVIEW.name,
|
|
146
|
-
|
|
147
|
-
VEO_3_FAST.name,
|
|
148
|
-
VEO_2.name
|
|
140
|
+
VEO_3_1_LITE_PREVIEW.name
|
|
149
141
|
];
|
|
150
142
|
export {
|
|
151
143
|
GEMINI_AUDIO_MODELS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-meta.js","sources":["../../src/model-meta.ts"],"sourcesContent":["import type {\n GeminiCachedContentOptions,\n GeminiCommonConfigOptions,\n GeminiSafetyOptions,\n GeminiStructuredOutputOptions,\n GeminiThinkingOptions,\n GeminiToolConfigOptions,\n} from './text/text-provider-options'\n\ninterface ModelMeta<TProviderOptions = unknown> {\n name: string\n supports: {\n input: Array<'text' | 'image' | 'audio' | 'video' | 'document'>\n output: Array<'text' | 'image' | 'audio' | 'video'>\n capabilities?: Array<\n | 'audio_generation'\n | 'batch_api'\n | 'caching'\n | 'function_calling'\n | 'live_api'\n | 'structured_output'\n | 'thinking'\n >\n tools?: Array<\n | 'code_execution'\n | 'file_search'\n | 'google_search'\n | 'google_search_retrieval'\n | 'google_maps'\n | 'url_context'\n | 'computer_use'\n >\n }\n max_input_tokens?: number\n max_output_tokens?: number\n knowledge_cutoff?: string\n pricing?: {\n input: {\n normal: number\n cached?: number\n }\n output: {\n normal: number\n }\n }\n /**\n * Type-level description of which provider options this model supports.\n */\n providerOptions?: TProviderOptions\n}\n\nconst GEMINI_3_1_PRO = {\n name: 'gemini-3.1-pro-preview',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 2.5,\n },\n output: {\n normal: 15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_FLASH = {\n name: 'gemini-3-flash-preview',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 0.5,\n },\n output: {\n normal: 3,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_PRO_IMAGE = {\n name: 'gemini-3-pro-image-preview',\n max_input_tokens: 65_536,\n max_output_tokens: 32_768,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image'],\n output: ['text', 'image'],\n capabilities: ['batch_api', 'structured_output', 'thinking'],\n tools: ['google_search'],\n },\n pricing: {\n input: {\n normal: 2,\n },\n output: {\n normal: 0.134,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_1_FLASH_IMAGE = {\n name: 'gemini-3.1-flash-image-preview',\n max_input_tokens: 65_536,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image'],\n output: ['text', 'image'],\n capabilities: ['batch_api', 'structured_output', 'thinking'],\n tools: ['google_search'],\n },\n pricing: {\n input: {\n normal: 0.25,\n },\n output: {\n normal: 1.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_1_FLASH_LITE = {\n name: 'gemini-3.1-flash-lite',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 0.25,\n },\n output: {\n normal: 1.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_1_FLASH_LITE_PREVIEW = {\n name: 'gemini-3.1-flash-lite-preview',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 0.25,\n },\n output: {\n normal: 1.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_2_5_PRO = {\n name: 'gemini-2.5-pro',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: [\n 'code_execution',\n 'file_search',\n 'google_maps',\n 'google_search',\n 'url_context',\n ],\n },\n pricing: {\n input: {\n normal: 2.5,\n },\n output: {\n normal: 15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_2_5_PRO_TTS = {\n name: 'gemini-2.5-pro-preview-tts',\n max_input_tokens: 8_192,\n max_output_tokens: 16_384,\n knowledge_cutoff: '2025-05-01',\n supports: {\n input: ['text'],\n output: ['audio'],\n capabilities: ['audio_generation'],\n },\n pricing: {\n input: {\n normal: 2.5,\n },\n output: {\n normal: 15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst GEMINI_2_5_FLASH = {\n name: 'gemini-2.5-flash',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: [\n 'code_execution',\n 'file_search',\n 'google_maps',\n 'google_search',\n 'url_context',\n ],\n },\n pricing: {\n input: {\n normal: 1,\n },\n output: {\n normal: 2.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_2_5_FLASH_IMAGE = {\n name: 'gemini-2.5-flash-image',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-06-01',\n supports: {\n input: ['text', 'image'],\n output: ['text', 'image'],\n capabilities: ['batch_api', 'caching', 'structured_output'],\n tools: ['file_search'],\n },\n pricing: {\n input: {\n normal: 0.3,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n/**\nconst GEMINI_2_5_FLASH_LIVE = {\n name: 'gemini-2.5-flash-native-audio-preview-09-2025',\n max_input_tokens: 141_072,\n max_output_tokens: 8_192,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'audio', 'video'],\n output: ['text', 'audio'],\n capabilities: [\n 'audio_generation',\n 'file_search',\n 'function_calling',\n 'live_api',\n 'search_grounding',\n 'thinking',\n ],\n },\n pricing: {\n // todo find this info\n input: {\n normal: 0,\n },\n output: {\n normal: 0,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiGenerationConfigOptions &\n GeminiCachedContentOptions &\n GeminiThinkingOptions\n>\n*/\nconst GEMINI_2_5_FLASH_TTS = {\n name: 'gemini-2.5-flash-preview-tts',\n max_input_tokens: 8_192,\n max_output_tokens: 16_384,\n knowledge_cutoff: '2025-05-01',\n supports: {\n input: ['text'],\n output: ['audio'],\n capabilities: ['audio_generation', 'batch_api'],\n },\n pricing: {\n input: {\n normal: 1,\n },\n output: {\n normal: 2.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\n/**\n * Gemini 3.1 Flash TTS Preview - latest expressive TTS model with\n * 200+ audio tags, 70+ languages, and multi-speaker dialogue support.\n * @see https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-tts-preview\n */\nconst GEMINI_3_1_FLASH_TTS = {\n name: 'gemini-3.1-flash-tts-preview',\n max_input_tokens: 32_768,\n max_output_tokens: 16_384,\n knowledge_cutoff: '2025-05-01',\n supports: {\n input: ['text'],\n output: ['audio'],\n capabilities: ['audio_generation', 'batch_api'],\n },\n pricing: {\n input: {\n normal: 0.5,\n },\n output: {\n normal: 10,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\n/**\n * Lyria 3 Pro Preview — Google's flagship music generation model.\n * Generates full-length songs with multiple verses, choruses, and bridges.\n * Outputs MP3 or WAV at 48 kHz stereo.\n * @see https://ai.google.dev/gemini-api/docs/models/lyria-3-pro-preview\n */\nconst LYRIA_3_PRO = {\n name: 'lyria-3-pro-preview',\n max_input_tokens: 131_072,\n supports: {\n input: ['text', 'image'],\n output: ['audio'],\n capabilities: ['audio_generation'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0,\n },\n },\n} as const satisfies ModelMeta\n\n/**\n * Lyria 3 Clip Preview — 30-second music clips in MP3 format.\n * @see https://ai.google.dev/gemini-api/docs/music-generation\n */\nconst LYRIA_3_CLIP = {\n name: 'lyria-3-clip-preview',\n max_input_tokens: 131_072,\n supports: {\n input: ['text', 'image'],\n output: ['audio'],\n capabilities: ['audio_generation'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0,\n },\n },\n} as const satisfies ModelMeta\n\nconst GEMINI_2_5_FLASH_LITE = {\n name: 'gemini-2.5-flash-lite',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'google_maps', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 0.1,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst IMAGEN_4_GENERATE = {\n name: 'imagen-4.0-generate-001',\n max_input_tokens: 480,\n max_output_tokens: 4,\n supports: {\n input: ['text'],\n output: ['image'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst IMAGEN_4_GENERATE_ULTRA = {\n name: 'imagen-4.0-ultra-generate-001',\n max_input_tokens: 480,\n max_output_tokens: 4,\n supports: {\n input: ['text'],\n output: ['image'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.6,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst IMAGEN_4_GENERATE_FAST = {\n name: 'imagen-4.0-fast-generate-001',\n max_input_tokens: 480,\n max_output_tokens: 4,\n supports: {\n input: ['text'],\n output: ['image'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.2,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst IMAGEN_3 = {\n name: 'imagen-3.0-generate-002',\n max_output_tokens: 4,\n supports: {\n input: ['text'],\n output: ['image'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.03,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n/**\n * Veo video generation models. Pricing is per second of generated video\n * (audio+video rate where the model supports audio).\n * @experimental Veo video generation is an experimental feature and may change.\n */\nconst VEO_3_1_PREVIEW = {\n name: 'veo-3.1-generate-preview',\n max_input_tokens: 1024,\n max_output_tokens: 1,\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst VEO_3_1_FAST_PREVIEW = {\n name: 'veo-3.1-fast-generate-preview',\n max_input_tokens: 1024,\n max_output_tokens: 1,\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst VEO_3 = {\n name: 'veo-3.0-generate-001',\n max_input_tokens: 1024,\n max_output_tokens: 1,\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst VEO_3_FAST = {\n name: 'veo-3.0-fast-generate-001',\n max_input_tokens: 1024,\n max_output_tokens: 1,\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst VEO_2 = {\n name: 'veo-2.0-generate-001',\n max_output_tokens: 2,\n supports: {\n input: ['text', 'image'],\n output: ['video'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.35,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst GEMINI_3_5_FLASH = {\n name: 'gemini-3.5-flash',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n supports: {\n input: ['text', 'image', 'video', 'document', 'audio'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 1.5,\n cached: 0.15,\n },\n output: {\n normal: 9,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nexport const GEMINI_MODELS = [\n GEMINI_3_5_FLASH.name,\n GEMINI_3_1_PRO.name,\n GEMINI_3_FLASH.name,\n GEMINI_3_1_FLASH_LITE.name,\n GEMINI_3_1_FLASH_LITE_PREVIEW.name,\n GEMINI_2_5_PRO.name,\n GEMINI_2_5_FLASH.name,\n GEMINI_2_5_FLASH_LITE.name,\n] as const\n\n/**\n * Gemini models that support combining `tools` + `responseSchema` in a\n * single streaming `generateContent` call (per issue #605). Per the\n * provider matrix, Gemini 3.x natively interleaves the schema-constrained\n * answer with function-calling on one pass; Gemini 2.x is unsupported /\n * brittle and keeps the engine's legacy finalization fallback.\n */\nexport const GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS = new Set<string>([\n GEMINI_3_1_PRO.name,\n GEMINI_3_FLASH.name,\n GEMINI_3_1_FLASH_LITE.name,\n GEMINI_3_1_FLASH_LITE_PREVIEW.name,\n GEMINI_3_5_FLASH.name,\n])\n\nexport type GeminiModels = (typeof GEMINI_MODELS)[number]\n\nexport type GeminiImageModels = (typeof GEMINI_IMAGE_MODELS)[number]\n\nexport const GEMINI_IMAGE_MODELS = [\n GEMINI_3_1_FLASH_IMAGE.name,\n GEMINI_3_PRO_IMAGE.name,\n GEMINI_2_5_FLASH_IMAGE.name,\n IMAGEN_3.name,\n IMAGEN_4_GENERATE.name,\n IMAGEN_4_GENERATE_FAST.name,\n IMAGEN_4_GENERATE_ULTRA.name,\n] as const\n\n/**\n * Text-to-speech models\n * @experimental Gemini TTS is an experimental feature and may change.\n */\nexport const GEMINI_TTS_MODELS = [\n GEMINI_3_1_FLASH_TTS.name,\n GEMINI_2_5_FLASH_TTS.name,\n GEMINI_2_5_PRO_TTS.name,\n] as const\n\n/**\n * Audio generation models (Lyria music generation).\n * @experimental Lyria music generation is an experimental feature and may change.\n */\nexport const GEMINI_AUDIO_MODELS = [\n LYRIA_3_PRO.name,\n LYRIA_3_CLIP.name,\n] as const\n\n/**\n * Available voice names for Gemini TTS\n * @see https://ai.google.dev/gemini-api/docs/speech-generation\n */\nexport const GEMINI_TTS_VOICES = [\n 'Zephyr',\n 'Puck',\n 'Charon',\n 'Kore',\n 'Fenrir',\n 'Leda',\n 'Orus',\n 'Aoede',\n 'Callirrhoe',\n 'Autonoe',\n 'Enceladus',\n 'Iapetus',\n 'Umbriel',\n 'Algieba',\n 'Despina',\n 'Erinome',\n 'Algenib',\n 'Rasalgethi',\n 'Laomedeia',\n 'Achernar',\n 'Alnilam',\n 'Schedar',\n 'Gacrux',\n 'Pulcherrima',\n 'Achird',\n 'Zubenelgenubi',\n 'Vindemiatrix',\n 'Sadachbia',\n 'Sadaltager',\n 'Sulafat',\n] as const\n\nexport type GeminiTTSVoice = (typeof GEMINI_TTS_VOICES)[number]\n\n/**\n * Veo video generation models.\n * @experimental Veo video generation is an experimental feature and may change.\n */\nexport const GEMINI_VIDEO_MODELS = [\n VEO_3_1_PREVIEW.name,\n VEO_3_1_FAST_PREVIEW.name,\n VEO_3.name,\n VEO_3_FAST.name,\n VEO_2.name,\n] as const\n\n// Manual type map for per-model provider options\nexport type GeminiChatModelProviderOptionsByName = {\n // Models with thinking and structured output support\n [GEMINI_3_1_PRO.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_3_FLASH.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_3_1_FLASH_LITE.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_3_1_FLASH_LITE_PREVIEW.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_2_5_PRO.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_2_5_FLASH.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_2_5_FLASH_LITE.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_3_5_FLASH.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n}\n\n/**\n * Type-only map from chat model name to its supported tool capabilities.\n * Based on the 'supports.tools' arrays defined for each model.\n */\nexport type GeminiChatModelToolCapabilitiesByName = {\n [GEMINI_3_1_PRO.name]: typeof GEMINI_3_1_PRO.supports.tools\n [GEMINI_3_FLASH.name]: typeof GEMINI_3_FLASH.supports.tools\n [GEMINI_3_1_FLASH_LITE.name]: typeof GEMINI_3_1_FLASH_LITE.supports.tools\n [GEMINI_3_1_FLASH_LITE_PREVIEW.name]: typeof GEMINI_3_1_FLASH_LITE_PREVIEW.supports.tools\n [GEMINI_2_5_PRO.name]: typeof GEMINI_2_5_PRO.supports.tools\n [GEMINI_2_5_FLASH.name]: typeof GEMINI_2_5_FLASH.supports.tools\n [GEMINI_2_5_FLASH_LITE.name]: typeof GEMINI_2_5_FLASH_LITE.supports.tools\n [GEMINI_3_5_FLASH.name]: typeof GEMINI_3_5_FLASH.supports.tools\n}\n\n/**\n * Type-only map from chat model name to its supported input modalities.\n * Based on the 'supports.input' arrays defined for each model.\n * Note: 'document' in the model meta is mapped to 'document' modality.\n * Used by the core AI types to constrain ContentPart types based on the selected model.\n * Note: These must be inlined as readonly arrays (not typeof) because the model\n * constants are not exported and typeof references don't work in .d.ts files\n * when consumed by external packages.\n *\n * @see https://ai.google.dev/gemini-api/docs/vision\n * @see https://ai.google.dev/gemini-api/docs/audio\n * @see https://ai.google.dev/gemini-api/docs/document-processing\n */\nexport type GeminiModelInputModalitiesByName = {\n // Models with full multimodal support (text, image, audio, video, document)\n [GEMINI_3_1_PRO.name]: typeof GEMINI_3_1_PRO.supports.input\n [GEMINI_3_FLASH.name]: typeof GEMINI_3_FLASH.supports.input\n [GEMINI_3_1_FLASH_LITE.name]: typeof GEMINI_3_1_FLASH_LITE.supports.input\n [GEMINI_3_1_FLASH_LITE_PREVIEW.name]: typeof GEMINI_3_1_FLASH_LITE_PREVIEW.supports.input\n [GEMINI_2_5_PRO.name]: typeof GEMINI_2_5_PRO.supports.input\n [GEMINI_2_5_FLASH_LITE.name]: typeof GEMINI_2_5_FLASH_LITE.supports.input\n [GEMINI_3_5_FLASH.name]: typeof GEMINI_3_5_FLASH.supports.input\n\n // Models with text, image, audio, video (no document)\n [GEMINI_2_5_FLASH.name]: typeof GEMINI_2_5_FLASH.supports.input\n}\n"],"names":[],"mappings":"AAmDA,MAAM,iBAAiB;AAAA,EACrB,MAAM;AAwBR;AASA,MAAM,iBAAiB;AAAA,EACrB,MAAM;AAwBR;AASA,MAAM,qBAAqB;AAAA,EACzB,MAAM;AAkBR;AASA,MAAM,yBAAyB;AAAA,EAC7B,MAAM;AAkBR;AASA,MAAM,wBAAwB;AAAA,EAC5B,MAAM;AAwBR;AASA,MAAM,gCAAgC;AAAA,EACpC,MAAM;AAwBR;AASA,MAAM,iBAAiB;AAAA,EACrB,MAAM;AA8BR;AASA,MAAM,qBAAqB;AAAA,EACzB,MAAM;AAiBR;AAOA,MAAM,mBAAmB;AAAA,EACvB,MAAM;AA8BR;AASA,MAAM,yBAAyB;AAAA,EAC7B,MAAM;AAkBR;AAyCA,MAAM,uBAAuB;AAAA,EAC3B,MAAM;AAiBR;AAYA,MAAM,uBAAuB;AAAA,EAC3B,MAAM;AAiBR;AAaA,MAAM,cAAc;AAAA,EAClB,MAAM;AAeR;AAMA,MAAM,eAAe;AAAA,EACnB,MAAM;AAeR;AAEA,MAAM,wBAAwB;AAAA,EAC5B,MAAM;AAwBR;AASA,MAAM,oBAAoB;AAAA,EACxB,MAAM;AAeR;AAOA,MAAM,0BAA0B;AAAA,EAC9B,MAAM;AAeR;AAOA,MAAM,yBAAyB;AAAA,EAC7B,MAAM;AAeR;AAOA,MAAM,WAAW;AAAA,EACf,MAAM;AAcR;AAWA,MAAM,kBAAkB;AAAA,EACtB,MAAM;AAeR;AAOA,MAAM,uBAAuB;AAAA,EAC3B,MAAM;AAeR;AAOA,MAAM,QAAQ;AAAA,EACZ,MAAM;AAeR;AAOA,MAAM,aAAa;AAAA,EACjB,MAAM;AAeR;AAOA,MAAM,QAAQ;AAAA,EACZ,MAAM;AAcR;AAOA,MAAM,mBAAmB;AAAA,EACvB,MAAM;AAwBR;AASO,MAAM,gBAAgB;AAAA,EAC3B,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,sBAAsB;AAAA,EACtB,8BAA8B;AAAA,EAC9B,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,sBAAsB;AACxB;AASO,MAAM,8DAA8C,IAAY;AAAA,EACrE,eAAe;AAAA,EACf,eAAe;AAAA,EACf,sBAAsB;AAAA,EACtB,8BAA8B;AAAA,EAC9B,iBAAiB;AACnB,CAAC;AAMM,MAAM,sBAAsB;AAAA,EACjC,uBAAuB;AAAA,EACvB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,wBAAwB;AAC1B;AAMO,MAAM,oBAAoB;AAAA,EAC/B,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,mBAAmB;AACrB;AAMO,MAAM,sBAAsB;AAAA,EACjC,YAAY;AAAA,EACZ,aAAa;AACf;AAMO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,MAAM,sBAAsB;AAAA,EACjC,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,MAAM;AAAA,EACN,WAAW;AAAA,EACX,MAAM;AACR;"}
|
|
1
|
+
{"version":3,"file":"model-meta.js","sources":["../../src/model-meta.ts"],"sourcesContent":["import type {\n GeminiCachedContentOptions,\n GeminiCommonConfigOptions,\n GeminiSafetyOptions,\n GeminiStructuredOutputOptions,\n GeminiThinkingOptions,\n GeminiToolConfigOptions,\n} from './text/text-provider-options'\n\ninterface ModelMeta<TProviderOptions = unknown> {\n name: string\n supports: {\n input: Array<'text' | 'image' | 'audio' | 'video' | 'document'>\n output: Array<'text' | 'image' | 'audio' | 'video'>\n capabilities?: Array<\n | 'audio_generation'\n | 'batch_api'\n | 'caching'\n | 'function_calling'\n | 'live_api'\n | 'structured_output'\n | 'thinking'\n >\n tools?: Array<\n | 'code_execution'\n | 'file_search'\n | 'google_search'\n | 'google_search_retrieval'\n | 'google_maps'\n | 'url_context'\n | 'computer_use'\n >\n }\n max_input_tokens?: number\n max_output_tokens?: number\n knowledge_cutoff?: string\n pricing?: {\n input: {\n normal: number\n cached?: number\n }\n output: {\n normal: number\n }\n }\n /**\n * Type-level description of which provider options this model supports.\n */\n providerOptions?: TProviderOptions\n}\n\nconst GEMINI_3_1_PRO = {\n name: 'gemini-3.1-pro-preview',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 2.5,\n },\n output: {\n normal: 15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_FLASH = {\n name: 'gemini-3-flash-preview',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 0.5,\n },\n output: {\n normal: 3,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_PRO_IMAGE = {\n name: 'gemini-3-pro-image-preview',\n max_input_tokens: 65_536,\n max_output_tokens: 32_768,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image'],\n output: ['text', 'image'],\n capabilities: ['batch_api', 'structured_output', 'thinking'],\n tools: ['google_search'],\n },\n pricing: {\n input: {\n normal: 2,\n },\n output: {\n normal: 0.134,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_1_FLASH_IMAGE = {\n name: 'gemini-3.1-flash-image-preview',\n max_input_tokens: 65_536,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image'],\n output: ['text', 'image'],\n capabilities: ['batch_api', 'structured_output', 'thinking'],\n tools: ['google_search'],\n },\n pricing: {\n input: {\n normal: 0.25,\n },\n output: {\n normal: 1.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_1_FLASH_LITE_IMAGE = {\n name: 'gemini-3.1-flash-lite-image',\n max_input_tokens: 65_536,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image'],\n output: ['text', 'image'],\n capabilities: ['batch_api', 'structured_output', 'thinking'],\n tools: ['google_search'],\n },\n pricing: {\n input: {\n normal: 0.25,\n },\n output: {\n normal: 1.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_1_FLASH_LITE = {\n name: 'gemini-3.1-flash-lite',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 0.25,\n },\n output: {\n normal: 1.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_3_1_FLASH_LITE_PREVIEW = {\n name: 'gemini-3.1-flash-lite-preview',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 0.25,\n },\n output: {\n normal: 1.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_2_5_PRO = {\n name: 'gemini-2.5-pro',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: [\n 'code_execution',\n 'file_search',\n 'google_maps',\n 'google_search',\n 'url_context',\n ],\n },\n pricing: {\n input: {\n normal: 2.5,\n },\n output: {\n normal: 15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_2_5_PRO_TTS = {\n name: 'gemini-2.5-pro-preview-tts',\n max_input_tokens: 8_192,\n max_output_tokens: 16_384,\n knowledge_cutoff: '2025-05-01',\n supports: {\n input: ['text'],\n output: ['audio'],\n capabilities: ['audio_generation'],\n },\n pricing: {\n input: {\n normal: 2.5,\n },\n output: {\n normal: 15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst GEMINI_2_5_FLASH = {\n name: 'gemini-2.5-flash',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: [\n 'code_execution',\n 'file_search',\n 'google_maps',\n 'google_search',\n 'url_context',\n ],\n },\n pricing: {\n input: {\n normal: 1,\n },\n output: {\n normal: 2.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst GEMINI_2_5_FLASH_IMAGE = {\n name: 'gemini-2.5-flash-image',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-06-01',\n supports: {\n input: ['text', 'image'],\n output: ['text', 'image'],\n capabilities: ['batch_api', 'caching', 'structured_output'],\n tools: ['file_search'],\n },\n pricing: {\n input: {\n normal: 0.3,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n/**\nconst GEMINI_2_5_FLASH_LIVE = {\n name: 'gemini-2.5-flash-native-audio-preview-09-2025',\n max_input_tokens: 141_072,\n max_output_tokens: 8_192,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'audio', 'video'],\n output: ['text', 'audio'],\n capabilities: [\n 'audio_generation',\n 'file_search',\n 'function_calling',\n 'live_api',\n 'search_grounding',\n 'thinking',\n ],\n },\n pricing: {\n // todo find this info\n input: {\n normal: 0,\n },\n output: {\n normal: 0,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiGenerationConfigOptions &\n GeminiCachedContentOptions &\n GeminiThinkingOptions\n>\n*/\nconst GEMINI_2_5_FLASH_TTS = {\n name: 'gemini-2.5-flash-preview-tts',\n max_input_tokens: 8_192,\n max_output_tokens: 16_384,\n knowledge_cutoff: '2025-05-01',\n supports: {\n input: ['text'],\n output: ['audio'],\n capabilities: ['audio_generation', 'batch_api'],\n },\n pricing: {\n input: {\n normal: 1,\n },\n output: {\n normal: 2.5,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\n/**\n * Gemini 3.1 Flash TTS Preview - latest expressive TTS model with\n * 200+ audio tags, 70+ languages, and multi-speaker dialogue support.\n * @see https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-tts-preview\n */\nconst GEMINI_3_1_FLASH_TTS = {\n name: 'gemini-3.1-flash-tts-preview',\n max_input_tokens: 32_768,\n max_output_tokens: 16_384,\n knowledge_cutoff: '2025-05-01',\n supports: {\n input: ['text'],\n output: ['audio'],\n capabilities: ['audio_generation', 'batch_api'],\n },\n pricing: {\n input: {\n normal: 0.5,\n },\n output: {\n normal: 10,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\n/**\n * Lyria 3 Pro Preview — Google's flagship music generation model.\n * Generates full-length songs with multiple verses, choruses, and bridges.\n * Outputs MP3 or WAV at 48 kHz stereo.\n * @see https://ai.google.dev/gemini-api/docs/models/lyria-3-pro-preview\n */\nconst LYRIA_3_PRO = {\n name: 'lyria-3-pro-preview',\n max_input_tokens: 131_072,\n supports: {\n input: ['text', 'image'],\n output: ['audio'],\n capabilities: ['audio_generation'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0,\n },\n },\n} as const satisfies ModelMeta\n\n/**\n * Lyria 3 Clip Preview — 30-second music clips in MP3 format.\n * @see https://ai.google.dev/gemini-api/docs/music-generation\n */\nconst LYRIA_3_CLIP = {\n name: 'lyria-3-clip-preview',\n max_input_tokens: 131_072,\n supports: {\n input: ['text', 'image'],\n output: ['audio'],\n capabilities: ['audio_generation'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0,\n },\n },\n} as const satisfies ModelMeta\n\nconst GEMINI_2_5_FLASH_LITE = {\n name: 'gemini-2.5-flash-lite',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n knowledge_cutoff: '2025-01-01',\n supports: {\n input: ['text', 'image', 'audio', 'video', 'document'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'google_maps', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 0.1,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nconst IMAGEN_4_GENERATE = {\n name: 'imagen-4.0-generate-001',\n max_input_tokens: 480,\n max_output_tokens: 4,\n supports: {\n input: ['text'],\n output: ['image'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst IMAGEN_4_GENERATE_ULTRA = {\n name: 'imagen-4.0-ultra-generate-001',\n max_input_tokens: 480,\n max_output_tokens: 4,\n supports: {\n input: ['text'],\n output: ['image'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.6,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst IMAGEN_4_GENERATE_FAST = {\n name: 'imagen-4.0-fast-generate-001',\n max_input_tokens: 480,\n max_output_tokens: 4,\n supports: {\n input: ['text'],\n output: ['image'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.2,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\n/**\n * Veo video generation models. Pricing is per second of generated video\n * (audio+video rate where the model supports audio).\n * @experimental Veo video generation is an experimental feature and may change.\n */\nconst VEO_3_1_PREVIEW = {\n name: 'veo-3.1-generate-preview',\n max_input_tokens: 1024,\n max_output_tokens: 1,\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.4,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst VEO_3_1_FAST_PREVIEW = {\n name: 'veo-3.1-fast-generate-preview',\n max_input_tokens: 1024,\n max_output_tokens: 1,\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.15,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst VEO_3_1_LITE_PREVIEW = {\n name: 'veo-3.1-lite-generate-preview',\n max_input_tokens: 1024,\n max_output_tokens: 1,\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n pricing: {\n input: {\n normal: 0,\n },\n output: {\n normal: 0.05,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions\n>\n\nconst GEMINI_3_5_FLASH = {\n name: 'gemini-3.5-flash',\n max_input_tokens: 1_048_576,\n max_output_tokens: 65_536,\n supports: {\n input: ['text', 'image', 'video', 'document', 'audio'],\n output: ['text'],\n capabilities: [\n 'batch_api',\n 'caching',\n 'function_calling',\n 'structured_output',\n 'thinking',\n ],\n tools: ['code_execution', 'file_search', 'google_search', 'url_context'],\n },\n pricing: {\n input: {\n normal: 1.5,\n cached: 0.15,\n },\n output: {\n normal: 9,\n },\n },\n} as const satisfies ModelMeta<\n GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n>\n\nexport const GEMINI_MODELS = [\n GEMINI_3_5_FLASH.name,\n GEMINI_3_1_PRO.name,\n GEMINI_3_FLASH.name,\n GEMINI_3_1_FLASH_LITE.name,\n GEMINI_3_1_FLASH_LITE_PREVIEW.name,\n GEMINI_2_5_PRO.name,\n GEMINI_2_5_FLASH.name,\n GEMINI_2_5_FLASH_LITE.name,\n] as const\n\n/**\n * Gemini models that support combining `tools` + `responseSchema` in a\n * single streaming `generateContent` call (per issue #605). Per the\n * provider matrix, Gemini 3.x natively interleaves the schema-constrained\n * answer with function-calling on one pass; Gemini 2.x is unsupported /\n * brittle and keeps the engine's legacy finalization fallback.\n */\nexport const GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS = new Set<string>([\n GEMINI_3_1_PRO.name,\n GEMINI_3_FLASH.name,\n GEMINI_3_1_FLASH_LITE.name,\n GEMINI_3_1_FLASH_LITE_PREVIEW.name,\n GEMINI_3_5_FLASH.name,\n])\n\nexport type GeminiModels = (typeof GEMINI_MODELS)[number]\n\nexport type GeminiImageModels = (typeof GEMINI_IMAGE_MODELS)[number]\n\nexport const GEMINI_IMAGE_MODELS = [\n GEMINI_3_1_FLASH_IMAGE.name,\n GEMINI_3_1_FLASH_LITE_IMAGE.name,\n GEMINI_3_PRO_IMAGE.name,\n GEMINI_2_5_FLASH_IMAGE.name,\n IMAGEN_4_GENERATE.name,\n IMAGEN_4_GENERATE_FAST.name,\n IMAGEN_4_GENERATE_ULTRA.name,\n] as const\n\n/**\n * Text-to-speech models\n * @experimental Gemini TTS is an experimental feature and may change.\n */\nexport const GEMINI_TTS_MODELS = [\n GEMINI_3_1_FLASH_TTS.name,\n GEMINI_2_5_FLASH_TTS.name,\n GEMINI_2_5_PRO_TTS.name,\n] as const\n\n/**\n * Audio generation models (Lyria music generation).\n * @experimental Lyria music generation is an experimental feature and may change.\n */\nexport const GEMINI_AUDIO_MODELS = [\n LYRIA_3_PRO.name,\n LYRIA_3_CLIP.name,\n] as const\n\n/**\n * Available voice names for Gemini TTS\n * @see https://ai.google.dev/gemini-api/docs/speech-generation\n */\nexport const GEMINI_TTS_VOICES = [\n 'Zephyr',\n 'Puck',\n 'Charon',\n 'Kore',\n 'Fenrir',\n 'Leda',\n 'Orus',\n 'Aoede',\n 'Callirrhoe',\n 'Autonoe',\n 'Enceladus',\n 'Iapetus',\n 'Umbriel',\n 'Algieba',\n 'Despina',\n 'Erinome',\n 'Algenib',\n 'Rasalgethi',\n 'Laomedeia',\n 'Achernar',\n 'Alnilam',\n 'Schedar',\n 'Gacrux',\n 'Pulcherrima',\n 'Achird',\n 'Zubenelgenubi',\n 'Vindemiatrix',\n 'Sadachbia',\n 'Sadaltager',\n 'Sulafat',\n] as const\n\nexport type GeminiTTSVoice = (typeof GEMINI_TTS_VOICES)[number]\n\n/**\n * Veo video generation models.\n * @experimental Veo video generation is an experimental feature and may change.\n */\nexport const GEMINI_VIDEO_MODELS = [\n VEO_3_1_PREVIEW.name,\n VEO_3_1_FAST_PREVIEW.name,\n VEO_3_1_LITE_PREVIEW.name,\n] as const\n\n// Manual type map for per-model provider options\nexport type GeminiChatModelProviderOptionsByName = {\n // Models with thinking and structured output support\n [GEMINI_3_1_PRO.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_3_FLASH.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_3_1_FLASH_LITE.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_3_1_FLASH_LITE_PREVIEW.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_2_5_PRO.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_2_5_FLASH.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_2_5_FLASH_LITE.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n [GEMINI_3_5_FLASH.name]: GeminiToolConfigOptions &\n GeminiSafetyOptions &\n GeminiCommonConfigOptions &\n GeminiCachedContentOptions &\n GeminiStructuredOutputOptions &\n GeminiThinkingOptions\n}\n\n/**\n * Type-only map from chat model name to its supported tool capabilities.\n * Based on the 'supports.tools' arrays defined for each model.\n */\nexport type GeminiChatModelToolCapabilitiesByName = {\n [GEMINI_3_1_PRO.name]: typeof GEMINI_3_1_PRO.supports.tools\n [GEMINI_3_FLASH.name]: typeof GEMINI_3_FLASH.supports.tools\n [GEMINI_3_1_FLASH_LITE.name]: typeof GEMINI_3_1_FLASH_LITE.supports.tools\n [GEMINI_3_1_FLASH_LITE_PREVIEW.name]: typeof GEMINI_3_1_FLASH_LITE_PREVIEW.supports.tools\n [GEMINI_2_5_PRO.name]: typeof GEMINI_2_5_PRO.supports.tools\n [GEMINI_2_5_FLASH.name]: typeof GEMINI_2_5_FLASH.supports.tools\n [GEMINI_2_5_FLASH_LITE.name]: typeof GEMINI_2_5_FLASH_LITE.supports.tools\n [GEMINI_3_5_FLASH.name]: typeof GEMINI_3_5_FLASH.supports.tools\n}\n\n/**\n * Type-only map from chat model name to its supported input modalities.\n * Based on the 'supports.input' arrays defined for each model.\n * Note: 'document' in the model meta is mapped to 'document' modality.\n * Used by the core AI types to constrain ContentPart types based on the selected model.\n * Note: These must be inlined as readonly arrays (not typeof) because the model\n * constants are not exported and typeof references don't work in .d.ts files\n * when consumed by external packages.\n *\n * @see https://ai.google.dev/gemini-api/docs/vision\n * @see https://ai.google.dev/gemini-api/docs/audio\n * @see https://ai.google.dev/gemini-api/docs/document-processing\n */\nexport type GeminiModelInputModalitiesByName = {\n // Models with full multimodal support (text, image, audio, video, document)\n [GEMINI_3_1_PRO.name]: typeof GEMINI_3_1_PRO.supports.input\n [GEMINI_3_FLASH.name]: typeof GEMINI_3_FLASH.supports.input\n [GEMINI_3_1_FLASH_LITE.name]: typeof GEMINI_3_1_FLASH_LITE.supports.input\n [GEMINI_3_1_FLASH_LITE_PREVIEW.name]: typeof GEMINI_3_1_FLASH_LITE_PREVIEW.supports.input\n [GEMINI_2_5_PRO.name]: typeof GEMINI_2_5_PRO.supports.input\n [GEMINI_2_5_FLASH_LITE.name]: typeof GEMINI_2_5_FLASH_LITE.supports.input\n [GEMINI_3_5_FLASH.name]: typeof GEMINI_3_5_FLASH.supports.input\n\n // Models with text, image, audio, video (no document)\n [GEMINI_2_5_FLASH.name]: typeof GEMINI_2_5_FLASH.supports.input\n}\n"],"names":[],"mappings":"AAmDA,MAAM,iBAAiB;AAAA,EACrB,MAAM;AAwBR;AASA,MAAM,iBAAiB;AAAA,EACrB,MAAM;AAwBR;AASA,MAAM,qBAAqB;AAAA,EACzB,MAAM;AAkBR;AASA,MAAM,yBAAyB;AAAA,EAC7B,MAAM;AAkBR;AASA,MAAM,8BAA8B;AAAA,EAClC,MAAM;AAkBR;AASA,MAAM,wBAAwB;AAAA,EAC5B,MAAM;AAwBR;AASA,MAAM,gCAAgC;AAAA,EACpC,MAAM;AAwBR;AASA,MAAM,iBAAiB;AAAA,EACrB,MAAM;AA8BR;AASA,MAAM,qBAAqB;AAAA,EACzB,MAAM;AAiBR;AAOA,MAAM,mBAAmB;AAAA,EACvB,MAAM;AA8BR;AASA,MAAM,yBAAyB;AAAA,EAC7B,MAAM;AAkBR;AAyCA,MAAM,uBAAuB;AAAA,EAC3B,MAAM;AAiBR;AAYA,MAAM,uBAAuB;AAAA,EAC3B,MAAM;AAiBR;AAaA,MAAM,cAAc;AAAA,EAClB,MAAM;AAeR;AAMA,MAAM,eAAe;AAAA,EACnB,MAAM;AAeR;AAEA,MAAM,wBAAwB;AAAA,EAC5B,MAAM;AAwBR;AASA,MAAM,oBAAoB;AAAA,EACxB,MAAM;AAeR;AAOA,MAAM,0BAA0B;AAAA,EAC9B,MAAM;AAeR;AAOA,MAAM,yBAAyB;AAAA,EAC7B,MAAM;AAeR;AAYA,MAAM,kBAAkB;AAAA,EACtB,MAAM;AAeR;AAOA,MAAM,uBAAuB;AAAA,EAC3B,MAAM;AAeR;AAOA,MAAM,uBAAuB;AAAA,EAC3B,MAAM;AAeR;AAOA,MAAM,mBAAmB;AAAA,EACvB,MAAM;AAwBR;AASO,MAAM,gBAAgB;AAAA,EAC3B,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,sBAAsB;AAAA,EACtB,8BAA8B;AAAA,EAC9B,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,sBAAsB;AACxB;AASO,MAAM,8DAA8C,IAAY;AAAA,EACrE,eAAe;AAAA,EACf,eAAe;AAAA,EACf,sBAAsB;AAAA,EACtB,8BAA8B;AAAA,EAC9B,iBAAiB;AACnB,CAAC;AAMM,MAAM,sBAAsB;AAAA,EACjC,uBAAuB;AAAA,EACvB,4BAA4B;AAAA,EAC5B,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,wBAAwB;AAC1B;AAMO,MAAM,oBAAoB;AAAA,EAC/B,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,mBAAmB;AACrB;AAMO,MAAM,sBAAsB;AAAA,EACjC,YAAY;AAAA,EACZ,aAAa;AACf;AAMO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,MAAM,sBAAsB;AAAA,EACjC,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,qBAAqB;AACvB;"}
|
|
@@ -65,9 +65,7 @@ export type GeminiVideoModelInputModalitiesByName = {
|
|
|
65
65
|
export type GeminiVideoModelDurationByName = {
|
|
66
66
|
'veo-3.1-generate-preview': 4 | 6 | 8;
|
|
67
67
|
'veo-3.1-fast-generate-preview': 4 | 6 | 8;
|
|
68
|
-
'veo-3.
|
|
69
|
-
'veo-3.0-fast-generate-001': 4 | 6 | 8;
|
|
70
|
-
'veo-2.0-generate-001': 5 | 6 | 8;
|
|
68
|
+
'veo-3.1-lite-generate-preview': 4 | 6 | 8;
|
|
71
69
|
};
|
|
72
70
|
/**
|
|
73
71
|
* Runtime duration table backing `availableDurations()` / `snapDuration()`.
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
const GEMINI_VIDEO_DURATIONS = {
|
|
2
2
|
"veo-3.1-generate-preview": { kind: "discrete", values: [4, 6, 8] },
|
|
3
3
|
"veo-3.1-fast-generate-preview": { kind: "discrete", values: [4, 6, 8] },
|
|
4
|
-
"veo-3.
|
|
5
|
-
"veo-3.0-fast-generate-001": { kind: "discrete", values: [4, 6, 8] },
|
|
6
|
-
"veo-2.0-generate-001": { kind: "discrete", values: [5, 6, 8] }
|
|
4
|
+
"veo-3.1-lite-generate-preview": { kind: "discrete", values: [4, 6, 8] }
|
|
7
5
|
};
|
|
8
6
|
function getGeminiVideoDurationOptions(model) {
|
|
9
7
|
return GEMINI_VIDEO_DURATIONS[model];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-provider-options.js","sources":["../../../src/video/video-provider-options.ts"],"sourcesContent":["/**\n * Gemini Veo Video Generation Provider Options\n *\n * Based on https://ai.google.dev/gemini-api/docs/video\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type { GenerateVideosConfig } from '@google/genai'\nimport type { GEMINI_VIDEO_MODELS } from '../model-meta'\n\n/**\n * Model type for Gemini Veo video generation.\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModel = (typeof GEMINI_VIDEO_MODELS)[number]\n\n/**\n * Supported aspect ratios for Veo video generation. This is the `size` value\n * for the Gemini video adapter — Veo expresses output shape as an aspect\n * ratio (plus an optional `resolution` in `modelOptions`), not pixel\n * dimensions.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoSize = '16:9' | '9:16'\n\n/**\n * Provider-specific options for Gemini Veo video generation.\n *\n * Derived from the SDK's `GenerateVideosConfig`, minus the fields the\n * adapter manages itself:\n * - `durationSeconds` — set via the typed top-level `duration` option\n * (use `adapter.snapDuration(seconds)` to coerce raw seconds)\n * - `aspectRatio` — set via the top-level `size` option\n * - `lastFrame` / `referenceImages` — set via image parts in the `prompt`\n * with `metadata.role: 'end_frame'` / `'reference'`\n * - `httpOptions` / `abortSignal` — client-level transport concerns\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoProviderOptions = Omit<\n GenerateVideosConfig,\n | 'durationSeconds'\n | 'aspectRatio'\n | 'lastFrame'\n | 'referenceImages'\n | 'httpOptions'\n | 'abortSignal'\n>\n\n/**\n * Model-specific provider options mapping.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelProviderOptionsByName = {\n [TModel in GeminiVideoModel]: GeminiVideoProviderOptions\n}\n\n/**\n * Model-specific size (aspect ratio) mapping.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelSizeByName = {\n [TModel in GeminiVideoModel]: GeminiVideoSize\n}\n\n/**\n * Per-model prompt input modalities. Every Veo model accepts image\n * conditioning inputs (first frame, last frame, reference images) alongside\n * the text prompt.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelInputModalitiesByName = {\n [TModel in GeminiVideoModel]: readonly ['image']\n}\n\n/**\n * Per-model duration unions (seconds, as numbers — the API's\n * `parameters.durationSeconds` field is numeric).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelDurationByName = {\n 'veo-3.1-generate-preview': 4 | 6 | 8\n 'veo-3.1-fast-generate-preview': 4 | 6 | 8\n 'veo-3.
|
|
1
|
+
{"version":3,"file":"video-provider-options.js","sources":["../../../src/video/video-provider-options.ts"],"sourcesContent":["/**\n * Gemini Veo Video Generation Provider Options\n *\n * Based on https://ai.google.dev/gemini-api/docs/video\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type { GenerateVideosConfig } from '@google/genai'\nimport type { GEMINI_VIDEO_MODELS } from '../model-meta'\n\n/**\n * Model type for Gemini Veo video generation.\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModel = (typeof GEMINI_VIDEO_MODELS)[number]\n\n/**\n * Supported aspect ratios for Veo video generation. This is the `size` value\n * for the Gemini video adapter — Veo expresses output shape as an aspect\n * ratio (plus an optional `resolution` in `modelOptions`), not pixel\n * dimensions.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoSize = '16:9' | '9:16'\n\n/**\n * Provider-specific options for Gemini Veo video generation.\n *\n * Derived from the SDK's `GenerateVideosConfig`, minus the fields the\n * adapter manages itself:\n * - `durationSeconds` — set via the typed top-level `duration` option\n * (use `adapter.snapDuration(seconds)` to coerce raw seconds)\n * - `aspectRatio` — set via the top-level `size` option\n * - `lastFrame` / `referenceImages` — set via image parts in the `prompt`\n * with `metadata.role: 'end_frame'` / `'reference'`\n * - `httpOptions` / `abortSignal` — client-level transport concerns\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoProviderOptions = Omit<\n GenerateVideosConfig,\n | 'durationSeconds'\n | 'aspectRatio'\n | 'lastFrame'\n | 'referenceImages'\n | 'httpOptions'\n | 'abortSignal'\n>\n\n/**\n * Model-specific provider options mapping.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelProviderOptionsByName = {\n [TModel in GeminiVideoModel]: GeminiVideoProviderOptions\n}\n\n/**\n * Model-specific size (aspect ratio) mapping.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelSizeByName = {\n [TModel in GeminiVideoModel]: GeminiVideoSize\n}\n\n/**\n * Per-model prompt input modalities. Every Veo model accepts image\n * conditioning inputs (first frame, last frame, reference images) alongside\n * the text prompt.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelInputModalitiesByName = {\n [TModel in GeminiVideoModel]: readonly ['image']\n}\n\n/**\n * Per-model duration unions (seconds, as numbers — the API's\n * `parameters.durationSeconds` field is numeric).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelDurationByName = {\n 'veo-3.1-generate-preview': 4 | 6 | 8\n 'veo-3.1-fast-generate-preview': 4 | 6 | 8\n 'veo-3.1-lite-generate-preview': 4 | 6 | 8\n}\n\n/**\n * Runtime duration table backing `availableDurations()` / `snapDuration()`.\n *\n * Curated from the official Veo docs\n * (https://ai.google.dev/gemini-api/docs/video) — the Gemini OpenAPI spec\n * types the `:predictLongRunning` request's `parameters` as unconstrained,\n * so it carries no per-model duration information to derive these from.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport const GEMINI_VIDEO_DURATIONS: {\n readonly [TModel in GeminiVideoModel]: DurationOptions<\n GeminiVideoModelDurationByName[TModel]\n >\n} = {\n 'veo-3.1-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n 'veo-3.1-fast-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n 'veo-3.1-lite-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n}\n\n/**\n * Look up the duration options for a Veo model.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function getGeminiVideoDurationOptions<TModel extends GeminiVideoModel>(\n model: TModel,\n): DurationOptions<GeminiVideoModelDurationByName[TModel]> {\n return GEMINI_VIDEO_DURATIONS[model]\n}\n"],"names":[],"mappings":"AAsGO,MAAM,yBAIT;AAAA,EACF,4BAA4B,EAAE,MAAM,YAAY,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAA;AAAA,EAChE,iCAAiC,EAAE,MAAM,YAAY,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAA;AAAA,EACrE,iCAAiC,EAAE,MAAM,YAAY,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAA;AACvE;AAOO,SAAS,8BACd,OACyD;AACzD,SAAO,uBAAuB,KAAK;AACrC;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-gemini",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Google Gemini adapter for TanStack AI chat, images, speech, audio generation, and structured outputs.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"@tanstack/ai-utils": "0.3.1"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@tanstack/ai": "^0.
|
|
62
|
+
"@tanstack/ai": "^0.39.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@vitest/coverage-v8": "4.0.14",
|
|
66
66
|
"vite": "^7.3.3",
|
|
67
67
|
"zod": "^4.2.0",
|
|
68
|
-
"@tanstack/ai": "0.
|
|
68
|
+
"@tanstack/ai": "0.39.0"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "vite build",
|
package/src/adapters/image.ts
CHANGED
|
@@ -420,14 +420,14 @@ export class GeminiImageAdapter<
|
|
|
420
420
|
* Creates a Gemini image adapter with explicit API key.
|
|
421
421
|
* Type resolution happens here at the call site.
|
|
422
422
|
*
|
|
423
|
-
* @param model - The model name (e.g., 'imagen-
|
|
423
|
+
* @param model - The model name (e.g., 'imagen-4.0-generate-001')
|
|
424
424
|
* @param apiKey - Your Google API key
|
|
425
425
|
* @param config - Optional additional configuration
|
|
426
426
|
* @returns Configured Gemini image adapter instance with resolved types
|
|
427
427
|
*
|
|
428
428
|
* @example
|
|
429
429
|
* ```typescript
|
|
430
|
-
* const adapter = createGeminiImage('imagen-
|
|
430
|
+
* const adapter = createGeminiImage('imagen-4.0-generate-001', "your-api-key");
|
|
431
431
|
*
|
|
432
432
|
* const result = await generateImage({
|
|
433
433
|
* adapter,
|
|
@@ -1550,7 +1550,7 @@ function extractTextFromInteraction(interaction: Interaction): string {
|
|
|
1550
1550
|
return interaction.output_text
|
|
1551
1551
|
}
|
|
1552
1552
|
let text = ''
|
|
1553
|
-
for (const step of interaction.steps) {
|
|
1553
|
+
for (const step of interaction.steps ?? []) {
|
|
1554
1554
|
if (step.type !== 'model_output' || !step.content) continue
|
|
1555
1555
|
for (const part of step.content) {
|
|
1556
1556
|
if (part.type === 'text') {
|
|
@@ -176,6 +176,7 @@ export type GeminiNativeImageSize =
|
|
|
176
176
|
*/
|
|
177
177
|
export type GeminiNativeImageModels =
|
|
178
178
|
| 'gemini-3.1-flash-image-preview'
|
|
179
|
+
| 'gemini-3.1-flash-lite-image'
|
|
179
180
|
| 'gemini-3-pro-image-preview'
|
|
180
181
|
| 'gemini-2.5-flash-image'
|
|
181
182
|
|
|
@@ -256,15 +257,14 @@ export function validateImageSize(
|
|
|
256
257
|
|
|
257
258
|
/**
|
|
258
259
|
* Per-model caps on images per request.
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
260
|
+
* The Imagen 4 family all support up to 4 images per request via the Gemini
|
|
261
|
+
* API (the rumored 8-image tier is Vertex-only and isn't reachable through
|
|
262
|
+
* @google/genai today). Unknown models fall through to the shared cap
|
|
263
|
+
* defined below.
|
|
263
264
|
*
|
|
264
265
|
* @see https://ai.google.dev/gemini-api/docs/imagen
|
|
265
266
|
*/
|
|
266
267
|
const IMAGEN_MAX_IMAGES_BY_MODEL: Record<string, number> = {
|
|
267
|
-
'imagen-3.0-generate-002': 4,
|
|
268
268
|
'imagen-4.0-generate-001': 4,
|
|
269
269
|
'imagen-4.0-ultra-generate-001': 4,
|
|
270
270
|
'imagen-4.0-fast-generate-001': 4,
|
package/src/model-meta.ts
CHANGED
|
@@ -173,6 +173,34 @@ const GEMINI_3_1_FLASH_IMAGE = {
|
|
|
173
173
|
GeminiThinkingOptions
|
|
174
174
|
>
|
|
175
175
|
|
|
176
|
+
const GEMINI_3_1_FLASH_LITE_IMAGE = {
|
|
177
|
+
name: 'gemini-3.1-flash-lite-image',
|
|
178
|
+
max_input_tokens: 65_536,
|
|
179
|
+
max_output_tokens: 65_536,
|
|
180
|
+
knowledge_cutoff: '2025-01-01',
|
|
181
|
+
supports: {
|
|
182
|
+
input: ['text', 'image'],
|
|
183
|
+
output: ['text', 'image'],
|
|
184
|
+
capabilities: ['batch_api', 'structured_output', 'thinking'],
|
|
185
|
+
tools: ['google_search'],
|
|
186
|
+
},
|
|
187
|
+
pricing: {
|
|
188
|
+
input: {
|
|
189
|
+
normal: 0.25,
|
|
190
|
+
},
|
|
191
|
+
output: {
|
|
192
|
+
normal: 1.5,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
} as const satisfies ModelMeta<
|
|
196
|
+
GeminiToolConfigOptions &
|
|
197
|
+
GeminiSafetyOptions &
|
|
198
|
+
GeminiCommonConfigOptions &
|
|
199
|
+
GeminiCachedContentOptions &
|
|
200
|
+
GeminiStructuredOutputOptions &
|
|
201
|
+
GeminiThinkingOptions
|
|
202
|
+
>
|
|
203
|
+
|
|
176
204
|
const GEMINI_3_1_FLASH_LITE = {
|
|
177
205
|
name: 'gemini-3.1-flash-lite',
|
|
178
206
|
max_input_tokens: 1_048_576,
|
|
@@ -610,27 +638,6 @@ const IMAGEN_4_GENERATE_FAST = {
|
|
|
610
638
|
GeminiCachedContentOptions
|
|
611
639
|
>
|
|
612
640
|
|
|
613
|
-
const IMAGEN_3 = {
|
|
614
|
-
name: 'imagen-3.0-generate-002',
|
|
615
|
-
max_output_tokens: 4,
|
|
616
|
-
supports: {
|
|
617
|
-
input: ['text'],
|
|
618
|
-
output: ['image'],
|
|
619
|
-
},
|
|
620
|
-
pricing: {
|
|
621
|
-
input: {
|
|
622
|
-
normal: 0,
|
|
623
|
-
},
|
|
624
|
-
output: {
|
|
625
|
-
normal: 0.03,
|
|
626
|
-
},
|
|
627
|
-
},
|
|
628
|
-
} as const satisfies ModelMeta<
|
|
629
|
-
GeminiToolConfigOptions &
|
|
630
|
-
GeminiSafetyOptions &
|
|
631
|
-
GeminiCommonConfigOptions &
|
|
632
|
-
GeminiCachedContentOptions
|
|
633
|
-
>
|
|
634
641
|
/**
|
|
635
642
|
* Veo video generation models. Pricing is per second of generated video
|
|
636
643
|
* (audio+video rate where the model supports audio).
|
|
@@ -682,8 +689,8 @@ const VEO_3_1_FAST_PREVIEW = {
|
|
|
682
689
|
GeminiCachedContentOptions
|
|
683
690
|
>
|
|
684
691
|
|
|
685
|
-
const
|
|
686
|
-
name: 'veo-3.
|
|
692
|
+
const VEO_3_1_LITE_PREVIEW = {
|
|
693
|
+
name: 'veo-3.1-lite-generate-preview',
|
|
687
694
|
max_input_tokens: 1024,
|
|
688
695
|
max_output_tokens: 1,
|
|
689
696
|
supports: {
|
|
@@ -695,52 +702,7 @@ const VEO_3 = {
|
|
|
695
702
|
normal: 0,
|
|
696
703
|
},
|
|
697
704
|
output: {
|
|
698
|
-
normal: 0.
|
|
699
|
-
},
|
|
700
|
-
},
|
|
701
|
-
} as const satisfies ModelMeta<
|
|
702
|
-
GeminiToolConfigOptions &
|
|
703
|
-
GeminiSafetyOptions &
|
|
704
|
-
GeminiCommonConfigOptions &
|
|
705
|
-
GeminiCachedContentOptions
|
|
706
|
-
>
|
|
707
|
-
|
|
708
|
-
const VEO_3_FAST = {
|
|
709
|
-
name: 'veo-3.0-fast-generate-001',
|
|
710
|
-
max_input_tokens: 1024,
|
|
711
|
-
max_output_tokens: 1,
|
|
712
|
-
supports: {
|
|
713
|
-
input: ['text', 'image'],
|
|
714
|
-
output: ['video', 'audio'],
|
|
715
|
-
},
|
|
716
|
-
pricing: {
|
|
717
|
-
input: {
|
|
718
|
-
normal: 0,
|
|
719
|
-
},
|
|
720
|
-
output: {
|
|
721
|
-
normal: 0.15,
|
|
722
|
-
},
|
|
723
|
-
},
|
|
724
|
-
} as const satisfies ModelMeta<
|
|
725
|
-
GeminiToolConfigOptions &
|
|
726
|
-
GeminiSafetyOptions &
|
|
727
|
-
GeminiCommonConfigOptions &
|
|
728
|
-
GeminiCachedContentOptions
|
|
729
|
-
>
|
|
730
|
-
|
|
731
|
-
const VEO_2 = {
|
|
732
|
-
name: 'veo-2.0-generate-001',
|
|
733
|
-
max_output_tokens: 2,
|
|
734
|
-
supports: {
|
|
735
|
-
input: ['text', 'image'],
|
|
736
|
-
output: ['video'],
|
|
737
|
-
},
|
|
738
|
-
pricing: {
|
|
739
|
-
input: {
|
|
740
|
-
normal: 0,
|
|
741
|
-
},
|
|
742
|
-
output: {
|
|
743
|
-
normal: 0.35,
|
|
705
|
+
normal: 0.05,
|
|
744
706
|
},
|
|
745
707
|
},
|
|
746
708
|
} as const satisfies ModelMeta<
|
|
@@ -816,9 +778,9 @@ export type GeminiImageModels = (typeof GEMINI_IMAGE_MODELS)[number]
|
|
|
816
778
|
|
|
817
779
|
export const GEMINI_IMAGE_MODELS = [
|
|
818
780
|
GEMINI_3_1_FLASH_IMAGE.name,
|
|
781
|
+
GEMINI_3_1_FLASH_LITE_IMAGE.name,
|
|
819
782
|
GEMINI_3_PRO_IMAGE.name,
|
|
820
783
|
GEMINI_2_5_FLASH_IMAGE.name,
|
|
821
|
-
IMAGEN_3.name,
|
|
822
784
|
IMAGEN_4_GENERATE.name,
|
|
823
785
|
IMAGEN_4_GENERATE_FAST.name,
|
|
824
786
|
IMAGEN_4_GENERATE_ULTRA.name,
|
|
@@ -889,9 +851,7 @@ export type GeminiTTSVoice = (typeof GEMINI_TTS_VOICES)[number]
|
|
|
889
851
|
export const GEMINI_VIDEO_MODELS = [
|
|
890
852
|
VEO_3_1_PREVIEW.name,
|
|
891
853
|
VEO_3_1_FAST_PREVIEW.name,
|
|
892
|
-
|
|
893
|
-
VEO_3_FAST.name,
|
|
894
|
-
VEO_2.name,
|
|
854
|
+
VEO_3_1_LITE_PREVIEW.name,
|
|
895
855
|
] as const
|
|
896
856
|
|
|
897
857
|
// Manual type map for per-model provider options
|
|
@@ -87,9 +87,7 @@ export type GeminiVideoModelInputModalitiesByName = {
|
|
|
87
87
|
export type GeminiVideoModelDurationByName = {
|
|
88
88
|
'veo-3.1-generate-preview': 4 | 6 | 8
|
|
89
89
|
'veo-3.1-fast-generate-preview': 4 | 6 | 8
|
|
90
|
-
'veo-3.
|
|
91
|
-
'veo-3.0-fast-generate-001': 4 | 6 | 8
|
|
92
|
-
'veo-2.0-generate-001': 5 | 6 | 8
|
|
90
|
+
'veo-3.1-lite-generate-preview': 4 | 6 | 8
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
/**
|
|
@@ -109,9 +107,7 @@ export const GEMINI_VIDEO_DURATIONS: {
|
|
|
109
107
|
} = {
|
|
110
108
|
'veo-3.1-generate-preview': { kind: 'discrete', values: [4, 6, 8] },
|
|
111
109
|
'veo-3.1-fast-generate-preview': { kind: 'discrete', values: [4, 6, 8] },
|
|
112
|
-
'veo-3.
|
|
113
|
-
'veo-3.0-fast-generate-001': { kind: 'discrete', values: [4, 6, 8] },
|
|
114
|
-
'veo-2.0-generate-001': { kind: 'discrete', values: [5, 6, 8] },
|
|
110
|
+
'veo-3.1-lite-generate-preview': { kind: 'discrete', values: [4, 6, 8] },
|
|
115
111
|
}
|
|
116
112
|
|
|
117
113
|
/**
|