@tanstack/ai-gemini 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +15 -1
  2. package/dist/esm/adapters/audio.js +113 -61
  3. package/dist/esm/adapters/audio.js.map +1 -1
  4. package/dist/esm/adapters/image.js +224 -225
  5. package/dist/esm/adapters/image.js.map +1 -1
  6. package/dist/esm/adapters/summarize.js +33 -12
  7. package/dist/esm/adapters/summarize.js.map +1 -1
  8. package/dist/esm/adapters/text.js +574 -650
  9. package/dist/esm/adapters/text.js.map +1 -1
  10. package/dist/esm/adapters/tts.js +197 -178
  11. package/dist/esm/adapters/tts.js.map +1 -1
  12. package/dist/esm/adapters/video.js +456 -409
  13. package/dist/esm/adapters/video.js.map +1 -1
  14. package/dist/esm/experimental/index.js +1 -6
  15. package/dist/esm/experimental/text-interactions/adapter.js +877 -1014
  16. package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
  17. package/dist/esm/image/image-provider-options.js +74 -54
  18. package/dist/esm/image/image-provider-options.js.map +1 -1
  19. package/dist/esm/index.js +4 -36
  20. package/dist/esm/model-meta.d.ts +53 -5
  21. package/dist/esm/model-meta.js +733 -129
  22. package/dist/esm/model-meta.js.map +1 -1
  23. package/dist/esm/realtime/adapter.js +242 -228
  24. package/dist/esm/realtime/adapter.js.map +1 -1
  25. package/dist/esm/realtime/client.js +314 -381
  26. package/dist/esm/realtime/client.js.map +1 -1
  27. package/dist/esm/realtime/index.js +3 -0
  28. package/dist/esm/realtime/token.js +50 -36
  29. package/dist/esm/realtime/token.js.map +1 -1
  30. package/dist/esm/realtime/utils.js +202 -245
  31. package/dist/esm/realtime/utils.js.map +1 -1
  32. package/dist/esm/tools/code-execution-tool.js +11 -13
  33. package/dist/esm/tools/code-execution-tool.js.map +1 -1
  34. package/dist/esm/tools/computer-use-tool.js +18 -28
  35. package/dist/esm/tools/computer-use-tool.js.map +1 -1
  36. package/dist/esm/tools/file-search-tool.js +11 -14
  37. package/dist/esm/tools/file-search-tool.js.map +1 -1
  38. package/dist/esm/tools/function-declaration-tool.js +11 -25
  39. package/dist/esm/tools/function-declaration-tool.js.map +1 -1
  40. package/dist/esm/tools/google-maps-tool.js +11 -14
  41. package/dist/esm/tools/google-maps-tool.js.map +1 -1
  42. package/dist/esm/tools/google-search-retriveal-tool.js +11 -14
  43. package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -1
  44. package/dist/esm/tools/google-search-tool.js +11 -14
  45. package/dist/esm/tools/google-search-tool.js.map +1 -1
  46. package/dist/esm/tools/index.js +2 -13
  47. package/dist/esm/tools/tool-converter.js +64 -57
  48. package/dist/esm/tools/tool-converter.js.map +1 -1
  49. package/dist/esm/tools/url-context-tool.js +11 -13
  50. package/dist/esm/tools/url-context-tool.js.map +1 -1
  51. package/dist/esm/usage.js +81 -87
  52. package/dist/esm/usage.js.map +1 -1
  53. package/dist/esm/utils/client.js +31 -24
  54. package/dist/esm/utils/client.js.map +1 -1
  55. package/dist/esm/utils/index.js +2 -0
  56. package/dist/esm/video/video-provider-options.js +73 -20
  57. package/dist/esm/video/video-provider-options.js.map +1 -1
  58. package/package.json +7 -7
  59. package/src/model-meta.ts +120 -10
  60. package/dist/esm/experimental/index.js.map +0 -1
  61. package/dist/esm/index.js.map +0 -1
  62. package/dist/esm/tools/index.js.map +0 -1
@@ -1,231 +1,230 @@
1
+ import { buildGeminiUsage } from "../usage.js";
2
+ import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../utils/client.js";
3
+ import "../utils/index.js";
4
+ import { parseNativeImageSize, sizeToAspectRatio, validateImageSize, validateNumberOfImages, validatePrompt } from "../image/image-provider-options.js";
1
5
  import { resolveMediaPrompt } from "@tanstack/ai";
