@tanstack/ai-gemini 0.23.0 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/esm/adapters/image.d.ts +14 -7
- package/dist/esm/adapters/image.js +27 -56
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/video.js +2 -1
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/experimental/text-interactions/adapter.d.ts +8 -5
- package/dist/esm/experimental/text-interactions/adapter.js +20 -18
- package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
- package/dist/esm/image/image-provider-options.d.ts +167 -20
- package/dist/esm/image/image-provider-options.js +52 -5
- package/dist/esm/image/image-provider-options.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/model-meta.d.ts +15 -2
- package/dist/esm/model-meta.js +82 -1
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/realtime/adapter.js +1 -5
- package/dist/esm/realtime/adapter.js.map +1 -1
- package/dist/esm/realtime/client.js.map +1 -1
- package/dist/esm/realtime/token.js +3 -2
- package/dist/esm/realtime/token.js.map +1 -1
- package/dist/esm/realtime/utils.js +4 -1
- package/dist/esm/realtime/utils.js.map +1 -1
- package/dist/esm/tools/code-execution-tool.js +3 -3
- package/dist/esm/tools/code-execution-tool.js.map +1 -1
- package/dist/esm/tools/computer-use-tool.js +4 -4
- package/dist/esm/tools/computer-use-tool.js.map +1 -1
- package/dist/esm/tools/file-search-tool.js +4 -4
- package/dist/esm/tools/file-search-tool.js.map +1 -1
- package/dist/esm/tools/gemini-provider-tool.d.ts +16 -0
- package/dist/esm/tools/gemini-provider-tool.js +42 -0
- package/dist/esm/tools/gemini-provider-tool.js.map +1 -0
- package/dist/esm/tools/google-maps-tool.js +4 -4
- package/dist/esm/tools/google-maps-tool.js.map +1 -1
- package/dist/esm/tools/google-search-retriveal-tool.js +4 -4
- package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -1
- package/dist/esm/tools/google-search-tool.js +4 -4
- package/dist/esm/tools/google-search-tool.js.map +1 -1
- package/dist/esm/tools/tool-converter.js +5 -3
- package/dist/esm/tools/tool-converter.js.map +1 -1
- package/dist/esm/tools/url-context-tool.js +3 -3
- package/dist/esm/tools/url-context-tool.js.map +1 -1
- package/dist/esm/usage.js +1 -3
- package/dist/esm/usage.js.map +1 -1
- package/package.json +5 -5
- package/src/adapters/image.ts +123 -36
- package/src/experimental/text-interactions/adapter.ts +31 -18
- package/src/image/image-provider-options.ts +226 -25
- package/src/index.ts +28 -0
- package/src/model-meta.ts +118 -1
- package/src/tools/code-execution-tool.ts +9 -6
- package/src/tools/computer-use-tool.ts +19 -13
- package/src/tools/file-search-tool.ts +13 -7
- package/src/tools/gemini-provider-tool.ts +60 -0
- package/src/tools/google-maps-tool.ts +13 -7
- package/src/tools/google-search-retriveal-tool.ts +15 -7
- package/src/tools/google-search-tool.ts +13 -7
- package/src/tools/tool-converter.ts +5 -4
- package/src/tools/url-context-tool.ts +9 -6
package/README.md
CHANGED
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
</a>
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
|
+
<br />
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
<a href="https://tanstack.com/blog/tanstack-open-source-awards-2026">
|
|
43
|
+
<img src="https://raw.githubusercontent.com/TanStack/ai/16826d81cade868956df240d6239a671e689193c/media/js-open-source-award-2026-ai-project-of-the-year.svg" alt="Winner of the 2026 JavaScript Open Source Award for AI Project of the Year" width="250" />
|
|
44
|
+
</a>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
39
47
|
# TanStack AI
|
|
40
48
|
|
|
41
49
|
Type-safe, provider-agnostic TypeScript SDK for building streaming chat,
|
|
@@ -191,7 +199,7 @@ Learn more in the
|
|
|
191
199
|
build low-latency realtime voice experiences.
|
|
192
200
|
- [Code Mode](https://tanstack.com/ai/latest/docs/code-mode/code-mode) - let
|
|
193
201
|
models write and execute TypeScript inside a secure isolate.
|
|
194
|
-
- [Code Mode with
|
|
202
|
+
- [Code Mode with Snippets](https://tanstack.com/ai/latest/docs/code-mode/code-mode-with-snippets) -
|
|
195
203
|
give Code Mode reusable runtime capabilities.
|
|
196
204
|
|
|
197
205
|
## Providers
|
|
@@ -208,6 +216,7 @@ Official adapters include:
|
|
|
208
216
|
| [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
|
|
209
217
|
| [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
|
|
210
218
|
| [`@tanstack/ai-elevenlabs`](https://tanstack.com/ai/latest/docs/adapters/elevenlabs) | ElevenLabs realtime voice, speech, transcription, music, and sound effects |
|
|
219
|
+
| [`@tanstack/ai-byteplus`](https://tanstack.com/ai/latest/docs/adapters/byteplus) | BytePlus Seed chat, Seedance video, Seedream image, and Seed Speech TTS/ASR |
|
|
211
220
|
| [`@tanstack/ai-fal`](https://tanstack.com/ai/latest/docs/adapters/fal) | fal.ai image, video, audio, speech, and transcription models |
|
|
212
221
|
|
|
213
222
|
The adapter system is tree-shakeable by activity. Import `openaiText` for chat,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseImageAdapter } from '@tanstack/ai/adapters';
|
|
2
|
-
import {
|
|
3
|
-
import { GeminiImageModelInputModalitiesByName, GeminiImageModelProviderOptionsByName, GeminiImageModelSizeByName
|
|
2
|
+
import { GeminiImageModels } from '../model-meta.js';
|
|
3
|
+
import { GeminiAnyImageProviderOptions, GeminiImageModelInputModalitiesByName, GeminiImageModelProviderOptionsByName, GeminiImageModelSizeByName } from '../image/image-provider-options.js';
|
|
4
4
|
import { ImageGenerationOptions, ImageGenerationResult } from '@tanstack/ai';
|
|
5
5
|
import { GeminiClientConfig } from '../utils/client.js';
|
|
6
6
|
/**
|
|
@@ -9,7 +9,7 @@ import { GeminiClientConfig } from '../utils/client.js';
|
|
|
9
9
|
export interface GeminiImageConfig extends GeminiClientConfig {
|
|
10
10
|
}
|
|
11
11
|
/** Model type for Gemini Image */
|
|
12
|
-
export type GeminiImageModel =
|
|
12
|
+
export type GeminiImageModel = GeminiImageModels;
|
|
13
13
|
/**
|
|
14
14
|
* Gemini Image Generation Adapter
|
|
15
15
|
*
|
|
@@ -24,19 +24,18 @@ export type GeminiImageModel = (typeof GEMINI_IMAGE_MODELS)[number];
|
|
|
24
24
|
* - Watermark options
|
|
25
25
|
* - Extended resolution tiers (Nano Banana 2)
|
|
26
26
|
*/
|
|
27
|
-
export declare class GeminiImageAdapter<TModel extends GeminiImageModel> extends BaseImageAdapter<TModel,
|
|
27
|
+
export declare class GeminiImageAdapter<TModel extends GeminiImageModel> extends BaseImageAdapter<TModel, GeminiAnyImageProviderOptions, GeminiImageModelProviderOptionsByName, GeminiImageModelSizeByName, GeminiImageModelInputModalitiesByName> {
|
|
28
28
|
readonly kind: "image";
|
|
29
29
|
readonly name: "gemini";
|
|
30
30
|
'~types': {
|
|
31
|
-
providerOptions:
|
|
31
|
+
providerOptions: GeminiAnyImageProviderOptions;
|
|
32
32
|
modelProviderOptionsByName: GeminiImageModelProviderOptionsByName;
|
|
33
33
|
modelSizeByName: GeminiImageModelSizeByName;
|
|
34
34
|
modelInputModalitiesByName: GeminiImageModelInputModalitiesByName;
|
|
35
35
|
};
|
|
36
36
|
private readonly client;
|
|
37
37
|
constructor(config: GeminiImageConfig, model: TModel);
|
|
38
|
-
generateImages(options: ImageGenerationOptions<
|
|
39
|
-
private isGeminiImageModel;
|
|
38
|
+
generateImages(options: ImageGenerationOptions<GeminiAnyImageProviderOptions>): Promise<ImageGenerationResult>;
|
|
40
39
|
private generateWithGeminiApi;
|
|
41
40
|
/**
|
|
42
41
|
* Build the multimodal `contents` payload. Text-only prompts pass through
|
|
@@ -54,6 +53,10 @@ export declare class GeminiImageAdapter<TModel extends GeminiImageModel> extends
|
|
|
54
53
|
private buildImagenConfig;
|
|
55
54
|
private transformImagenResponse;
|
|
56
55
|
}
|
|
56
|
+
/** @deprecated Shut down 2026-06-25. Use `gemini-3.1-flash-image`. */
|
|
57
|
+
export declare function createGeminiImage(model: 'gemini-3.1-flash-image-preview', apiKey: string, config?: Omit<GeminiImageConfig, 'apiKey'>): GeminiImageAdapter<'gemini-3.1-flash-image-preview'>;
|
|
58
|
+
/** @deprecated Shut down 2026-06-25. Use `gemini-3-pro-image`. */
|
|
59
|
+
export declare function createGeminiImage(model: 'gemini-3-pro-image-preview', apiKey: string, config?: Omit<GeminiImageConfig, 'apiKey'>): GeminiImageAdapter<'gemini-3-pro-image-preview'>;
|
|
57
60
|
/**
|
|
58
61
|
* Creates a Gemini image adapter with explicit API key.
|
|
59
62
|
* Type resolution happens here at the call site.
|
|
@@ -74,6 +77,10 @@ export declare class GeminiImageAdapter<TModel extends GeminiImageModel> extends
|
|
|
74
77
|
* ```
|
|
75
78
|
*/
|
|
76
79
|
export declare function createGeminiImage<TModel extends GeminiImageModel>(model: TModel, apiKey: string, config?: Omit<GeminiImageConfig, 'apiKey'>): GeminiImageAdapter<TModel>;
|
|
80
|
+
/** @deprecated Shut down 2026-06-25. Use `gemini-3.1-flash-image`. */
|
|
81
|
+
export declare function geminiImage(model: 'gemini-3.1-flash-image-preview', config?: Omit<GeminiImageConfig, 'apiKey'>): GeminiImageAdapter<'gemini-3.1-flash-image-preview'>;
|
|
82
|
+
/** @deprecated Shut down 2026-06-25. Use `gemini-3-pro-image`. */
|
|
83
|
+
export declare function geminiImage(model: 'gemini-3-pro-image-preview', config?: Omit<GeminiImageConfig, 'apiKey'>): GeminiImageAdapter<'gemini-3-pro-image-preview'>;
|
|
77
84
|
/**
|
|
78
85
|
* Creates a Gemini image adapter with automatic API key detection from environment variables.
|
|
79
86
|
* Type resolution happens here at the call site.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildGeminiUsage } from "../usage.js";
|
|
2
2
|
import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../utils/client.js";
|
|
3
3
|
import "../utils/index.js";
|
|
4
|
-
import { parseNativeImageSize, sizeToAspectRatio, validateImageSize, validateNumberOfImages, validatePrompt } from "../image/image-provider-options.js";
|
|
4
|
+
import { isGeminiNativeImageModel, parseNativeImageSize, sizeToAspectRatio, validateImageSize, validateNumberOfImages, validatePrompt } from "../image/image-provider-options.js";
|
|
5
5
|
import { resolveMediaPrompt } from "@tanstack/ai";
|
|
6
6
|
import { BaseImageAdapter } from "@tanstack/ai/adapters";
|
|
7
7
|
//#region src/adapters/image.ts
|
|
@@ -41,7 +41,7 @@ var GeminiImageAdapter = class extends BaseImageAdapter {
|
|
|
41
41
|
});
|
|
42
42
|
if (resolved.videos.length > 0) throw new Error(`${this.name}.generateImages does not support video prompt parts (model: ${model}).`);
|
|
43
43
|
if (resolved.audios.length > 0) throw new Error(`${this.name}.generateImages does not support audio prompt parts (model: ${model}).`);
|
|
44
|
-
if (
|
|
44
|
+
if (isGeminiNativeImageModel(model)) return await this.generateWithGeminiApi(options, resolved);
|
|
45
45
|
if (resolved.images.length > 0) throw new Error(`${this.name}: model "${model}" (Imagen) does not support image prompt parts. Use a Gemini-native image model (e.g. gemini-2.5-flash-image, "nano-banana") for image-conditioned generation.`);
|
|
46
46
|
validateImageSize(model, options.size);
|
|
47
47
|
validateNumberOfImages(model, options.numberOfImages);
|
|
@@ -60,21 +60,21 @@ var GeminiImageAdapter = class extends BaseImageAdapter {
|
|
|
60
60
|
throw error;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
isGeminiImageModel(model) {
|
|
64
|
-
return model.startsWith("gemini-");
|
|
65
|
-
}
|
|
66
63
|
async generateWithGeminiApi(options, resolved) {
|
|
67
64
|
const { model, size, numberOfImages, modelOptions } = options;
|
|
68
65
|
const parsedSize = size ? parseNativeImageSize(size) : void 0;
|
|
69
|
-
const
|
|
70
|
-
|
|
66
|
+
const imageConfig = {
|
|
67
|
+
...parsedSize?.aspectRatio && { aspectRatio: parsedSize.aspectRatio },
|
|
68
|
+
...parsedSize?.resolution && { imageSize: parsedSize.resolution },
|
|
69
|
+
...modelOptions?.imageConfig
|
|
70
|
+
};
|
|
71
71
|
const config = {
|
|
72
|
-
...
|
|
72
|
+
...modelOptions?.seed !== void 0 && { seed: modelOptions.seed },
|
|
73
|
+
...modelOptions?.safetySettings !== void 0 && { safetySettings: modelOptions.safetySettings },
|
|
74
|
+
...modelOptions?.thinkingConfig !== void 0 && { thinkingConfig: modelOptions.thinkingConfig },
|
|
75
|
+
...modelOptions?.systemInstruction !== void 0 && { systemInstruction: modelOptions.systemInstruction },
|
|
73
76
|
responseModalities: ["TEXT", "IMAGE"],
|
|
74
|
-
...
|
|
75
|
-
...parsedSize.aspectRatio && { aspectRatio: parsedSize.aspectRatio },
|
|
76
|
-
...parsedSize.resolution && { imageSize: parsedSize.resolution }
|
|
77
|
-
} }
|
|
77
|
+
...Object.keys(imageConfig).length > 0 && { imageConfig }
|
|
78
78
|
};
|
|
79
79
|
const contents = this.buildContents(resolved, numberOfImages);
|
|
80
80
|
const response = await this.client.models.generateContent({
|
|
@@ -141,7 +141,21 @@ var GeminiImageAdapter = class extends BaseImageAdapter {
|
|
|
141
141
|
return {
|
|
142
142
|
numberOfImages: numberOfImages ?? 1,
|
|
143
143
|
...sizeAspectRatio !== void 0 && { aspectRatio: sizeAspectRatio },
|
|
144
|
-
...modelOptions
|
|
144
|
+
...modelOptions?.aspectRatio !== void 0 && { aspectRatio: modelOptions.aspectRatio },
|
|
145
|
+
...modelOptions?.personGeneration !== void 0 && { personGeneration: modelOptions.personGeneration },
|
|
146
|
+
...modelOptions?.safetyFilterLevel !== void 0 && { safetyFilterLevel: modelOptions.safetyFilterLevel },
|
|
147
|
+
...modelOptions?.seed !== void 0 && { seed: modelOptions.seed },
|
|
148
|
+
...modelOptions?.addWatermark !== void 0 && { addWatermark: modelOptions.addWatermark },
|
|
149
|
+
...modelOptions?.language !== void 0 && { language: modelOptions.language },
|
|
150
|
+
...modelOptions?.negativePrompt !== void 0 && { negativePrompt: modelOptions.negativePrompt },
|
|
151
|
+
...modelOptions?.outputMimeType !== void 0 && { outputMimeType: modelOptions.outputMimeType },
|
|
152
|
+
...modelOptions?.outputCompressionQuality !== void 0 && { outputCompressionQuality: modelOptions.outputCompressionQuality },
|
|
153
|
+
...modelOptions?.guidanceScale !== void 0 && { guidanceScale: modelOptions.guidanceScale },
|
|
154
|
+
...modelOptions?.enhancePrompt !== void 0 && { enhancePrompt: modelOptions.enhancePrompt },
|
|
155
|
+
...modelOptions?.includeSafetyAttributes !== void 0 && { includeSafetyAttributes: modelOptions.includeSafetyAttributes },
|
|
156
|
+
...modelOptions?.includeRaiReason !== void 0 && { includeRaiReason: modelOptions.includeRaiReason },
|
|
157
|
+
...modelOptions?.outputGcsUri !== void 0 && { outputGcsUri: modelOptions.outputGcsUri },
|
|
158
|
+
...modelOptions?.labels !== void 0 && { labels: modelOptions.labels }
|
|
145
159
|
};
|
|
146
160
|
}
|
|
147
161
|
transformImagenResponse(model, response) {
|
|
@@ -172,55 +186,12 @@ var GeminiImageAdapter = class extends BaseImageAdapter {
|
|
|
172
186
|
};
|
|
173
187
|
}
|
|
174
188
|
};
|
|
175
|
-
/**
|
|
176
|
-
* Creates a Gemini image adapter with explicit API key.
|
|
177
|
-
* Type resolution happens here at the call site.
|
|
178
|
-
*
|
|
179
|
-
* @param model - The model name (e.g., 'imagen-4.0-generate-001')
|
|
180
|
-
* @param apiKey - Your Google API key
|
|
181
|
-
* @param config - Optional additional configuration
|
|
182
|
-
* @returns Configured Gemini image adapter instance with resolved types
|
|
183
|
-
*
|
|
184
|
-
* @example
|
|
185
|
-
* ```typescript
|
|
186
|
-
* const adapter = createGeminiImage('imagen-4.0-generate-001', "your-api-key");
|
|
187
|
-
*
|
|
188
|
-
* const result = await generateImage({
|
|
189
|
-
* adapter,
|
|
190
|
-
* prompt: 'A cute baby sea otter'
|
|
191
|
-
* });
|
|
192
|
-
* ```
|
|
193
|
-
*/
|
|
194
189
|
function createGeminiImage(model, apiKey, config) {
|
|
195
190
|
return new GeminiImageAdapter({
|
|
196
191
|
apiKey,
|
|
197
192
|
...config
|
|
198
193
|
}, model);
|
|
199
194
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Creates a Gemini image adapter with automatic API key detection from environment variables.
|
|
202
|
-
* Type resolution happens here at the call site.
|
|
203
|
-
*
|
|
204
|
-
* Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:
|
|
205
|
-
* - `process.env` (Node.js)
|
|
206
|
-
* - `window.env` (Browser with injected env)
|
|
207
|
-
*
|
|
208
|
-
* @param model - The model name (e.g., 'imagen-4.0-generate-001')
|
|
209
|
-
* @param config - Optional configuration (excluding apiKey which is auto-detected)
|
|
210
|
-
* @returns Configured Gemini image adapter instance with resolved types
|
|
211
|
-
* @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment
|
|
212
|
-
*
|
|
213
|
-
* @example
|
|
214
|
-
* ```typescript
|
|
215
|
-
* // Automatically uses GOOGLE_API_KEY from environment
|
|
216
|
-
* const adapter = geminiImage('imagen-4.0-generate-001');
|
|
217
|
-
*
|
|
218
|
-
* const result = await generateImage({
|
|
219
|
-
* adapter,
|
|
220
|
-
* prompt: 'A beautiful sunset over mountains'
|
|
221
|
-
* });
|
|
222
|
-
* ```
|
|
223
|
-
*/
|
|
224
195
|
function geminiImage(model, config) {
|
|
225
196
|
return createGeminiImage(model, getGeminiApiKeyFromEnv(), config);
|
|
226
197
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","names":[],"sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseImageAdapter } from '@tanstack/ai/adapters'\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/client'\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 = 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 buildContents(\n resolved: ResolvedMediaPrompt,\n numberOfImages: number | undefined,\n ): 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> = resolved.parts.map((part) => {\n if (part.type === 'text') {\n return { 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 if (countInstruction) {\n parts.push({ text: countInstruction })\n }\n return [{ role: 'user', parts }]\n }\n\n private imagePartToGeminiPart(part: ImagePart<MediaInputMetadata>): 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 // URL sources (public HTTPS, Files API URIs, gs://) pass through as\n // `fileData` and Gemini fetches them server-side — same as the chat\n // adapter. Fetching locally and inlining as base64 double-buffers the\n // image and OOMs on memory-constrained runtimes (e.g. Cloudflare\n // Workers).\n return {\n fileData: {\n fileUri: part.source.value,\n mimeType: part.source.mimeType ?? 'image/jpeg',\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"image.js","names":[],"sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport { buildGeminiUsage } from '../usage'\nimport {\n isGeminiNativeImageModel,\n parseNativeImageSize,\n sizeToAspectRatio,\n validateImageSize,\n validateNumberOfImages,\n validatePrompt,\n} from '../image/image-provider-options'\nimport type { GeminiImageModels } from '../model-meta'\nimport type {\n GeminiAnyImageProviderOptions,\n GeminiImageModelInputModalitiesByName,\n GeminiImageModelProviderOptionsByName,\n GeminiImageModelSizeByName,\n GeminiNativeImageProviderOptions,\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 ImageConfig,\n Part,\n} from '@google/genai'\nimport type { GeminiClientConfig } from '../utils/client'\n\n/**\n * Configuration for Gemini image adapter\n */\nexport interface GeminiImageConfig extends GeminiClientConfig {}\n\n/** Model type for Gemini Image */\nexport type GeminiImageModel = GeminiImageModels\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 GeminiAnyImageProviderOptions,\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: GeminiAnyImageProviderOptions\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<GeminiAnyImageProviderOptions>,\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 (isGeminiNativeImageModel(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 async generateWithGeminiApi(\n options: ImageGenerationOptions<GeminiNativeImageProviderOptions>,\n resolved: ResolvedMediaPrompt,\n ): Promise<ImageGenerationResult> {\n const { model, size, numberOfImages, modelOptions } = options\n\n const parsedSize = size ? parseNativeImageSize(size) : undefined\n\n // The portable `size` option is the baseline; modelOptions.imageConfig is\n // the provider escape hatch and wins per field, so a caller passing only\n // `imageConfig.imageSize` keeps the aspectRatio derived from `size`.\n const imageConfig: ImageConfig = {\n ...(parsedSize?.aspectRatio && { aspectRatio: parsedSize.aspectRatio }),\n ...(parsedSize?.resolution && { imageSize: parsedSize.resolution }),\n ...modelOptions?.imageConfig,\n }\n\n // Named picks, never a wholesale spread: the Imagen-shaped fields of\n // GeminiImageProviderOptions (personGeneration, safetyFilterLevel,\n // addWatermark, outputMimeType, …) are only valid on GenerateImagesConfig\n // and would be rejected by generateContent. Picking by name means no\n // Imagen field can reach this path even if one slips past the per-model\n // provider-options map.\n const nativeConfig: GenerateContentConfig = {\n ...(modelOptions?.seed !== undefined && { seed: modelOptions.seed }),\n ...(modelOptions?.safetySettings !== undefined && {\n safetySettings: modelOptions.safetySettings,\n }),\n ...(modelOptions?.thinkingConfig !== undefined && {\n thinkingConfig: modelOptions.thinkingConfig,\n }),\n ...(modelOptions?.systemInstruction !== undefined && {\n systemInstruction: modelOptions.systemInstruction,\n }),\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 ...(Object.keys(imageConfig).length > 0 && { imageConfig }),\n }\n\n const contents = 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 buildContents(\n resolved: ResolvedMediaPrompt,\n numberOfImages: number | undefined,\n ): 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> = resolved.parts.map((part) => {\n if (part.type === 'text') {\n return { 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 if (countInstruction) {\n parts.push({ text: countInstruction })\n }\n return [{ role: 'user', parts }]\n }\n\n private imagePartToGeminiPart(part: ImagePart<MediaInputMetadata>): 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 // URL sources (public HTTPS, Files API URIs, gs://) pass through as\n // `fileData` and Gemini fetches them server-side — same as the chat\n // adapter. Fetching locally and inlining as base64 double-buffers the\n // image and OOMs on memory-constrained runtimes (e.g. Cloudflare\n // Workers).\n return {\n fileData: {\n fileUri: part.source.value,\n mimeType: part.source.mimeType ?? 'image/jpeg',\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<GeminiAnyImageProviderOptions>,\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\n // Named picks, never a wholesale spread — the mirror image of the native\n // path below. A native-only field (safetySettings, thinkingConfig,\n // imageConfig, systemInstruction) belongs to GenerateContentConfig and is\n // rejected by generateImages with 400 INVALID_ARGUMENT, so it must not be\n // able to reach here even when the caller's `modelOptions` was typed\n // against both shapes at once (e.g. an adapter inferred from a union of\n // model names).\n return {\n numberOfImages: numberOfImages ?? 1,\n // Map size to aspect ratio if provided; modelOptions.aspectRatio,\n // picked after it, overrides.\n ...(sizeAspectRatio !== undefined && { aspectRatio: sizeAspectRatio }),\n ...(modelOptions?.aspectRatio !== undefined && {\n aspectRatio: modelOptions.aspectRatio,\n }),\n ...(modelOptions?.personGeneration !== undefined && {\n personGeneration: modelOptions.personGeneration,\n }),\n ...(modelOptions?.safetyFilterLevel !== undefined && {\n safetyFilterLevel: modelOptions.safetyFilterLevel,\n }),\n ...(modelOptions?.seed !== undefined && { seed: modelOptions.seed }),\n ...(modelOptions?.addWatermark !== undefined && {\n addWatermark: modelOptions.addWatermark,\n }),\n ...(modelOptions?.language !== undefined && {\n language: modelOptions.language,\n }),\n ...(modelOptions?.negativePrompt !== undefined && {\n negativePrompt: modelOptions.negativePrompt,\n }),\n ...(modelOptions?.outputMimeType !== undefined && {\n outputMimeType: modelOptions.outputMimeType,\n }),\n ...(modelOptions?.outputCompressionQuality !== undefined && {\n outputCompressionQuality: modelOptions.outputCompressionQuality,\n }),\n ...(modelOptions?.guidanceScale !== undefined && {\n guidanceScale: modelOptions.guidanceScale,\n }),\n ...(modelOptions?.enhancePrompt !== undefined && {\n enhancePrompt: modelOptions.enhancePrompt,\n }),\n ...(modelOptions?.includeSafetyAttributes !== undefined && {\n includeSafetyAttributes: modelOptions.includeSafetyAttributes,\n }),\n ...(modelOptions?.includeRaiReason !== undefined && {\n includeRaiReason: modelOptions.includeRaiReason,\n }),\n ...(modelOptions?.outputGcsUri !== undefined && {\n outputGcsUri: modelOptions.outputGcsUri,\n }),\n ...(modelOptions?.labels !== undefined && {\n labels: modelOptions.labels,\n }),\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/** @deprecated Shut down 2026-06-25. Use `gemini-3.1-flash-image`. */\nexport function createGeminiImage(\n model: 'gemini-3.1-flash-image-preview',\n apiKey: string,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<'gemini-3.1-flash-image-preview'>\n/** @deprecated Shut down 2026-06-25. Use `gemini-3-pro-image`. */\nexport function createGeminiImage(\n model: 'gemini-3-pro-image-preview',\n apiKey: string,\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<'gemini-3-pro-image-preview'>\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>\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/** @deprecated Shut down 2026-06-25. Use `gemini-3.1-flash-image`. */\nexport function geminiImage(\n model: 'gemini-3.1-flash-image-preview',\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<'gemini-3.1-flash-image-preview'>\n/** @deprecated Shut down 2026-06-25. Use `gemini-3-pro-image`. */\nexport function geminiImage(\n model: 'gemini-3-pro-image-preview',\n config?: Omit<GeminiImageConfig, 'apiKey'>,\n): GeminiImageAdapter<'gemini-3-pro-image-preview'>\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>\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"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","names":[],"sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { FinishReason } from '@google/genai'\nimport { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport { toRunErrorRawEvent } from '@tanstack/ai/adapter-internals'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { convertToolsToProviderFormat } from '../tools/tool-converter'\nimport { buildGeminiUsage } from '../usage'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport { GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS } from '../model-meta'\nimport type {\n GEMINI_MODELS,\n GeminiChatModelProviderOptionsByName,\n GeminiChatModelToolCapabilitiesByName,\n GeminiModelInputModalitiesByName,\n} from '../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type {\n Content,\n GenerateContentParameters,\n GenerateContentResponse,\n GoogleGenAI,\n Part,\n ThinkingLevel,\n} from '@google/genai'\nimport type {\n ContentPart,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { ExternalTextProviderOptions } from '../text/text-provider-options'\nimport type {\n GeminiMessageMetadataByModality,\n GeminiToolCallMetadata,\n} from '../message-types'\nimport type { GeminiClientConfig } from '../utils/client'\n\n/**\n * Configuration for Gemini text adapter\n */\nexport interface GeminiTextConfig extends GeminiClientConfig {}\n\n/**\n * Gemini-specific provider options for text/chat\n */\nexport type GeminiTextProviderOptions = ExternalTextProviderOptions\n\n// ===========================\n// Type Resolution Helpers\n// ===========================\n\n/**\n * Resolve provider options for a specific model.\n * If the model has explicit options in the map, use those; otherwise use base options.\n */\ntype ResolveProviderOptions<TModel extends string> =\n TModel extends keyof GeminiChatModelProviderOptionsByName\n ? GeminiChatModelProviderOptionsByName[TModel]\n : GeminiTextProviderOptions\n\n/**\n * Resolve input modalities for a specific model.\n * If the model has explicit modalities in the map, use those; otherwise use all modalities.\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.\n * If the model has explicit tools in the map, use those; otherwise use empty tuple.\n */\ntype ResolveToolCapabilities<TModel extends string> =\n TModel extends keyof GeminiChatModelToolCapabilitiesByName\n ? NonNullable<GeminiChatModelToolCapabilitiesByName[TModel]>\n : readonly []\n\n// ===========================\n// Adapter Implementation\n// ===========================\n\n/**\n * Gemini Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Gemini chat/text completion functionality.\n * Import only what you need for smaller bundle sizes.\n */\nexport class GeminiTextAdapter<\n TModel extends (typeof GEMINI_MODELS)[number],\n TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,\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 GeminiToolCallMetadata\n> {\n override readonly kind = 'text' as const\n readonly name = 'gemini' as const\n\n private readonly client: GoogleGenAI\n\n constructor(config: GeminiTextConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n }\n\n async *chatStream(\n options: TextOptions<GeminiTextProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const mappedOptions = this.mapCommonOptionsToGemini(options)\n const { logger } = options\n\n try {\n logger.request(\n `activity=chat provider=gemini model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n { provider: 'gemini', model: this.model },\n )\n const result =\n await this.client.models.generateContentStream(mappedOptions)\n\n yield* this.processStreamChunks(result, options, logger)\n } catch (error) {\n const rawEvent = toRunErrorRawEvent(error)\n logger.errors('gemini.chatStream fatal', {\n error,\n source: 'gemini.chatStream',\n })\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message:\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during the chat stream.',\n // Forward the provider's structured error body when present (see\n // toRunErrorRawEvent); omitted otherwise.\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message:\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during the chat stream.',\n },\n }\n }\n }\n\n /**\n * Generate structured output using Gemini's native JSON response format.\n * Uses responseMimeType: 'application/json' and responseSchema for structured output.\n * The outputSchema is already JSON Schema (converted in the ai layer).\n */\n async structuredOutput(\n options: StructuredOutputOptions<GeminiTextProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const { logger } = chatOptions\n\n const mappedOptions = this.mapCommonOptionsToGemini(chatOptions)\n\n try {\n logger.request(\n `activity=chat provider=gemini model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,\n { provider: 'gemini', model: this.model },\n )\n // Add structured output configuration\n const result = await this.client.models.generateContent({\n ...mappedOptions,\n config: {\n ...mappedOptions.config,\n responseMimeType: 'application/json',\n responseSchema: outputSchema,\n },\n })\n\n // Extract text content from the response\n const rawText = this.extractTextFromResponse(result)\n\n // Parse the JSON response\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 {\n data: parsed,\n rawText,\n usage: result.usageMetadata\n ? buildGeminiUsage(result.usageMetadata)\n : undefined,\n }\n } catch (error) {\n logger.errors('gemini.structuredOutput fatal', {\n error,\n source: 'gemini.structuredOutput',\n })\n throw new Error(\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during structured output generation.',\n )\n }\n }\n\n /**\n * Extract text content from a non-streaming response\n */\n private extractTextFromResponse(response: GenerateContentResponse): string {\n let textContent = ''\n\n if (response.candidates?.[0]?.content?.parts) {\n for (const part of response.candidates[0].content.parts) {\n if (part.text) {\n textContent += part.text\n }\n }\n }\n\n return textContent\n }\n\n private async *processStreamChunks(\n result: AsyncGenerator<GenerateContentResponse, unknown, unknown>,\n options: TextOptions<GeminiTextProviderOptions>,\n logger: InternalLogger,\n ): AsyncIterable<StreamChunk> {\n const model = options.model\n let accumulatedContent = ''\n let accumulatedThinking = ''\n const toolCallMap = new Map<\n string,\n {\n name: string\n args: string\n index: number\n started: boolean\n thoughtSignature?: string\n }\n >()\n let nextToolIndex = 0\n\n // AG-UI lifecycle tracking\n const runId = options.runId ?? generateId(this.name)\n const threadId = options.threadId ?? generateId(this.name)\n const messageId = generateId(this.name)\n let stepId: string | null = null\n let reasoningMessageId: string | null = null\n let hasClosedReasoning = false\n let hasEmittedRunStarted = false\n let hasEmittedTextMessageStart = false\n let hasEmittedStepStarted = false\n\n for await (const chunk of result) {\n logger.provider(`provider=gemini`, { chunk })\n // Emit RUN_STARTED on first chunk\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp: Date.now(),\n parentRunId: options.parentRunId,\n }\n }\n\n if (chunk.candidates?.[0]?.content?.parts) {\n const parts = chunk.candidates[0].content.parts\n\n for (const part of parts) {\n if (part.text) {\n if (part.thought) {\n // Emit STEP_STARTED and REASONING events on first thinking content\n if (!hasEmittedStepStarted) {\n hasEmittedStepStarted = true\n stepId = generateId(this.name)\n reasoningMessageId = generateId(this.name)\n\n // Spec REASONING events\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning' as const,\n model,\n timestamp: Date.now(),\n }\n\n // Legacy STEP events (kept during transition)\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model,\n timestamp: Date.now(),\n stepType: 'thinking',\n }\n }\n\n accumulatedThinking += part.text\n\n // Spec REASONING content event — reasoningMessageId is set in the\n // hasEmittedStepStarted block above (entered on the same `part.thought` path)\n if (!reasoningMessageId) continue\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: part.text,\n model,\n timestamp: Date.now(),\n }\n\n // Legacy STEP event\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId || generateId(this.name),\n stepId: stepId || generateId(this.name),\n model,\n timestamp: Date.now(),\n delta: part.text,\n content: accumulatedThinking,\n }\n } else if (part.text.trim()) {\n // Close reasoning before text starts\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n }\n\n // Skip whitespace-only text parts (e.g. \"\\n\" during auto-continuation)\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += part.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp: Date.now(),\n delta: part.text,\n content: accumulatedContent,\n }\n }\n }\n\n const functionCall = part.functionCall\n if (functionCall) {\n const toolCallId =\n functionCall.id ||\n `${functionCall.name}_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\n // Gemini emits thoughtSignature as a Part-level sibling of\n // functionCall (per @google/genai Part type), not nested inside\n // functionCall itself.\n const partThoughtSignature = part.thoughtSignature || undefined\n\n let toolCallData = toolCallMap.get(toolCallId)\n if (!toolCallData) {\n toolCallData = {\n name: functionCall.name || '',\n args:\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs),\n index: nextToolIndex++,\n started: false,\n // Only set thoughtSignature when present — under EOPT, the\n // optional field cannot accept an explicit `undefined`.\n ...(partThoughtSignature !== undefined && {\n thoughtSignature: partThoughtSignature,\n }),\n }\n toolCallMap.set(toolCallId, toolCallData)\n } else {\n if (!toolCallData.thoughtSignature && partThoughtSignature) {\n toolCallData.thoughtSignature = partThoughtSignature\n }\n try {\n const existingArgs = JSON.parse(toolCallData.args)\n const newArgs =\n typeof functionArgs === 'string'\n ? JSON.parse(functionArgs)\n : functionArgs\n const mergedArgs = { ...existingArgs, ...newArgs }\n toolCallData.args = JSON.stringify(mergedArgs)\n } catch {\n toolCallData.args =\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs)\n }\n }\n\n // Emit TOOL_CALL_START if not already started\n if (!toolCallData.started) {\n toolCallData.started = true\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName: toolCallData.name,\n toolName: toolCallData.name,\n parentMessageId: messageId,\n model,\n timestamp: Date.now(),\n index: toolCallData.index,\n ...(toolCallData.thoughtSignature && {\n metadata: {\n thoughtSignature: toolCallData.thoughtSignature,\n } satisfies GeminiToolCallMetadata,\n }),\n }\n }\n\n // Emit TOOL_CALL_ARGS\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId,\n model,\n timestamp: Date.now(),\n delta: toolCallData.args,\n args: toolCallData.args,\n }\n }\n }\n } else if (chunk.data && chunk.data.trim()) {\n // Skip whitespace-only data (e.g. \"\\n\" during auto-continuation)\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += chunk.data\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp: Date.now(),\n delta: chunk.data,\n content: accumulatedContent,\n }\n }\n\n if (chunk.candidates?.[0]?.finishReason) {\n const finishReason = chunk.candidates[0].finishReason\n\n if (finishReason === FinishReason.UNEXPECTED_TOOL_CALL) {\n if (chunk.candidates[0].content?.parts) {\n for (const part of chunk.candidates[0].content.parts) {\n const functionCall = part.functionCall\n if (functionCall) {\n const toolCallId =\n functionCall.id ||\n `${functionCall.name}_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\n const argsString =\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs)\n\n toolCallMap.set(toolCallId, {\n name: functionCall.name || '',\n args: argsString,\n index: nextToolIndex++,\n started: true,\n })\n\n // Emit TOOL_CALL_START\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName: functionCall.name || '',\n toolName: functionCall.name || '',\n parentMessageId: messageId,\n model,\n timestamp: Date.now(),\n index: nextToolIndex - 1,\n }\n\n // Emit TOOL_CALL_END with parsed input\n let parsedInput: unknown = {}\n try {\n const parsed =\n typeof functionArgs === 'string'\n ? JSON.parse(functionArgs)\n : functionArgs\n parsedInput =\n parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n parsedInput = {}\n }\n\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolCallName: functionCall.name || '',\n toolName: functionCall.name || '',\n model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n }\n }\n }\n }\n\n // Emit TOOL_CALL_END for all tracked tool calls\n for (const [toolCallId, toolCallData] of toolCallMap.entries()) {\n let parsedInput: unknown = {}\n try {\n const parsed = JSON.parse(toolCallData.args)\n parsedInput = parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n parsedInput = {}\n }\n\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolCallName: toolCallData.name,\n toolName: toolCallData.name,\n model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n }\n\n // Reset so a new TEXT_MESSAGE_START is emitted if text follows tool calls\n if (toolCallMap.size > 0) {\n hasEmittedTextMessageStart = false\n }\n\n if (finishReason === FinishReason.MAX_TOKENS) {\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model,\n timestamp: Date.now(),\n message:\n 'The response was cut off because the maximum token limit was reached.',\n code: 'max_tokens',\n error: {\n message:\n 'The response was cut off because the maximum token limit was reached.',\n code: 'max_tokens',\n },\n }\n }\n\n // Close reasoning events if still open\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n }\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model,\n timestamp: Date.now(),\n }\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: Date.now(),\n finishReason: toolCallMap.size > 0 ? 'tool_calls' : 'stop',\n // RunFinishedEvent.usage is `usage?: {...}` (no `| undefined`) under\n // exactOptionalPropertyTypes; only include it when usageMetadata is\n // present rather than assigning an explicit `undefined`.\n ...(chunk.usageMetadata && {\n usage: buildGeminiUsage(chunk.usageMetadata),\n }),\n }\n }\n }\n }\n\n private convertContentPartToGemini(part: ContentPart): Part {\n switch (part.type) {\n case 'text':\n return { text: part.content }\n case 'image':\n case 'audio':\n case 'video':\n case 'document': {\n if (part.source.type === 'data') {\n return {\n inlineData: {\n data: part.source.value,\n mimeType: part.source.mimeType,\n },\n }\n } else {\n // For URL sources, use provided mimeType or fall back to reasonable defaults\n const defaultMimeType = {\n image: 'image/jpeg',\n audio: 'audio/mp3',\n video: 'video/mp4',\n document: 'application/pdf',\n }[part.type]\n\n return {\n fileData: {\n fileUri: part.source.value,\n mimeType: part.source.mimeType ?? defaultMimeType,\n },\n }\n }\n }\n default: {\n const _exhaustiveCheck: never = part\n throw new Error(\n `Unsupported content part type: ${(_exhaustiveCheck as ContentPart).type}`,\n )\n }\n }\n }\n\n private formatMessages(\n messages: Array<ModelMessage>,\n ): GenerateContentParameters['contents'] {\n // Build a lookup from toolCallId → function name so functionResponse uses the\n // correct name instead of the raw call ID.\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 formatted = messages.map((msg) => {\n const role: 'user' | 'model' = msg.role === 'assistant' ? 'model' : 'user'\n const parts: Array<Part> = []\n\n if (Array.isArray(msg.content)) {\n for (const contentPart of msg.content) {\n parts.push(this.convertContentPartToGemini(contentPart))\n }\n } else if (msg.content && msg.role !== 'tool') {\n parts.push({ text: msg.content })\n }\n\n if (msg.role === 'assistant' && msg.toolCalls?.length) {\n for (const toolCall of msg.toolCalls) {\n let parsedArgs: Record<string, unknown> = {}\n try {\n parsedArgs = toolCall.function.arguments\n ? (JSON.parse(toolCall.function.arguments) as Record<\n string,\n unknown\n >)\n : {}\n } catch {\n parsedArgs = {}\n }\n\n const thoughtSignature = (\n toolCall.metadata as GeminiToolCallMetadata | undefined\n )?.thoughtSignature\n // Gemini requires thoughtSignature at the Part level (sibling of\n // functionCall), not nested inside functionCall. Nesting it causes\n // the API to reject the next turn with\n // \"Function call is missing a thought_signature\".\n const part: Part = {\n functionCall: {\n id: toolCall.id,\n name: toolCall.function.name,\n args: parsedArgs,\n },\n }\n if (thoughtSignature) {\n part.thoughtSignature = thoughtSignature\n }\n parts.push(part)\n }\n }\n\n if (msg.role === 'tool' && msg.toolCallId) {\n const functionName =\n toolCallIdToName.get(msg.toolCallId) || msg.toolCallId\n const toolContent = msg.content\n if (Array.isArray(toolContent)) {\n const textChunks: Array<string> = []\n const mediaParts: Array<Part> = []\n for (const part of toolContent) {\n if (part.type === 'text') {\n textChunks.push(part.content)\n } else if (part.source.type === 'data') {\n mediaParts.push({\n inlineData: {\n data: part.source.value,\n mimeType: part.source.mimeType,\n },\n })\n } else {\n const defaultMimeType = {\n image: 'image/jpeg',\n audio: 'audio/mp3',\n video: 'video/mp4',\n document: 'application/pdf',\n }[part.type]\n mediaParts.push({\n fileData: {\n fileUri: part.source.value,\n mimeType: part.source.mimeType ?? defaultMimeType,\n },\n })\n }\n }\n parts.push({\n functionResponse: {\n id: msg.toolCallId,\n name: functionName,\n response: { content: textChunks.join('\\n') },\n ...(mediaParts.length > 0 && { parts: mediaParts }),\n },\n })\n } else {\n parts.push({\n functionResponse: {\n id: msg.toolCallId,\n name: functionName,\n response: { content: toolContent || '' },\n },\n })\n }\n }\n\n return {\n role,\n parts: parts.length > 0 ? parts : [{ text: '' }],\n }\n })\n\n // Post-process: Gemini requires strictly alternating user/model roles.\n // Tool results are mapped to role:'user', which can create consecutive\n // user messages when followed by a new user message. Merge them.\n return this.mergeConsecutiveSameRoleMessages(formatted)\n }\n\n /**\n * Merge consecutive messages of the same role into a single message.\n * Gemini's API requires strictly alternating user/model roles.\n * Tool results are mapped to role:'user', which can collide with actual\n * user messages in multi-turn conversations.\n *\n * Also filters out empty model messages (e.g., from a previous failed request)\n * and deduplicates functionResponse parts with the same name (tool call ID).\n */\n private mergeConsecutiveSameRoleMessages(\n messages: Array<Content>,\n ): Array<Content> {\n const merged: Array<Content> = []\n\n for (const msg of messages) {\n const parts = msg.parts || []\n\n // Skip empty model messages (no parts or only empty text)\n if (msg.role === 'model') {\n const hasContent =\n parts.length > 0 &&\n !parts.every(\n (p) => 'text' in p && (p as { text: string }).text === '',\n )\n if (!hasContent) {\n continue\n }\n }\n\n const prev = merged[merged.length - 1]\n if (prev && prev.role === msg.role) {\n // Merge parts arrays\n prev.parts = [...(prev.parts || []), ...parts]\n } else {\n merged.push({ ...msg, parts: [...parts] })\n }\n }\n\n // Deduplicate functionResponse parts with the same name (tool call ID)\n for (const msg of merged) {\n if (!msg.parts) continue\n const seenFunctionResponseNames = new Set<string>()\n msg.parts = msg.parts.filter((part) => {\n if ('functionResponse' in part && part.functionResponse?.name) {\n if (seenFunctionResponseNames.has(part.functionResponse.name)) {\n return false\n }\n seenFunctionResponseNames.add(part.functionResponse.name)\n }\n return true\n })\n }\n\n return merged\n }\n\n private mapCommonOptionsToGemini(\n options: TextOptions<GeminiTextProviderOptions>,\n ) {\n // Separate `thinkingConfig` from the other model options so the loose\n // local `thinkingLevel?: keyof typeof ThinkingLevel` type doesn't leak\n // into the SDK config object via the `...modelOpts` spread — we re-add a\n // properly-typed `ThinkingConfig` below.\n const { thinkingConfig, ...modelOpts } = options.modelOptions ?? {}\n // Build the thinkingConfig payload only when the caller actually supplied\n // one. Our local `thinkingLevel` is typed as `keyof typeof ThinkingLevel`\n // (string union) so users can pass plain strings; the SDK target is the\n // `ThinkingLevel` enum, and every field is `field?: T` under EOPT — so we\n // re-emit fields via conditional spreads.\n const mappedThinkingConfig = thinkingConfig\n ? {\n ...(thinkingConfig.includeThoughts !== undefined && {\n includeThoughts: thinkingConfig.includeThoughts,\n }),\n ...(thinkingConfig.thinkingBudget !== undefined && {\n thinkingBudget: thinkingConfig.thinkingBudget,\n }),\n ...(thinkingConfig.thinkingLevel\n ? {\n thinkingLevel: thinkingConfig.thinkingLevel as ThinkingLevel,\n }\n : {}),\n }\n : undefined\n\n const normalizedPrompts = normalizeSystemPrompts(options.systemPrompts)\n const systemInstruction =\n normalizedPrompts.length > 0\n ? normalizedPrompts.map((p) => p.content).join('\\n')\n : undefined\n\n // Native combined mode (issue #605): when the engine threads\n // `outputSchema` through TextOptions, the adapter declared\n // `supportsCombinedToolsAndSchema` (Gemini 3.x only). The schema is\n // already JSON Schema (pre-converted at the activity boundary). Wire\n // it into `config.responseSchema` + `responseMimeType: 'application/json'`\n // alongside any `tools` — the model emits function calls during the\n // agent loop and the schema-constrained JSON on its natural final\n // turn, so the engine can harvest it without the separate\n // `structuredOutput` finalization round-trip.\n const combinedSchema = options.outputSchema as\n | Record<string, unknown>\n | undefined\n const combinedSchemaConfig = combinedSchema\n ? {\n responseMimeType: 'application/json' as const,\n responseSchema: combinedSchema,\n }\n : undefined\n\n // Vendor `GenerateContentConfig` fields are `field?: T` (no `| undefined`)\n // under EOPT, so spread each common option only when present rather than\n // emitting `field: undefined`s into the wire payload.\n const requestOptions: GenerateContentParameters = {\n model: options.model,\n contents: this.formatMessages(options.messages),\n config: {\n ...modelOpts,\n ...(mappedThinkingConfig !== undefined && {\n thinkingConfig: mappedThinkingConfig,\n }),\n ...(systemInstruction !== undefined && { systemInstruction }),\n tools: convertToolsToProviderFormat(options.tools),\n ...(combinedSchemaConfig ?? {}),\n },\n }\n\n return requestOptions\n }\n\n /**\n * Gemini 3.x natively combines `tools` + `responseSchema` in a single\n * streaming `generateContentStream` call (issue #605). Gemini 2.x is\n * documented as brittle for the combination and keeps the engine's\n * legacy finalization path.\n */\n supportsCombinedToolsAndSchema(): boolean {\n return GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS.has(this.model)\n }\n}\n\n/**\n * Creates a Gemini text adapter with explicit API key.\n * Type resolution happens here at the call site.\n */\nexport function createGeminiChat<TModel extends (typeof GEMINI_MODELS)[number]>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiTextConfig, 'apiKey'>,\n): GeminiTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>,\n ResolveToolCapabilities<TModel>\n> {\n return new GeminiTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini text adapter with automatic API key detection.\n * Type resolution happens here at the call site.\n */\nexport function geminiText<TModel extends (typeof GEMINI_MODELS)[number]>(\n model: TModel,\n config?: Omit<GeminiTextConfig, 'apiKey'>,\n): GeminiTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>,\n ResolveToolCapabilities<TModel>\n> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiChat(model, apiKey, config)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"text.js","names":[],"sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { FinishReason } from '@google/genai'\nimport { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport { toRunErrorRawEvent } from '@tanstack/ai/adapter-internals'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { convertToolsToProviderFormat } from '../tools/tool-converter'\nimport { buildGeminiUsage } from '../usage'\nimport {\n createGeminiClient,\n generateId,\n getGeminiApiKeyFromEnv,\n} from '../utils'\nimport { GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS } from '../model-meta'\nimport type {\n GEMINI_MODELS,\n GeminiChatModelProviderOptionsByName,\n GeminiChatModelToolCapabilitiesByName,\n GeminiModelInputModalitiesByName,\n} from '../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type {\n Content,\n GenerateContentParameters,\n GenerateContentResponse,\n GoogleGenAI,\n Part,\n ThinkingLevel,\n} from '@google/genai'\nimport type {\n ContentPart,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { ExternalTextProviderOptions } from '../text/text-provider-options'\nimport type {\n GeminiMessageMetadataByModality,\n GeminiToolCallMetadata,\n} from '../message-types'\nimport type { GeminiClientConfig } from '../utils/client'\n\n/**\n * Configuration for Gemini text adapter\n */\nexport interface GeminiTextConfig extends GeminiClientConfig {}\n\n/**\n * Gemini-specific provider options for text/chat\n */\nexport type GeminiTextProviderOptions = ExternalTextProviderOptions\n\n// ===========================\n// Type Resolution Helpers\n// ===========================\n\n/**\n * Resolve provider options for a specific model.\n * If the model has explicit options in the map, use those; otherwise use base options.\n */\ntype ResolveProviderOptions<TModel extends string> =\n TModel extends keyof GeminiChatModelProviderOptionsByName\n ? GeminiChatModelProviderOptionsByName[TModel]\n : GeminiTextProviderOptions\n\n/**\n * Resolve input modalities for a specific model.\n * If the model has explicit modalities in the map, use those; otherwise use all modalities.\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.\n * If the model has explicit tools in the map, use those; otherwise use empty tuple.\n */\ntype ResolveToolCapabilities<TModel extends string> =\n TModel extends keyof GeminiChatModelToolCapabilitiesByName\n ? NonNullable<GeminiChatModelToolCapabilitiesByName[TModel]>\n : readonly []\n\n// ===========================\n// Adapter Implementation\n// ===========================\n\n/**\n * Gemini Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Gemini chat/text completion functionality.\n * Import only what you need for smaller bundle sizes.\n */\nexport class GeminiTextAdapter<\n TModel extends (typeof GEMINI_MODELS)[number],\n TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,\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 GeminiToolCallMetadata\n> {\n override readonly kind = 'text' as const\n readonly name = 'gemini' as const\n\n private readonly client: GoogleGenAI\n\n constructor(config: GeminiTextConfig, model: TModel) {\n super({}, model)\n this.client = createGeminiClient(config)\n }\n\n async *chatStream(\n options: TextOptions<GeminiTextProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const mappedOptions = this.mapCommonOptionsToGemini(options)\n const { logger } = options\n\n try {\n logger.request(\n `activity=chat provider=gemini model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n { provider: 'gemini', model: this.model },\n )\n const result =\n await this.client.models.generateContentStream(mappedOptions)\n\n yield* this.processStreamChunks(result, options, logger)\n } catch (error) {\n const rawEvent = toRunErrorRawEvent(error)\n logger.errors('gemini.chatStream fatal', {\n error,\n source: 'gemini.chatStream',\n })\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message:\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during the chat stream.',\n // Forward the provider's structured error body when present (see\n // toRunErrorRawEvent); omitted otherwise.\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message:\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during the chat stream.',\n },\n }\n }\n }\n\n /**\n * Generate structured output using Gemini's native JSON response format.\n * Uses responseMimeType: 'application/json' and responseSchema for structured output.\n * The outputSchema is already JSON Schema (converted in the ai layer).\n */\n async structuredOutput(\n options: StructuredOutputOptions<GeminiTextProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const { logger } = chatOptions\n\n const mappedOptions = this.mapCommonOptionsToGemini(chatOptions)\n\n try {\n logger.request(\n `activity=chat provider=gemini model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,\n { provider: 'gemini', model: this.model },\n )\n // Add structured output configuration\n const result = await this.client.models.generateContent({\n ...mappedOptions,\n config: {\n ...mappedOptions.config,\n responseMimeType: 'application/json',\n responseSchema: outputSchema,\n },\n })\n\n // Extract text content from the response\n const rawText = this.extractTextFromResponse(result)\n\n // Parse the JSON response\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 {\n data: parsed,\n rawText,\n usage: result.usageMetadata\n ? buildGeminiUsage(result.usageMetadata)\n : undefined,\n }\n } catch (error) {\n logger.errors('gemini.structuredOutput fatal', {\n error,\n source: 'gemini.structuredOutput',\n })\n throw new Error(\n error instanceof Error\n ? error.message\n : 'An unknown error occurred during structured output generation.',\n )\n }\n }\n\n /**\n * Extract text content from a non-streaming response\n */\n private extractTextFromResponse(response: GenerateContentResponse): string {\n let textContent = ''\n\n if (response.candidates?.[0]?.content?.parts) {\n for (const part of response.candidates[0].content.parts) {\n if (part.text) {\n textContent += part.text\n }\n }\n }\n\n return textContent\n }\n\n private async *processStreamChunks(\n result: AsyncGenerator<GenerateContentResponse, unknown, unknown>,\n options: TextOptions<GeminiTextProviderOptions>,\n logger: InternalLogger,\n ): AsyncIterable<StreamChunk> {\n const model = options.model\n let accumulatedContent = ''\n let accumulatedThinking = ''\n const toolCallMap = new Map<\n string,\n {\n name: string\n args: string\n index: number\n started: boolean\n thoughtSignature?: string\n }\n >()\n let nextToolIndex = 0\n\n // AG-UI lifecycle tracking\n const runId = options.runId ?? generateId(this.name)\n const threadId = options.threadId ?? generateId(this.name)\n const messageId = generateId(this.name)\n let stepId: string | null = null\n let reasoningMessageId: string | null = null\n let hasClosedReasoning = false\n let hasEmittedRunStarted = false\n let hasEmittedTextMessageStart = false\n let hasEmittedStepStarted = false\n\n for await (const chunk of result) {\n logger.provider(`provider=gemini`, { chunk })\n // Emit RUN_STARTED on first chunk\n if (!hasEmittedRunStarted) {\n hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp: Date.now(),\n parentRunId: options.parentRunId,\n }\n }\n\n if (chunk.candidates?.[0]?.content?.parts) {\n const parts = chunk.candidates[0].content.parts\n\n for (const part of parts) {\n if (part.text) {\n if (part.thought) {\n // Emit STEP_STARTED and REASONING events on first thinking content\n if (!hasEmittedStepStarted) {\n hasEmittedStepStarted = true\n stepId = generateId(this.name)\n reasoningMessageId = generateId(this.name)\n\n // Spec REASONING events\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning' as const,\n model,\n timestamp: Date.now(),\n }\n\n // Legacy STEP events (kept during transition)\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model,\n timestamp: Date.now(),\n stepType: 'thinking',\n }\n }\n\n accumulatedThinking += part.text\n\n // Spec REASONING content event — reasoningMessageId is set in the\n // hasEmittedStepStarted block above (entered on the same `part.thought` path)\n if (!reasoningMessageId) continue\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: part.text,\n model,\n timestamp: Date.now(),\n }\n\n // Legacy STEP event\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId || generateId(this.name),\n stepId: stepId || generateId(this.name),\n model,\n timestamp: Date.now(),\n delta: part.text,\n content: accumulatedThinking,\n }\n } else if (part.text.trim()) {\n // Close reasoning before text starts\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n }\n\n // Skip whitespace-only text parts (e.g. \"\\n\" during auto-continuation)\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += part.text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp: Date.now(),\n delta: part.text,\n content: accumulatedContent,\n }\n }\n }\n\n const functionCall = part.functionCall\n if (functionCall) {\n const toolCallId =\n functionCall.id ||\n `${functionCall.name}_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\n // Gemini emits thoughtSignature as a Part-level sibling of\n // functionCall (per @google/genai Part type), not nested inside\n // functionCall itself.\n const partThoughtSignature = part.thoughtSignature || undefined\n\n let toolCallData = toolCallMap.get(toolCallId)\n if (!toolCallData) {\n toolCallData = {\n name: functionCall.name || '',\n args:\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs),\n index: nextToolIndex++,\n started: false,\n // Only set thoughtSignature when present — under EOPT, the\n // optional field cannot accept an explicit `undefined`.\n ...(partThoughtSignature !== undefined && {\n thoughtSignature: partThoughtSignature,\n }),\n }\n toolCallMap.set(toolCallId, toolCallData)\n } else {\n if (!toolCallData.thoughtSignature && partThoughtSignature) {\n toolCallData.thoughtSignature = partThoughtSignature\n }\n try {\n const existingArgs = JSON.parse(toolCallData.args)\n const newArgs =\n typeof functionArgs === 'string'\n ? JSON.parse(functionArgs)\n : functionArgs\n const mergedArgs = { ...existingArgs, ...newArgs }\n toolCallData.args = JSON.stringify(mergedArgs)\n } catch {\n toolCallData.args =\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs)\n }\n }\n\n // Emit TOOL_CALL_START if not already started\n if (!toolCallData.started) {\n toolCallData.started = true\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName: toolCallData.name,\n toolName: toolCallData.name,\n parentMessageId: messageId,\n model,\n timestamp: Date.now(),\n index: toolCallData.index,\n ...(toolCallData.thoughtSignature && {\n metadata: {\n thoughtSignature: toolCallData.thoughtSignature,\n } satisfies GeminiToolCallMetadata,\n }),\n }\n }\n\n // Emit TOOL_CALL_ARGS\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId,\n model,\n timestamp: Date.now(),\n delta: toolCallData.args,\n args: toolCallData.args,\n }\n }\n }\n } else if (chunk.data && chunk.data.trim()) {\n // Skip whitespace-only data (e.g. \"\\n\" during auto-continuation)\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId,\n model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += chunk.data\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId,\n model,\n timestamp: Date.now(),\n delta: chunk.data,\n content: accumulatedContent,\n }\n }\n\n if (chunk.candidates?.[0]?.finishReason) {\n const finishReason = chunk.candidates[0].finishReason\n\n if (finishReason === FinishReason.UNEXPECTED_TOOL_CALL) {\n if (chunk.candidates[0].content?.parts) {\n for (const part of chunk.candidates[0].content.parts) {\n const functionCall = part.functionCall\n if (functionCall) {\n const toolCallId =\n functionCall.id ||\n `${functionCall.name}_${Date.now()}_${nextToolIndex}`\n const functionArgs = functionCall.args || {}\n\n const argsString =\n typeof functionArgs === 'string'\n ? functionArgs\n : JSON.stringify(functionArgs)\n\n toolCallMap.set(toolCallId, {\n name: functionCall.name || '',\n args: argsString,\n index: nextToolIndex++,\n started: true,\n })\n\n // Emit TOOL_CALL_START\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName: functionCall.name || '',\n toolName: functionCall.name || '',\n parentMessageId: messageId,\n model,\n timestamp: Date.now(),\n index: nextToolIndex - 1,\n }\n\n // Emit TOOL_CALL_END with parsed input\n let parsedInput: unknown = {}\n try {\n const parsed =\n typeof functionArgs === 'string'\n ? JSON.parse(functionArgs)\n : functionArgs\n parsedInput =\n parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n parsedInput = {}\n }\n\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolCallName: functionCall.name || '',\n toolName: functionCall.name || '',\n model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n }\n }\n }\n }\n\n // Emit TOOL_CALL_END for all tracked tool calls\n for (const [toolCallId, toolCallData] of toolCallMap.entries()) {\n let parsedInput: unknown = {}\n try {\n const parsed = JSON.parse(toolCallData.args)\n parsedInput = parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n parsedInput = {}\n }\n\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolCallName: toolCallData.name,\n toolName: toolCallData.name,\n model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n }\n\n // Reset so a new TEXT_MESSAGE_START is emitted if text follows tool calls\n if (toolCallMap.size > 0) {\n hasEmittedTextMessageStart = false\n }\n\n if (finishReason === FinishReason.MAX_TOKENS) {\n yield {\n type: EventType.RUN_ERROR,\n runId,\n model,\n timestamp: Date.now(),\n message:\n 'The response was cut off because the maximum token limit was reached.',\n code: 'max_tokens',\n error: {\n message:\n 'The response was cut off because the maximum token limit was reached.',\n code: 'max_tokens',\n },\n }\n }\n\n // Close reasoning events if still open\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model,\n timestamp: Date.now(),\n }\n }\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId,\n model,\n timestamp: Date.now(),\n }\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: Date.now(),\n finishReason: toolCallMap.size > 0 ? 'tool_calls' : 'stop',\n // RunFinishedEvent.usage is `usage?: {...}` (no `| undefined`) under\n // exactOptionalPropertyTypes; only include it when usageMetadata is\n // present rather than assigning an explicit `undefined`.\n ...(chunk.usageMetadata && {\n usage: buildGeminiUsage(chunk.usageMetadata),\n }),\n }\n }\n }\n }\n\n private convertContentPartToGemini(part: ContentPart): Part {\n switch (part.type) {\n case 'text':\n return { text: part.content }\n case 'image':\n case 'audio':\n case 'video':\n case 'document': {\n if (part.source.type === 'data') {\n return {\n inlineData: {\n data: part.source.value,\n mimeType: part.source.mimeType,\n },\n }\n } else {\n // For URL sources, use provided mimeType or fall back to reasonable defaults\n const defaultMimeType = {\n image: 'image/jpeg',\n audio: 'audio/mp3',\n video: 'video/mp4',\n document: 'application/pdf',\n }[part.type]\n\n return {\n fileData: {\n fileUri: part.source.value,\n mimeType: part.source.mimeType ?? defaultMimeType,\n },\n }\n }\n }\n default: {\n const _exhaustiveCheck: never = part\n throw new Error(\n `Unsupported content part type: ${(_exhaustiveCheck as ContentPart).type}`,\n )\n }\n }\n }\n\n private formatMessages(\n messages: Array<ModelMessage>,\n ): GenerateContentParameters['contents'] {\n // Build a lookup from toolCallId → function name so functionResponse uses the\n // correct name instead of the raw call ID.\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 formatted = messages.map((msg) => {\n const role: 'user' | 'model' = msg.role === 'assistant' ? 'model' : 'user'\n const parts: Array<Part> = []\n\n if (Array.isArray(msg.content)) {\n for (const contentPart of msg.content) {\n parts.push(this.convertContentPartToGemini(contentPart))\n }\n } else if (msg.content && msg.role !== 'tool') {\n parts.push({ text: msg.content })\n }\n\n if (msg.role === 'assistant' && msg.toolCalls?.length) {\n for (const toolCall of msg.toolCalls) {\n let parsedArgs: Record<string, unknown> = {}\n try {\n parsedArgs = toolCall.function.arguments\n ? (JSON.parse(toolCall.function.arguments) as Record<\n string,\n unknown\n >)\n : {}\n } catch {\n parsedArgs = {}\n }\n\n const thoughtSignature = (\n toolCall.metadata as GeminiToolCallMetadata | undefined\n )?.thoughtSignature\n // Gemini requires thoughtSignature at the Part level (sibling of\n // functionCall), not nested inside functionCall. Nesting it causes\n // the API to reject the next turn with\n // \"Function call is missing a thought_signature\".\n const part: Part = {\n functionCall: {\n id: toolCall.id,\n name: toolCall.function.name,\n args: parsedArgs,\n },\n }\n if (thoughtSignature) {\n part.thoughtSignature = thoughtSignature\n }\n parts.push(part)\n }\n }\n\n if (msg.role === 'tool' && msg.toolCallId) {\n const functionName =\n toolCallIdToName.get(msg.toolCallId) || msg.toolCallId\n const toolContent = msg.content\n if (Array.isArray(toolContent)) {\n const textChunks: Array<string> = []\n const mediaParts: Array<Part> = []\n for (const part of toolContent) {\n if (part.type === 'text') {\n textChunks.push(part.content)\n } else if (part.source.type === 'data') {\n mediaParts.push({\n inlineData: {\n data: part.source.value,\n mimeType: part.source.mimeType,\n },\n })\n } else {\n const defaultMimeType = {\n image: 'image/jpeg',\n audio: 'audio/mp3',\n video: 'video/mp4',\n document: 'application/pdf',\n }[part.type]\n mediaParts.push({\n fileData: {\n fileUri: part.source.value,\n mimeType: part.source.mimeType ?? defaultMimeType,\n },\n })\n }\n }\n parts.push({\n functionResponse: {\n id: msg.toolCallId,\n name: functionName,\n response: { content: textChunks.join('\\n') },\n ...(mediaParts.length > 0 && { parts: mediaParts }),\n },\n })\n } else {\n parts.push({\n functionResponse: {\n id: msg.toolCallId,\n name: functionName,\n response: { content: toolContent || '' },\n },\n })\n }\n }\n\n return {\n role,\n parts: parts.length > 0 ? parts : [{ text: '' }],\n }\n })\n\n // Post-process: Gemini requires strictly alternating user/model roles.\n // Tool results are mapped to role:'user', which can create consecutive\n // user messages when followed by a new user message. Merge them.\n return this.mergeConsecutiveSameRoleMessages(formatted)\n }\n\n /**\n * Merge consecutive messages of the same role into a single message.\n * Gemini's API requires strictly alternating user/model roles.\n * Tool results are mapped to role:'user', which can collide with actual\n * user messages in multi-turn conversations.\n *\n * Also filters out empty model messages (e.g., from a previous failed request)\n * and deduplicates functionResponse parts with the same name (tool call ID).\n */\n private mergeConsecutiveSameRoleMessages(\n messages: Array<Content>,\n ): Array<Content> {\n const merged: Array<Content> = []\n\n for (const msg of messages) {\n const parts = msg.parts || []\n\n // Skip empty model messages (no parts or only empty text)\n if (msg.role === 'model') {\n const hasContent =\n parts.length > 0 &&\n !parts.every(\n (p) => 'text' in p && (p as { text: string }).text === '',\n )\n if (!hasContent) {\n continue\n }\n }\n\n const prev = merged[merged.length - 1]\n if (prev && prev.role === msg.role) {\n // Merge parts arrays\n prev.parts = [...(prev.parts || []), ...parts]\n } else {\n merged.push({ ...msg, parts: [...parts] })\n }\n }\n\n // Deduplicate functionResponse parts with the same name (tool call ID)\n for (const msg of merged) {\n if (!msg.parts) continue\n const seenFunctionResponseNames = new Set<string>()\n msg.parts = msg.parts.filter((part) => {\n if ('functionResponse' in part && part.functionResponse?.name) {\n if (seenFunctionResponseNames.has(part.functionResponse.name)) {\n return false\n }\n seenFunctionResponseNames.add(part.functionResponse.name)\n }\n return true\n })\n }\n\n return merged\n }\n\n private mapCommonOptionsToGemini(\n options: TextOptions<GeminiTextProviderOptions>,\n ) {\n // Separate `thinkingConfig` from the other model options so the loose\n // local `thinkingLevel?: keyof typeof ThinkingLevel` type doesn't leak\n // into the SDK config object via the `...modelOpts` spread — we re-add a\n // properly-typed `ThinkingConfig` below.\n const { thinkingConfig, ...modelOpts } = options.modelOptions ?? {}\n // Build the thinkingConfig payload only when the caller actually supplied\n // one. Our local `thinkingLevel` is typed as `keyof typeof ThinkingLevel`\n // (string union) so users can pass plain strings; the SDK target is the\n // `ThinkingLevel` enum, and every field is `field?: T` under EOPT — so we\n // re-emit fields via conditional spreads.\n const mappedThinkingConfig = thinkingConfig\n ? {\n ...(thinkingConfig.includeThoughts !== undefined && {\n includeThoughts: thinkingConfig.includeThoughts,\n }),\n ...(thinkingConfig.thinkingBudget !== undefined && {\n thinkingBudget: thinkingConfig.thinkingBudget,\n }),\n ...(thinkingConfig.thinkingLevel\n ? {\n thinkingLevel: thinkingConfig.thinkingLevel as ThinkingLevel,\n }\n : {}),\n }\n : undefined\n\n const normalizedPrompts = normalizeSystemPrompts(options.systemPrompts)\n const systemInstruction =\n normalizedPrompts.length > 0\n ? normalizedPrompts.map((p) => p.content).join('\\n')\n : undefined\n\n // Native combined mode (issue #605): when the engine threads\n // `outputSchema` through TextOptions, the adapter declared\n // `supportsCombinedToolsAndSchema` (Gemini 3.x only). The schema is\n // already JSON Schema (pre-converted at the activity boundary). Wire\n // it into `config.responseSchema` + `responseMimeType: 'application/json'`\n // alongside any `tools` — the model emits function calls during the\n // agent loop and the schema-constrained JSON on its natural final\n // turn, so the engine can harvest it without the separate\n // `structuredOutput` finalization round-trip.\n const combinedSchema = options.outputSchema as\n | Record<string, unknown>\n | undefined\n const combinedSchemaConfig = combinedSchema\n ? {\n responseMimeType: 'application/json' as const,\n responseSchema: combinedSchema,\n }\n : undefined\n\n // Vendor `GenerateContentConfig` fields are `field?: T` (no `| undefined`)\n // under EOPT, so spread each common option only when present rather than\n // emitting `field: undefined`s into the wire payload.\n const requestOptions: GenerateContentParameters = {\n model: options.model,\n contents: this.formatMessages(options.messages),\n config: {\n ...modelOpts,\n ...(mappedThinkingConfig !== undefined && {\n thinkingConfig: mappedThinkingConfig,\n }),\n ...(systemInstruction !== undefined && { systemInstruction }),\n tools: convertToolsToProviderFormat(options.tools),\n ...(combinedSchemaConfig ?? {}),\n },\n }\n\n return requestOptions\n }\n\n /**\n * Gemini 3.x natively combines `tools` + `responseSchema` in a single\n * streaming `generateContentStream` call (issue #605). Gemini 2.x is\n * documented as brittle for the combination and keeps the engine's\n * legacy finalization path.\n */\n supportsCombinedToolsAndSchema(): boolean {\n return GEMINI_COMBINED_TOOLS_AND_SCHEMA_MODELS.has(this.model)\n }\n}\n\n/**\n * Creates a Gemini text adapter with explicit API key.\n * Type resolution happens here at the call site.\n */\nexport function createGeminiChat<TModel extends (typeof GEMINI_MODELS)[number]>(\n model: TModel,\n apiKey: string,\n config?: Omit<GeminiTextConfig, 'apiKey'>,\n): GeminiTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>,\n ResolveToolCapabilities<TModel>\n> {\n return new GeminiTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Gemini text adapter with automatic API key detection.\n * Type resolution happens here at the call site.\n */\nexport function geminiText<TModel extends (typeof GEMINI_MODELS)[number]>(\n model: TModel,\n config?: Omit<GeminiTextConfig, 'apiKey'>,\n): GeminiTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>,\n ResolveToolCapabilities<TModel>\n> {\n const apiKey = getGeminiApiKeyFromEnv()\n return createGeminiChat(model, apiKey, config)\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgGA,IAAa,oBAAb,cAOU,gBAOR;CACA,OAAyB;CACzB,OAAgB;CAEhB;CAEA,YAAY,QAA0B,OAAe;EACnD,MAAM,CAAC,GAAG,KAAK;EACf,KAAK,SAAS,mBAAmB,MAAM;CACzC;CAEA,OAAO,WACL,SAC4B;EAC5B,MAAM,gBAAgB,KAAK,yBAAyB,OAAO;EAC3D,MAAM,EAAE,WAAW;EAEnB,IAAI;GACF,OAAO,QACL,uCAAuC,KAAK,MAAM,YAAY,QAAQ,SAAS,OAAO,SAAS,QAAQ,OAAO,UAAU,EAAE,eAC1H;IAAE,UAAU;IAAU,OAAO,KAAK;GAAM,CAC1C;GACA,MAAM,SACJ,MAAM,KAAK,OAAO,OAAO,sBAAsB,aAAa;GAE9D,OAAO,KAAK,oBAAoB,QAAQ,SAAS,MAAM;EACzD,SAAS,OAAO;GACd,MAAM,WAAW,mBAAmB,KAAK;GACzC,OAAO,OAAO,2BAA2B;IACvC;IACA,QAAQ;GACV,CAAC;GACD,MAAM;IACJ,MAAM,UAAU;IAChB,OAAO,QAAQ;IACf,WAAW,KAAK,IAAI;IACpB,SACE,iBAAiB,QACb,MAAM,UACN;IAGN,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;IACzC,OAAO,EACL,SACE,iBAAiB,QACb,MAAM,UACN,oDACR;GACF;EACF;CACF;;;;;;CAOA,MAAM,iBACJ,SAC0C;EAC1C,MAAM,EAAE,aAAa,iBAAiB;EACtC,MAAM,EAAE,WAAW;EAEnB,MAAM,gBAAgB,KAAK,yBAAyB,WAAW;EAE/D,IAAI;GACF,OAAO,QACL,uCAAuC,KAAK,MAAM,YAAY,YAAY,SAAS,OAAO,SAAS,YAAY,OAAO,UAAU,EAAE,gBAClI;IAAE,UAAU;IAAU,OAAO,KAAK;GAAM,CAC1C;GAEA,MAAM,SAAS,MAAM,KAAK,OAAO,OAAO,gBAAgB;IACtD,GAAG;IACH,QAAQ;KACN,GAAG,cAAc;KACjB,kBAAkB;KAClB,gBAAgB;IAClB;GACF,CAAC;GAGD,MAAM,UAAU,KAAK,wBAAwB,MAAM;GAGnD,IAAI;GACJ,IAAI;IACF,SAAS,KAAK,MAAM,OAAO;GAC7B,QAAQ;IACN,MAAM,IAAI,MACR,uDAAuD,QAAQ,MAAM,GAAG,GAAG,IAAI,QAAQ,SAAS,MAAM,QAAQ,IAChH;GACF;GAEA,OAAO;IACL,MAAM;IACN;IACA,OAAO,OAAO,gBACV,iBAAiB,OAAO,aAAa,IACrC,KAAA;GACN;EACF,SAAS,OAAO;GACd,OAAO,OAAO,iCAAiC;IAC7C;IACA,QAAQ;GACV,CAAC;GACD,MAAM,IAAI,MACR,iBAAiB,QACb,MAAM,UACN,gEACN;EACF;CACF;;;;CAKA,wBAAgC,UAA2C;EACzE,IAAI,cAAc;EAElB,IAAI,SAAS,aAAa,EAAE,EAAE,SAAS,OAChC;QAAA,MAAM,QAAQ,SAAS,WAAW,EAAE,CAAC,QAAQ,OAChD,IAAI,KAAK,MACP,eAAe,KAAK;EAAA;EAK1B,OAAO;CACT;CAEA,OAAe,oBACb,QACA,SACA,QAC4B;EAC5B,MAAM,QAAQ,QAAQ;EACtB,IAAI,qBAAqB;EACzB,IAAI,sBAAsB;EAC1B,MAAM,8BAAc,IAAI,IAStB;EACF,IAAI,gBAAgB;EAGpB,MAAM,QAAQ,QAAQ,SAAS,WAAW,KAAK,IAAI;EACnD,MAAM,WAAW,QAAQ,YAAY,WAAW,KAAK,IAAI;EACzD,MAAM,YAAY,WAAW,KAAK,IAAI;EACtC,IAAI,SAAwB;EAC5B,IAAI,qBAAoC;EACxC,IAAI,qBAAqB;EACzB,IAAI,uBAAuB;EAC3B,IAAI,6BAA6B;EACjC,IAAI,wBAAwB;EAE5B,WAAW,MAAM,SAAS,QAAQ;GAChC,OAAO,SAAS,mBAAmB,EAAE,MAAM,CAAC;GAE5C,IAAI,CAAC,sBAAsB;IACzB,uBAAuB;IACvB,MAAM;KACJ,MAAM,UAAU;KAChB;KACA;KACA;KACA,WAAW,KAAK,IAAI;KACpB,aAAa,QAAQ;IACvB;GACF;GAEA,IAAI,MAAM,aAAa,EAAE,EAAE,SAAS,OAAO;IACzC,MAAM,QAAQ,MAAM,WAAW,EAAE,CAAC,QAAQ;IAE1C,KAAK,MAAM,QAAQ,OAAO;KACxB,IAAI,KAAK,MAAM;MACb,IAAI,KAAK,SAAS;OAEhB,IAAI,CAAC,uBAAuB;QAC1B,wBAAwB;QACxB,SAAS,WAAW,KAAK,IAAI;QAC7B,qBAAqB,WAAW,KAAK,IAAI;QAGzC,MAAM;SACJ,MAAM,UAAU;SAChB,WAAW;SACX;SACA,WAAW,KAAK,IAAI;QACtB;QACA,MAAM;SACJ,MAAM,UAAU;SAChB,WAAW;SACX,MAAM;SACN;SACA,WAAW,KAAK,IAAI;QACtB;QAGA,MAAM;SACJ,MAAM,UAAU;SAChB,UAAU;SACV;SACA;SACA,WAAW,KAAK,IAAI;SACpB,UAAU;QACZ;OACF;OAEA,uBAAuB,KAAK;OAI5B,IAAI,CAAC,oBAAoB;OACzB,MAAM;QACJ,MAAM,UAAU;QAChB,WAAW;QACX,OAAO,KAAK;QACZ;QACA,WAAW,KAAK,IAAI;OACtB;OAGA,MAAM;QACJ,MAAM,UAAU;QAChB,UAAU,UAAU,WAAW,KAAK,IAAI;QACxC,QAAQ,UAAU,WAAW,KAAK,IAAI;QACtC;QACA,WAAW,KAAK,IAAI;QACpB,OAAO,KAAK;QACZ,SAAS;OACX;MACF,OAAO,IAAI,KAAK,KAAK,KAAK,GAAG;OAE3B,IAAI,sBAAsB,CAAC,oBAAoB;QAC7C,qBAAqB;QACrB,MAAM;SACJ,MAAM,UAAU;SAChB,WAAW;SACX;SACA,WAAW,KAAK,IAAI;QACtB;QACA,MAAM;SACJ,MAAM,UAAU;SAChB,WAAW;SACX;SACA,WAAW,KAAK,IAAI;QACtB;OACF;OAIA,IAAI,CAAC,4BAA4B;QAC/B,6BAA6B;QAC7B,MAAM;SACJ,MAAM,UAAU;SAChB;SACA;SACA,WAAW,KAAK,IAAI;SACpB,MAAM;QACR;OACF;OAEA,sBAAsB,KAAK;OAC3B,MAAM;QACJ,MAAM,UAAU;QAChB;QACA;QACA,WAAW,KAAK,IAAI;QACpB,OAAO,KAAK;QACZ,SAAS;OACX;MACF;KACF;KAEA,MAAM,eAAe,KAAK;KAC1B,IAAI,cAAc;MAChB,MAAM,aACJ,aAAa,MACb,GAAG,aAAa,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG;MACxC,MAAM,eAAe,aAAa,QAAQ,CAAC;MAK3C,MAAM,uBAAuB,KAAK,oBAAoB,KAAA;MAEtD,IAAI,eAAe,YAAY,IAAI,UAAU;MAC7C,IAAI,CAAC,cAAc;OACjB,eAAe;QACb,MAAM,aAAa,QAAQ;QAC3B,MACE,OAAO,iBAAiB,WACpB,eACA,KAAK,UAAU,YAAY;QACjC,OAAO;QACP,SAAS;QAGT,GAAI,yBAAyB,KAAA,KAAa,EACxC,kBAAkB,qBACpB;OACF;OACA,YAAY,IAAI,YAAY,YAAY;MAC1C,OAAO;OACL,IAAI,CAAC,aAAa,oBAAoB,sBACpC,aAAa,mBAAmB;OAElC,IAAI;QACF,MAAM,eAAe,KAAK,MAAM,aAAa,IAAI;QACjD,MAAM,UACJ,OAAO,iBAAiB,WACpB,KAAK,MAAM,YAAY,IACvB;QACN,MAAM,aAAa;SAAE,GAAG;SAAc,GAAG;QAAQ;QACjD,aAAa,OAAO,KAAK,UAAU,UAAU;OAC/C,QAAQ;QACN,aAAa,OACX,OAAO,iBAAiB,WACpB,eACA,KAAK,UAAU,YAAY;OACnC;MACF;MAGA,IAAI,CAAC,aAAa,SAAS;OACzB,aAAa,UAAU;OACvB,MAAM;QACJ,MAAM,UAAU;QAChB;QACA,cAAc,aAAa;QAC3B,UAAU,aAAa;QACvB,iBAAiB;QACjB;QACA,WAAW,KAAK,IAAI;QACpB,OAAO,aAAa;QACpB,GAAI,aAAa,oBAAoB,EACnC,UAAU,EACR,kBAAkB,aAAa,iBACjC,EACF;OACF;MACF;MAGA,MAAM;OACJ,MAAM,UAAU;OAChB;OACA;OACA,WAAW,KAAK,IAAI;OACpB,OAAO,aAAa;OACpB,MAAM,aAAa;MACrB;KACF;IACF;GACF,OAAO,IAAI,MAAM,QAAQ,MAAM,KAAK,KAAK,GAAG;IAG1C,IAAI,CAAC,4BAA4B;KAC/B,6BAA6B;KAC7B,MAAM;MACJ,MAAM,UAAU;MAChB;MACA;MACA,WAAW,KAAK,IAAI;MACpB,MAAM;KACR;IACF;IAEA,sBAAsB,MAAM;IAC5B,MAAM;KACJ,MAAM,UAAU;KAChB;KACA;KACA,WAAW,KAAK,IAAI;KACpB,OAAO,MAAM;KACb,SAAS;IACX;GACF;GAEA,IAAI,MAAM,aAAa,EAAE,EAAE,cAAc;IACvC,MAAM,eAAe,MAAM,WAAW,EAAE,CAAC;IAEzC,IAAI,iBAAiB,aAAa,sBAC5B;SAAA,MAAM,WAAW,EAAE,CAAC,SAAS,OAC/B,KAAK,MAAM,QAAQ,MAAM,WAAW,EAAE,CAAC,QAAQ,OAAO;MACpD,MAAM,eAAe,KAAK;MAC1B,IAAI,cAAc;OAChB,MAAM,aACJ,aAAa,MACb,GAAG,aAAa,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG;OACxC,MAAM,eAAe,aAAa,QAAQ,CAAC;OAE3C,MAAM,aACJ,OAAO,iBAAiB,WACpB,eACA,KAAK,UAAU,YAAY;OAEjC,YAAY,IAAI,YAAY;QAC1B,MAAM,aAAa,QAAQ;QAC3B,MAAM;QACN,OAAO;QACP,SAAS;OACX,CAAC;OAGD,MAAM;QACJ,MAAM,UAAU;QAChB;QACA,cAAc,aAAa,QAAQ;QACnC,UAAU,aAAa,QAAQ;QAC/B,iBAAiB;QACjB;QACA,WAAW,KAAK,IAAI;QACpB,OAAO,gBAAgB;OACzB;OAGA,IAAI,cAAuB,CAAC;OAC5B,IAAI;QACF,MAAM,SACJ,OAAO,iBAAiB,WACpB,KAAK,MAAM,YAAY,IACvB;QACN,cACE,UAAU,OAAO,WAAW,WAAW,SAAS,CAAC;OACrD,QAAQ;QACN,cAAc,CAAC;OACjB;OAEA,MAAM;QACJ,MAAM,UAAU;QAChB;QACA,cAAc,aAAa,QAAQ;QACnC,UAAU,aAAa,QAAQ;QAC/B;QACA,WAAW,KAAK,IAAI;QACpB,OAAO;OACT;MACF;KACF;;IAKJ,KAAK,MAAM,CAAC,YAAY,iBAAiB,YAAY,QAAQ,GAAG;KAC9D,IAAI,cAAuB,CAAC;KAC5B,IAAI;MACF,MAAM,SAAS,KAAK,MAAM,aAAa,IAAI;MAC3C,cAAc,UAAU,OAAO,WAAW,WAAW,SAAS,CAAC;KACjE,QAAQ;MACN,cAAc,CAAC;KACjB;KAEA,MAAM;MACJ,MAAM,UAAU;MAChB;MACA,cAAc,aAAa;MAC3B,UAAU,aAAa;MACvB;MACA,WAAW,KAAK,IAAI;MACpB,OAAO;KACT;IACF;IAGA,IAAI,YAAY,OAAO,GACrB,6BAA6B;IAG/B,IAAI,iBAAiB,aAAa,YAChC,MAAM;KACJ,MAAM,UAAU;KAChB;KACA;KACA,WAAW,KAAK,IAAI;KACpB,SACE;KACF,MAAM;KACN,OAAO;MACL,SACE;MACF,MAAM;KACR;IACF;IAIF,IAAI,sBAAsB,CAAC,oBAAoB;KAC7C,qBAAqB;KACrB,MAAM;MACJ,MAAM,UAAU;MAChB,WAAW;MACX;MACA,WAAW,KAAK,IAAI;KACtB;KACA,MAAM;MACJ,MAAM,UAAU;MAChB,WAAW;MACX;MACA,WAAW,KAAK,IAAI;KACtB;IACF;IAGA,IAAI,4BACF,MAAM;KACJ,MAAM,UAAU;KAChB;KACA;KACA,WAAW,KAAK,IAAI;IACtB;IAGF,MAAM;KACJ,MAAM,UAAU;KAChB;KACA;KACA;KACA,WAAW,KAAK,IAAI;KACpB,cAAc,YAAY,OAAO,IAAI,eAAe;KAIpD,GAAI,MAAM,iBAAiB,EACzB,OAAO,iBAAiB,MAAM,aAAa,EAC7C;IACF;GACF;EACF;CACF;CAEA,2BAAmC,MAAyB;EAC1D,QAAQ,KAAK,MAAb;GACE,KAAK,QACH,OAAO,EAAE,MAAM,KAAK,QAAQ;GAC9B,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK,YACH,IAAI,KAAK,OAAO,SAAS,QACvB,OAAO,EACL,YAAY;IACV,MAAM,KAAK,OAAO;IAClB,UAAU,KAAK,OAAO;GACxB,EACF;QACK;IAEL,MAAM,kBAAkB;KACtB,OAAO;KACP,OAAO;KACP,OAAO;KACP,UAAU;IACZ,EAAE,KAAK;IAEP,OAAO,EACL,UAAU;KACR,SAAS,KAAK,OAAO;KACrB,UAAU,KAAK,OAAO,YAAY;IACpC,EACF;GACF;GAEF,SAEE,MAAM,IAAI,MACR,kCAAmC,KAAiC,MACtE;EAEJ;CACF;CAEA,eACE,UACuC;EAGvC,MAAM,mCAAmB,IAAI,IAAoB;EACjD,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,SAAS,eAAe,IAAI,WAClC,KAAK,MAAM,MAAM,IAAI,WACnB,iBAAiB,IAAI,GAAG,IAAI,GAAG,SAAS,IAAI;EAKlD,MAAM,YAAY,SAAS,KAAK,QAAQ;GACtC,MAAM,OAAyB,IAAI,SAAS,cAAc,UAAU;GACpE,MAAM,QAAqB,CAAC;GAE5B,IAAI,MAAM,QAAQ,IAAI,OAAO,GAC3B,KAAK,MAAM,eAAe,IAAI,SAC5B,MAAM,KAAK,KAAK,2BAA2B,WAAW,CAAC;QAEpD,IAAI,IAAI,WAAW,IAAI,SAAS,QACrC,MAAM,KAAK,EAAE,MAAM,IAAI,QAAQ,CAAC;GAGlC,IAAI,IAAI,SAAS,eAAe,IAAI,WAAW,QAC7C,KAAK,MAAM,YAAY,IAAI,WAAW;IACpC,IAAI,aAAsC,CAAC;IAC3C,IAAI;KACF,aAAa,SAAS,SAAS,YAC1B,KAAK,MAAM,SAAS,SAAS,SAAS,IAIvC,CAAC;IACP,QAAQ;KACN,aAAa,CAAC;IAChB;IAEA,MAAM,mBACJ,SAAS,UACR;IAKH,MAAM,OAAa,EACjB,cAAc;KACZ,IAAI,SAAS;KACb,MAAM,SAAS,SAAS;KACxB,MAAM;IACR,EACF;IACA,IAAI,kBACF,KAAK,mBAAmB;IAE1B,MAAM,KAAK,IAAI;GACjB;GAGF,IAAI,IAAI,SAAS,UAAU,IAAI,YAAY;IACzC,MAAM,eACJ,iBAAiB,IAAI,IAAI,UAAU,KAAK,IAAI;IAC9C,MAAM,cAAc,IAAI;IACxB,IAAI,MAAM,QAAQ,WAAW,GAAG;KAC9B,MAAM,aAA4B,CAAC;KACnC,MAAM,aAA0B,CAAC;KACjC,KAAK,MAAM,QAAQ,aACjB,IAAI,KAAK,SAAS,QAChB,WAAW,KAAK,KAAK,OAAO;UACvB,IAAI,KAAK,OAAO,SAAS,QAC9B,WAAW,KAAK,EACd,YAAY;MACV,MAAM,KAAK,OAAO;MAClB,UAAU,KAAK,OAAO;KACxB,EACF,CAAC;UACI;MACL,MAAM,kBAAkB;OACtB,OAAO;OACP,OAAO;OACP,OAAO;OACP,UAAU;MACZ,EAAE,KAAK;MACP,WAAW,KAAK,EACd,UAAU;OACR,SAAS,KAAK,OAAO;OACrB,UAAU,KAAK,OAAO,YAAY;MACpC,EACF,CAAC;KACH;KAEF,MAAM,KAAK,EACT,kBAAkB;MAChB,IAAI,IAAI;MACR,MAAM;MACN,UAAU,EAAE,SAAS,WAAW,KAAK,IAAI,EAAE;MAC3C,GAAI,WAAW,SAAS,KAAK,EAAE,OAAO,WAAW;KACnD,EACF,CAAC;IACH,OACE,MAAM,KAAK,EACT,kBAAkB;KAChB,IAAI,IAAI;KACR,MAAM;KACN,UAAU,EAAE,SAAS,eAAe,GAAG;IACzC,EACF,CAAC;GAEL;GAEA,OAAO;IACL;IACA,OAAO,MAAM,SAAS,IAAI,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC;GACjD;EACF,CAAC;EAKD,OAAO,KAAK,iCAAiC,SAAS;CACxD;;;;;;;;;;CAWA,iCACE,UACgB;EAChB,MAAM,SAAyB,CAAC;EAEhC,KAAK,MAAM,OAAO,UAAU;GAC1B,MAAM,QAAQ,IAAI,SAAS,CAAC;GAG5B,IAAI,IAAI,SAAS,SAMX;QAAA,EAJF,MAAM,SAAS,KACf,CAAC,MAAM,OACJ,MAAM,UAAU,KAAM,EAAuB,SAAS,EACzD,IAEA;GAAA;GAIJ,MAAM,OAAO,OAAO,OAAO,SAAS;GACpC,IAAI,QAAQ,KAAK,SAAS,IAAI,MAE5B,KAAK,QAAQ,CAAC,GAAI,KAAK,SAAS,CAAC,GAAI,GAAG,KAAK;QAE7C,OAAO,KAAK;IAAE,GAAG;IAAK,OAAO,CAAC,GAAG,KAAK;GAAE,CAAC;EAE7C;EAGA,KAAK,MAAM,OAAO,QAAQ;GACxB,IAAI,CAAC,IAAI,OAAO;GAChB,MAAM,4CAA4B,IAAI,IAAY;GAClD,IAAI,QAAQ,IAAI,MAAM,QAAQ,SAAS;IACrC,IAAI,sBAAsB,QAAQ,KAAK,kBAAkB,MAAM;KAC7D,IAAI,0BAA0B,IAAI,KAAK,iBAAiB,IAAI,GAC1D,OAAO;KAET,0BAA0B,IAAI,KAAK,iBAAiB,IAAI;IAC1D;IACA,OAAO;GACT,CAAC;EACH;EAEA,OAAO;CACT;CAEA,yBACE,SACA;EAKA,MAAM,EAAE,gBAAgB,GAAG,cAAc,QAAQ,gBAAgB,CAAC;EAMlE,MAAM,uBAAuB,iBACzB;GACE,GAAI,eAAe,oBAAoB,KAAA,KAAa,EAClD,iBAAiB,eAAe,gBAClC;GACA,GAAI,eAAe,mBAAmB,KAAA,KAAa,EACjD,gBAAgB,eAAe,eACjC;GACA,GAAI,eAAe,gBACf,EACE,eAAe,eAAe,cAChC,IACA,CAAC;EACP,IACA,KAAA;EAEJ,MAAM,oBAAoB,uBAAuB,QAAQ,aAAa;EACtE,MAAM,oBACJ,kBAAkB,SAAS,IACvB,kBAAkB,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,IACjD,KAAA;EAWN,MAAM,iBAAiB,QAAQ;EAG/B,MAAM,uBAAuB,iBACzB;GACE,kBAAkB;GAClB,gBAAgB;EAClB,IACA,KAAA;EAmBJ,OAAO;GAbL,OAAO,QAAQ;GACf,UAAU,KAAK,eAAe,QAAQ,QAAQ;GAC9C,QAAQ;IACN,GAAG;IACH,GAAI,yBAAyB,KAAA,KAAa,EACxC,gBAAgB,qBAClB;IACA,GAAI,sBAAsB,KAAA,KAAa,EAAE,kBAAkB;IAC3D,OAAO,6BAA6B,QAAQ,KAAK;IACjD,GAAI,wBAAwB,CAAC;GAC/B;EAGK;CACT;;;;;;;CAQA,iCAA0C;EACxC,OAAO,wCAAwC,IAAI,KAAK,KAAK;CAC/D;AACF;;;;;AAMA,SAAgB,iBACd,OACA,QACA,QAMA;CACA,OAAO,IAAI,kBAAkB;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AAC3D;;;;;AAMA,SAAgB,WACd,OACA,QAMA;CAEA,OAAO,iBAAiB,OADT,uBACgB,GAAQ,MAAM;AAC/C"}
|
|
@@ -48,8 +48,9 @@ async function imagePartToVeoImage(part, allowUrlFetch) {
|
|
|
48
48
|
const response = await fetch(url);
|
|
49
49
|
if (!response.ok) throw new Error(`Failed to fetch image input (${response.status} ${response.statusText}): ${url}`);
|
|
50
50
|
const blob = await response.blob();
|
|
51
|
+
const buffer = await blob.arrayBuffer();
|
|
51
52
|
return {
|
|
52
|
-
imageBytes: arrayBufferToBase64(
|
|
53
|
+
imageBytes: arrayBufferToBase64(buffer),
|
|
53
54
|
mimeType: part.source.mimeType || blob.type || "image/png"
|
|
54
55
|
};
|
|
55
56
|
}
|