2
6
  import { BaseImageAdapter } from "@tanstack/ai/adapters";
3
- import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "../utils/client.js";
4
- import { buildGeminiUsage } from "../usage.js";
5
- import { validatePrompt, validateImageSize, validateNumberOfImages, parseNativeImageSize, sizeToAspectRatio } from "../image/image-provider-options.js";
6
- class GeminiImageAdapter extends BaseImageAdapter {
7
- kind = "image";
8
- name = "gemini";
9
- client;
10
- constructor(config, model) {
11
- super(model, config);
12
- this.client = createGeminiClient(config);
13
- }
14
- async generateImages(options) {
15
- const { model, logger } = options;
16
- logger.request(
17
- `activity=generateImage provider=gemini model=${this.model}`,
18
- {
19
- provider: "gemini",
20
- model: this.model
21
- }
22
- );
23
- try {
24
- const resolved = resolveMediaPrompt(options.prompt);
25
- if (resolved.images.length === 0) {
26
- validatePrompt({ prompt: resolved.text, model });
27
- }
28
- if (resolved.videos.length > 0) {
29
- throw new Error(
30
- `${this.name}.generateImages does not support video prompt parts (model: ${model}).`
31
- );
32
- }
33
- if (resolved.audios.length > 0) {
34
- throw new Error(
35
- `${this.name}.generateImages does not support audio prompt parts (model: ${model}).`
36
- );
37
- }
38
- if (this.isGeminiImageModel(model)) {
39
- return await this.generateWithGeminiApi(options, resolved);
40
- }
41
- if (resolved.images.length > 0) {
42
- throw new Error(
43
- `${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.`
44
- );
45
- }
46
- validateImageSize(model, options.size);
47
- validateNumberOfImages(model, options.numberOfImages);
48
- const config = this.buildImagenConfig(options);
49
- const response = await this.client.models.generateImages({
50
- model,
51
- prompt: resolved.text,
52
- config
53
- });
54
- return this.transformImagenResponse(model, response);
55
- } catch (error) {
56
- logger.errors("gemini.generateImage fatal", {
57
- error,
58
- source: "gemini.generateImage"
59
- });
60
- throw error;
61
- }
62
- }
63
- isGeminiImageModel(model) {
64
- return model.startsWith("gemini-");
65
- }
66
- async generateWithGeminiApi(options, resolved) {
67
- const { model, size, numberOfImages, modelOptions } = options;
68
- const parsedSize = size ? parseNativeImageSize(size) : void 0;
69
- const nativeConfig = {};
70
- if (modelOptions?.seed !== void 0) {
71
- nativeConfig.seed = modelOptions.seed;
72
- }
73
- const config = {
74
- ...nativeConfig,
75
- // Include TEXT so the model can interleave descriptions between images.
76
- // IMPORTANT: responseModalities is a protected default — set it AFTER
77
- // nativeConfig so nothing can silently disable image output.
78
- responseModalities: ["TEXT", "IMAGE"],
79
- ...parsedSize && {
80
- imageConfig: {
81
- ...parsedSize.aspectRatio && {
82
- aspectRatio: parsedSize.aspectRatio
83
- },
84
- ...parsedSize.resolution && {
85
- imageSize: parsedSize.resolution
86
- }
87
- }
88
- }
89
- };
90
- const contents = this.buildContents(resolved, numberOfImages);
91
- const response = await this.client.models.generateContent({
92
- model,
93
- contents,
94
- config
95
- });
96
- return this.transformGeminiResponse(model, response);
97
- }
98
- /**
99
- * Build the multimodal `contents` payload. Text-only prompts pass through
100
- * as a plain string (the SDK accepts it directly); prompts with image
101
- * parts become a single user `Content` whose `parts` mirror the prompt's
102
- * interleaved order — position is meaningful to Gemini ("not like this
103
- * *(image)*, more like this *(image)*").
104
- *
105
- * The generateContent API has no numberOfImages parameter, so when more
106
- * than one image is requested a trailing instruction is appended.
107
- */
108
- buildContents(resolved, numberOfImages) {
109
- const countInstruction = numberOfImages && numberOfImages > 1 ? `Generate ${numberOfImages} distinct images.` : void 0;
110
- if (resolved.images.length === 0) {
111
- return countInstruction ? `${resolved.text} ${countInstruction}` : resolved.text;
112
- }
113
- const parts = resolved.parts.map((part) => {
114
- if (part.type === "text") {
115
- return { text: part.content };
116
- }
117
- if (part.type === "image") {
118
- return this.imagePartToGeminiPart(part);
119
- }
120
- throw new Error(
121
- `gemini: unsupported prompt part type "${part.type}" in image generation.`
122
- );
123
- });
124
- if (countInstruction) {
125
- parts.push({ text: countInstruction });
126
- }
127
- return [{ role: "user", parts }];
128
- }
129
- imagePartToGeminiPart(part) {
130
- if (part.source.type === "data") {
131
- return {
132
- inlineData: {
133
- mimeType: part.source.mimeType || "image/png",
134
- data: part.source.value
135
- }
136
- };
137
- }
138
- return {
139
- fileData: {
140
- fileUri: part.source.value,
141
- mimeType: part.source.mimeType ?? "image/jpeg"
142
- }
143
- };
144
- }
145
- transformGeminiResponse(model, response) {
146
- const images = [];
147
- const textParts = [];
148
- const parts = response.candidates?.[0]?.content?.parts ?? [];
149
- for (const part of parts) {
150
- if (part.inlineData?.data && typeof part.inlineData.data === "string" && part.inlineData.data.length > 0) {
151
- images.push({ b64Json: part.inlineData.data });
152
- } else if (typeof part.text === "string" && part.text.length > 0) {
153
- textParts.push(part.text);
154
- }
155
- }
156
- if (images.length === 0) {
157
- const reason = textParts.length > 0 ? `: ${textParts.join(" ").trim()}` : " (no inline image or text parts were returned).";
158
- throw new Error(`Gemini ${model} returned no images${reason}`);
159
- }
160
- return {
161
- id: generateId(this.name),
162
- model,
163
- images,
164
- // Surface token usage (with per-modality breakdown) when the model
165
- // reports it (e.g. Nano Banana via generateContent). Conditionally spread
166
- // to satisfy exactOptionalPropertyTypes — only include usage when
167
- // present. See #330.
168
- ...response.usageMetadata ? { usage: buildGeminiUsage(response.usageMetadata) } : {}
169
- };
170
- }
171
- buildImagenConfig(options) {
172
- const { size, numberOfImages, modelOptions } = options;
173
- const sizeAspectRatio = size ? sizeToAspectRatio(size) : void 0;
174
- return {
175
- numberOfImages: numberOfImages ?? 1,
176
- // Map size to aspect ratio if provided (modelOptions.aspectRatio will override)
177
- ...sizeAspectRatio !== void 0 && { aspectRatio: sizeAspectRatio },
178
- ...modelOptions
179
- };
180
- }
181
- transformImagenResponse(model, response) {
182
- const entries = response.generatedImages ?? [];
183
- const images = [];
184
- const filterReasons = [];
185
- for (const item of entries) {
186
- const b64Json = item.image?.imageBytes;
187
- if (b64Json) {
188
- images.push({
189
- b64Json,
190
- ...item.enhancedPrompt !== void 0 && {
191
- revisedPrompt: item.enhancedPrompt
192
- }
193
- });
194
- continue;
195
- }
196
- const reason = item.raiFilteredReason;
197
- if (reason) {
198
- filterReasons.push(reason);
199
- }
200
- }
201
- if (entries.length > 0 && images.length === 0) {
202
- const joined = filterReasons.length > 0 ? filterReasons.join("; ") : "";
203
- throw new Error(
204
- `Imagen ${model} returned no images: all ${entries.length} generated image(s) were filtered by Responsible-AI${joined ? ` (${joined})` : ""}.`
205
- );
206
- }
207
- if (filterReasons.length > 0 && typeof console !== "undefined") {
208
- console.warn(
209
- `[gemini-image] ${filterReasons.length} of ${entries.length} images from ${model} were filtered by Responsible-AI: ${filterReasons.join("; ")}`
210
- );
211
- }
212
- return {
213
- id: generateId(this.name),
214
- model,
215
- images
216
- };
217
- }
218
- }
7
+ //#region src/adapters/image.ts
8
+ /**
9
+ * Gemini Image Generation Adapter
10
+ *
11
+ * Tree-shakeable adapter for Gemini image generation functionality.
12
+ * Supports Imagen 3/4 models (via generateImages API) and Gemini native
13
+ * image models like Nano Banana 2 (via generateContent API).
14
+ *
15
+ * Features:
16
+ * - Aspect ratio-based image sizing
17
+ * - Person generation controls
18
+ * - Safety filtering
19
+ * - Watermark options
20
+ * - Extended resolution tiers (Nano Banana 2)
21
+ */
22
+ var GeminiImageAdapter = class extends BaseImageAdapter {
23
+ kind = "image";
24
+ name = "gemini";
25
+ client;
26
+ constructor(config, model) {
27
+ super(model, config);
28
+ this.client = createGeminiClient(config);
29
+ }
30
+ async generateImages(options) {
31
+ const { model, logger } = options;
32
+ logger.request(`activity=generateImage provider=gemini model=${this.model}`, {
33
+ provider: "gemini",
34
+ model: this.model
35
+ });
36
+ try {
37
+ const resolved = resolveMediaPrompt(options.prompt);
38
+ if (resolved.images.length === 0) validatePrompt({
39
+ prompt: resolved.text,
40
+ model
41
+ });
42
+ if (resolved.videos.length > 0) throw new Error(`${this.name}.generateImages does not support video prompt parts (model: ${model}).`);
43
+ if (resolved.audios.length > 0) throw new Error(`${this.name}.generateImages does not support audio prompt parts (model: ${model}).`);
44
+ if (this.isGeminiImageModel(model)) return await this.generateWithGeminiApi(options, resolved);
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
+ validateImageSize(model, options.size);
47
+ validateNumberOfImages(model, options.numberOfImages);
48
+ const config = this.buildImagenConfig(options);
49
+ const response = await this.client.models.generateImages({
50
+ model,
51
+ prompt: resolved.text,
52
+ config
53
+ });
54
+ return this.transformImagenResponse(model, response);
55
+ } catch (error) {
56
+ logger.errors("gemini.generateImage fatal", {
57
+ error,
58
+ source: "gemini.generateImage"
59
+ });
60
+ throw error;
61
+ }
62
+ }
63
+ isGeminiImageModel(model) {
64
+ return model.startsWith("gemini-");
65
+ }
66
+ async generateWithGeminiApi(options, resolved) {
67
+ const { model, size, numberOfImages, modelOptions } = options;
68
+ const parsedSize = size ? parseNativeImageSize(size) : void 0;
69
+ const nativeConfig = {};
70
+ if (modelOptions?.seed !== void 0) nativeConfig.seed = modelOptions.seed;
71
+ const config = {
72
+ ...nativeConfig,
73
+ responseModalities: ["TEXT", "IMAGE"],
74
+ ...parsedSize && { imageConfig: {
75
+ ...parsedSize.aspectRatio && { aspectRatio: parsedSize.aspectRatio },
76
+ ...parsedSize.resolution && { imageSize: parsedSize.resolution }
77
+ } }
78
+ };
79
+ const contents = this.buildContents(resolved, numberOfImages);
80
+ const response = await this.client.models.generateContent({
81
+ model,
82
+ contents,
83
+ config
84
+ });
85
+ return this.transformGeminiResponse(model, response);
86
+ }
87
+ /**
88
+ * Build the multimodal `contents` payload. Text-only prompts pass through
89
+ * as a plain string (the SDK accepts it directly); prompts with image
90
+ * parts become a single user `Content` whose `parts` mirror the prompt's
91
+ * interleaved order — position is meaningful to Gemini ("not like this
92
+ * *(image)*, more like this *(image)*").
93
+ *
94
+ * The generateContent API has no numberOfImages parameter, so when more
95
+ * than one image is requested a trailing instruction is appended.
96
+ */
97
+ buildContents(resolved, numberOfImages) {
98
+ const countInstruction = numberOfImages && numberOfImages > 1 ? `Generate ${numberOfImages} distinct images.` : void 0;
99
+ if (resolved.images.length === 0) return countInstruction ? `${resolved.text} ${countInstruction}` : resolved.text;
100
+ const parts = resolved.parts.map((part) => {
101
+ if (part.type === "text") return { text: part.content };
102
+ if (part.type === "image") return this.imagePartToGeminiPart(part);
103
+ throw new Error(`gemini: unsupported prompt part type "${part.type}" in image generation.`);
104
+ });
105
+ if (countInstruction) parts.push({ text: countInstruction });
106
+ return [{
107
+ role: "user",
108
+ parts
109
+ }];
110
+ }
111
+ imagePartToGeminiPart(part) {
112
+ if (part.source.type === "data") return { inlineData: {
113
+ mimeType: part.source.mimeType || "image/png",
114
+ data: part.source.value
115
+ } };
116
+ return { fileData: {
117
+ fileUri: part.source.value,
118
+ mimeType: part.source.mimeType ?? "image/jpeg"
119
+ } };
120
+ }
121
+ transformGeminiResponse(model, response) {
122
+ const images = [];
123
+ const textParts = [];
124
+ const parts = response.candidates?.[0]?.content?.parts ?? [];
125
+ for (const part of parts) if (part.inlineData?.data && typeof part.inlineData.data === "string" && part.inlineData.data.length > 0) images.push({ b64Json: part.inlineData.data });
126
+ else if (typeof part.text === "string" && part.text.length > 0) textParts.push(part.text);
127
+ if (images.length === 0) {
128
+ const reason = textParts.length > 0 ? `: ${textParts.join(" ").trim()}` : " (no inline image or text parts were returned).";
129
+ throw new Error(`Gemini ${model} returned no images${reason}`);
130
+ }
131
+ return {
132
+ id: generateId(this.name),
133
+ model,
134
+ images,
135
+ ...response.usageMetadata ? { usage: buildGeminiUsage(response.usageMetadata) } : {}
136
+ };
137
+ }
138
+ buildImagenConfig(options) {
139
+ const { size, numberOfImages, modelOptions } = options;
140
+ const sizeAspectRatio = size ? sizeToAspectRatio(size) : void 0;
141
+ return {
142
+ numberOfImages: numberOfImages ?? 1,
143
+ ...sizeAspectRatio !== void 0 && { aspectRatio: sizeAspectRatio },
144
+ ...modelOptions
145
+ };
146
+ }
147
+ transformImagenResponse(model, response) {
148
+ const entries = response.generatedImages ?? [];
149
+ const images = [];
150
+ const filterReasons = [];
151
+ for (const item of entries) {
152
+ const b64Json = item.image?.imageBytes;
153
+ if (b64Json) {
154
+ images.push({
155
+ b64Json,
156
+ ...item.enhancedPrompt !== void 0 && { revisedPrompt: item.enhancedPrompt }
157
+ });
158
+ continue;
159
+ }
160
+ const reason = item.raiFilteredReason;
161
+ if (reason) filterReasons.push(reason);
162
+ }
163
+ if (entries.length > 0 && images.length === 0) {
164
+ const joined = filterReasons.length > 0 ? filterReasons.join("; ") : "";
165
+ throw new Error(`Imagen ${model} returned no images: all ${entries.length} generated image(s) were filtered by Responsible-AI${joined ? ` (${joined})` : ""}.`);
166
+ }
167
+ if (filterReasons.length > 0 && typeof console !== "undefined") console.warn(`[gemini-image] ${filterReasons.length} of ${entries.length} images from ${model} were filtered by Responsible-AI: ${filterReasons.join("; ")}`);
168
+ return {
169
+ id: generateId(this.name),
170
+ model,
171
+ images
172
+ };
173
+ }
174
+ };
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
+ */
219
194
  function createGeminiImage(model, apiKey, config) {
220
- return new GeminiImageAdapter({ apiKey, ...config }, model);
195
+ return new GeminiImageAdapter({
196
+ apiKey,
197
+ ...config
198
+ }, model);
221
199
  }
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
+ */
222
224
  function geminiImage(model, config) {
223
- const apiKey = getGeminiApiKeyFromEnv();
224
- return createGeminiImage(model, apiKey, config);
225
+ return createGeminiImage(model, getGeminiApiKeyFromEnv(), config);
225
226
  }
226
- export {
227
- GeminiImageAdapter,
228
- createGeminiImage,
229
- geminiImage
230
- };
231
- //# sourceMappingURL=image.js.map
227
+ //#endregion
228
+ export { GeminiImageAdapter, createGeminiImage, geminiImage };
229
+
230
+ //# sourceMappingURL=image.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport {\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"],"names":[],"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 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,18 +1,39 @@
1
- import { ChatStreamSummarizeAdapter } from "@tanstack/ai/adapters";
2
1
  import { getGeminiApiKeyFromEnv } from "../utils/client.js";
2
+ import "../utils/index.js";
3
3
  import { GeminiTextAdapter } from "./text.js";
4
+ import { ChatStreamSummarizeAdapter } from "@tanstack/ai/adapters";
5
+ //#region src/adapters/summarize.ts
6
+ /**
7
+ * Creates a Gemini summarize adapter with explicit API key and model.
8
+ *
9
+ * Note: keeps the historical (apiKey, model, config) argument order to
10
+ * avoid breaking existing callers.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const adapter = createGeminiSummarize('AIza...', 'gemini-2.5-flash');
15
+ * ```
16
+ */
4
17
  function createGeminiSummarize(apiKey, model, config) {
5
- return new ChatStreamSummarizeAdapter(
6
- new GeminiTextAdapter({ ...config, apiKey }, model),
7
- model,
8
- "gemini"
9
- );
18
+ return new ChatStreamSummarizeAdapter(new GeminiTextAdapter({
19
+ ...config,
20
+ apiKey
21
+ }, model), model, "gemini");
10
22
  }
23
+ /**
24
+ * Creates a Gemini summarize adapter with API key from `GOOGLE_API_KEY` /
25
+ * `GEMINI_API_KEY` environment variables.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const adapter = geminiSummarize('gemini-2.5-flash');
30
+ * await summarize({ adapter, text: 'Long article text...' });
31
+ * ```
32
+ */
11
33
  function geminiSummarize(model, config) {
12
- return createGeminiSummarize(getGeminiApiKeyFromEnv(), model, config);
34
+ return createGeminiSummarize(getGeminiApiKeyFromEnv(), model, config);
13
35
  }
14
- export {
15
- createGeminiSummarize,
16
- geminiSummarize
17
- };
18
- //# sourceMappingURL=summarize.js.map
36
+ //#endregion
37
+ export { createGeminiSummarize, geminiSummarize };
38
+
39
+ //# sourceMappingURL=summarize.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"summarize.js","sources":["../../../src/adapters/summarize.ts"],"sourcesContent":["import { ChatStreamSummarizeAdapter } from '@tanstack/ai/adapters'\nimport { getGeminiApiKeyFromEnv } from '../utils'\nimport { GeminiTextAdapter } from './text'\nimport type { InferTextProviderOptions } from '@tanstack/ai/adapters'\nimport type { GEMINI_MODELS } from '../model-meta'\nimport type { GeminiClientConfig } from '../utils/client'\n\n/**\n * Configuration for Gemini summarize adapter\n */\nexport interface GeminiSummarizeConfig extends GeminiClientConfig {}\n\nexport type GeminiSummarizeModel = (typeof GEMINI_MODELS)[number]\n\n/**\n * Creates a Gemini summarize adapter with explicit API key and model.\n *\n * Note: keeps the historical (apiKey, model, config) argument order to\n * avoid breaking existing callers.\n *\n * @example\n * ```typescript\n * const adapter = createGeminiSummarize('AIza...', 'gemini-2.5-flash');\n * ```\n */\nexport function createGeminiSummarize<TModel extends GeminiSummarizeModel>(\n apiKey: string,\n model: TModel,\n config?: Omit<GeminiSummarizeConfig, 'apiKey'>,\n): ChatStreamSummarizeAdapter<\n TModel,\n InferTextProviderOptions<GeminiTextAdapter<TModel>>\n> {\n return new ChatStreamSummarizeAdapter(\n new GeminiTextAdapter({ ...config, apiKey }, model),\n model,\n 'gemini',\n )\n}\n\n/**\n * Creates a Gemini summarize adapter with API key from `GOOGLE_API_KEY` /\n * `GEMINI_API_KEY` environment variables.\n *\n * @example\n * ```typescript\n * const adapter = geminiSummarize('gemini-2.5-flash');\n * await summarize({ adapter, text: 'Long article text...' });\n * ```\n */\nexport function geminiSummarize<TModel extends GeminiSummarizeModel>(\n model: TModel,\n config?: Omit<GeminiSummarizeConfig, 'apiKey'>,\n): ChatStreamSummarizeAdapter<\n TModel,\n InferTextProviderOptions<GeminiTextAdapter<TModel>>\n> {\n return createGeminiSummarize(getGeminiApiKeyFromEnv(), model, config)\n}\n"],"names":[],"mappings":";;;AAyBO,SAAS,sBACd,QACA,OACA,QAIA;AACA,SAAO,IAAI;AAAA,IACT,IAAI,kBAAkB,EAAE,GAAG,QAAQ,OAAA,GAAU,KAAK;AAAA,IAClD;AAAA,IACA;AAAA,EAAA;AAEJ;AAYO,SAAS,gBACd,OACA,QAIA;AACA,SAAO,sBAAsB,0BAA0B,OAAO,MAAM;AACtE;"}
1
+ {"version":3,"file":"summarize.js","names":[],"sources":["../../../src/adapters/summarize.ts"],"sourcesContent":["import { ChatStreamSummarizeAdapter } from '@tanstack/ai/adapters'\nimport { getGeminiApiKeyFromEnv } from '../utils'\nimport { GeminiTextAdapter } from './text'\nimport type { InferTextProviderOptions } from '@tanstack/ai/adapters'\nimport type { GEMINI_MODELS } from '../model-meta'\nimport type { GeminiClientConfig } from '../utils/client'\n\n/**\n * Configuration for Gemini summarize adapter\n */\nexport interface GeminiSummarizeConfig extends GeminiClientConfig {}\n\nexport type GeminiSummarizeModel = (typeof GEMINI_MODELS)[number]\n\n/**\n * Creates a Gemini summarize adapter with explicit API key and model.\n *\n * Note: keeps the historical (apiKey, model, config) argument order to\n * avoid breaking existing callers.\n *\n * @example\n * ```typescript\n * const adapter = createGeminiSummarize('AIza...', 'gemini-2.5-flash');\n * ```\n */\nexport function createGeminiSummarize<TModel extends GeminiSummarizeModel>(\n apiKey: string,\n model: TModel,\n config?: Omit<GeminiSummarizeConfig, 'apiKey'>,\n): ChatStreamSummarizeAdapter<\n TModel,\n InferTextProviderOptions<GeminiTextAdapter<TModel>>\n> {\n return new ChatStreamSummarizeAdapter(\n new GeminiTextAdapter({ ...config, apiKey }, model),\n model,\n 'gemini',\n )\n}\n\n/**\n * Creates a Gemini summarize adapter with API key from `GOOGLE_API_KEY` /\n * `GEMINI_API_KEY` environment variables.\n *\n * @example\n * ```typescript\n * const adapter = geminiSummarize('gemini-2.5-flash');\n * await summarize({ adapter, text: 'Long article text...' });\n * ```\n */\nexport function geminiSummarize<TModel extends GeminiSummarizeModel>(\n model: TModel,\n config?: Omit<GeminiSummarizeConfig, 'apiKey'>,\n): ChatStreamSummarizeAdapter<\n TModel,\n InferTextProviderOptions<GeminiTextAdapter<TModel>>\n> {\n return createGeminiSummarize(getGeminiApiKeyFromEnv(), model, config)\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,SAAgB,sBACd,QACA,OACA,QAIA;CACA,OAAO,IAAI,2BACT,IAAI,kBAAkB;EAAE,GAAG;EAAQ;CAAO,GAAG,KAAK,GAClD,OACA,QACF;AACF;;;;;;;;;;;AAYA,SAAgB,gBACd,OACA,QAIA;CACA,OAAO,sBAAsB,uBAAuB,GAAG,OAAO,MAAM;AACtE"}