@tanstack/ai-gemini 0.0.3 → 0.1.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 (41) hide show
  1. package/README.md +26 -0
  2. package/dist/esm/adapters/image.d.ts +83 -0
  3. package/dist/esm/adapters/image.js +60 -0
  4. package/dist/esm/adapters/image.js.map +1 -0
  5. package/dist/esm/adapters/summarize.d.ts +53 -0
  6. package/dist/esm/adapters/summarize.js +139 -0
  7. package/dist/esm/adapters/summarize.js.map +1 -0
  8. package/dist/esm/adapters/text.d.ts +63 -0
  9. package/dist/esm/{gemini-adapter.js → adapters/text.js} +86 -132
  10. package/dist/esm/adapters/text.js.map +1 -0
  11. package/dist/esm/adapters/tts.d.ts +129 -0
  12. package/dist/esm/adapters/tts.js +78 -0
  13. package/dist/esm/adapters/tts.js.map +1 -0
  14. package/dist/esm/image/image-provider-options.d.ts +139 -0
  15. package/dist/esm/image/image-provider-options.js +53 -0
  16. package/dist/esm/image/image-provider-options.js.map +1 -0
  17. package/dist/esm/index.d.ts +15 -2
  18. package/dist/esm/index.js +22 -4
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/model-meta.d.ts +128 -1
  21. package/dist/esm/model-meta.js +71 -5
  22. package/dist/esm/model-meta.js.map +1 -1
  23. package/dist/esm/tools/tool-converter.js +5 -3
  24. package/dist/esm/tools/tool-converter.js.map +1 -1
  25. package/dist/esm/utils/client.d.ts +17 -0
  26. package/dist/esm/utils/client.js +25 -0
  27. package/dist/esm/utils/client.js.map +1 -0
  28. package/dist/esm/utils/index.d.ts +1 -0
  29. package/package.json +5 -4
  30. package/src/adapters/image.ts +188 -0
  31. package/src/adapters/summarize.ts +242 -0
  32. package/src/{gemini-adapter.ts → adapters/text.ts} +196 -245
  33. package/src/adapters/tts.ts +223 -0
  34. package/src/image/image-provider-options.ts +239 -0
  35. package/src/index.ts +66 -2
  36. package/src/model-meta.ts +87 -62
  37. package/src/tools/tool-converter.ts +6 -5
  38. package/src/utils/client.ts +43 -0
  39. package/src/utils/index.ts +6 -0
  40. package/dist/esm/gemini-adapter.d.ts +0 -71
  41. package/dist/esm/gemini-adapter.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tool-converter.js","sources":["../../../src/tools/tool-converter.ts"],"sourcesContent":["import { convertZodToJsonSchema } from '@tanstack/ai'\nimport { convertCodeExecutionToolToAdapterFormat } from './code-execution-tool'\nimport { convertComputerUseToolToAdapterFormat } from './computer-use-tool'\nimport { convertFileSearchToolToAdapterFormat } from './file-search-tool'\nimport { convertGoogleMapsToolToAdapterFormat } from './google-maps-tool'\nimport { convertGoogleSearchRetrievalToolToAdapterFormat } from './google-search-retriveal-tool'\nimport { convertGoogleSearchToolToAdapterFormat } from './google-search-tool'\nimport { convertUrlContextToolToAdapterFormat } from './url-context-tool'\nimport type { Tool } from '@tanstack/ai'\nimport type { ToolUnion } from '@google/genai'\n\n/**\n * Converts standard Tool format to Gemini-specific tool format\n *\n * @param tools - Array of standard Tool objects\n * @returns Array of Gemini-specific tool definitions\n *\n * @example\n * ```typescript\n * const tools: Tool[] = [{\n * name: \"get_weather\",\n * description: \"Get weather for a location\",\n * inputSchema: z.object({\n * location: z.string()\n * })\n * }];\n *\n * const geminiTools = convertToolsToProviderFormat(tools);\n * ```\n */\nexport function convertToolsToProviderFormat<TTool extends Tool>(\n tools: Array<TTool> | undefined,\n): Array<ToolUnion> {\n if (!tools || tools.length === 0) {\n return []\n }\n const result: Array<ToolUnion> = []\n const functionDeclarations: Array<{\n name: string\n description?: string\n parameters?: any\n }> = []\n\n // Process each tool and group function declarations together\n for (const tool of tools) {\n const name = tool.name\n\n switch (name) {\n case 'code_execution':\n result.push(convertCodeExecutionToolToAdapterFormat(tool))\n break\n case 'computer_use':\n result.push(convertComputerUseToolToAdapterFormat(tool))\n break\n case 'file_search':\n result.push(convertFileSearchToolToAdapterFormat(tool))\n break\n case 'google_maps':\n result.push(convertGoogleMapsToolToAdapterFormat(tool))\n break\n case 'google_search_retrieval':\n result.push(convertGoogleSearchRetrievalToolToAdapterFormat(tool))\n break\n case 'google_search':\n result.push(convertGoogleSearchToolToAdapterFormat(tool))\n break\n case 'url_context':\n result.push(convertUrlContextToolToAdapterFormat(tool))\n break\n default:\n // Collect function declarations to group together\n // Description is required for Gemini function declarations\n if (!tool.description) {\n throw new Error(\n `Tool ${tool.name} requires a description for Gemini adapter`,\n )\n }\n\n // Convert Zod schema to JSON Schema\n const jsonSchema = convertZodToJsonSchema(tool.inputSchema)\n\n functionDeclarations.push({\n name: tool.name,\n description: tool.description,\n parameters: jsonSchema,\n })\n break\n }\n }\n\n // If we have function declarations, add them as a single tool\n if (functionDeclarations.length > 0) {\n result.push({\n functionDeclarations: functionDeclarations,\n })\n }\n\n return result\n}\n"],"names":[],"mappings":";;;;;;;;AA8BO,SAAS,6BACd,OACkB;AAClB,MAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,WAAO,CAAA;AAAA,EACT;AACA,QAAM,SAA2B,CAAA;AACjC,QAAM,uBAID,CAAA;AAGL,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,KAAK;AAElB,YAAQ,MAAA;AAAA,MACN,KAAK;AACH,eAAO,KAAK,wCAA4C,CAAC;AACzD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,sCAAsC,IAAI,CAAC;AACvD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,qCAAqC,IAAI,CAAC;AACtD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,qCAAqC,IAAI,CAAC;AACtD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,gDAAgD,IAAI,CAAC;AACjE;AAAA,MACF,KAAK;AACH,eAAO,KAAK,uCAAuC,IAAI,CAAC;AACxD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,qCAAyC,CAAC;AACtD;AAAA,MACF;AAGE,YAAI,CAAC,KAAK,aAAa;AACrB,gBAAM,IAAI;AAAA,YACR,QAAQ,KAAK,IAAI;AAAA,UAAA;AAAA,QAErB;AAGA,cAAM,aAAa,uBAAuB,KAAK,WAAW;AAE1D,6BAAqB,KAAK;AAAA,UACxB,MAAM,KAAK;AAAA,UACX,aAAa,KAAK;AAAA,UAClB,YAAY;AAAA,QAAA,CACb;AACD;AAAA,IAAA;AAAA,EAEN;AAGA,MAAI,qBAAqB,SAAS,GAAG;AACnC,WAAO,KAAK;AAAA,MACV;AAAA,IAAA,CACD;AAAA,EACH;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"tool-converter.js","sources":["../../../src/tools/tool-converter.ts"],"sourcesContent":["import { convertCodeExecutionToolToAdapterFormat } from './code-execution-tool'\nimport { convertComputerUseToolToAdapterFormat } from './computer-use-tool'\nimport { convertFileSearchToolToAdapterFormat } from './file-search-tool'\nimport { convertGoogleMapsToolToAdapterFormat } from './google-maps-tool'\nimport { convertGoogleSearchRetrievalToolToAdapterFormat } from './google-search-retriveal-tool'\nimport { convertGoogleSearchToolToAdapterFormat } from './google-search-tool'\nimport { convertUrlContextToolToAdapterFormat } from './url-context-tool'\nimport type { Tool } from '@tanstack/ai'\nimport type { ToolUnion } from '@google/genai'\n\n/**\n * Converts standard Tool format to Gemini-specific tool format\n *\n * @param tools - Array of standard Tool objects\n * @returns Array of Gemini-specific tool definitions\n *\n * @example\n * ```typescript\n * const tools: Tool[] = [{\n * name: \"get_weather\",\n * description: \"Get weather for a location\",\n * inputSchema: z.object({\n * location: z.string()\n * })\n * }];\n *\n * const geminiTools = convertToolsToProviderFormat(tools);\n * ```\n */\nexport function convertToolsToProviderFormat<TTool extends Tool>(\n tools: Array<TTool> | undefined,\n): Array<ToolUnion> {\n if (!tools || tools.length === 0) {\n return []\n }\n const result: Array<ToolUnion> = []\n const functionDeclarations: Array<{\n name: string\n description?: string\n parameters?: any\n }> = []\n\n // Process each tool and group function declarations together\n for (const tool of tools) {\n const name = tool.name\n\n switch (name) {\n case 'code_execution':\n result.push(convertCodeExecutionToolToAdapterFormat(tool))\n break\n case 'computer_use':\n result.push(convertComputerUseToolToAdapterFormat(tool))\n break\n case 'file_search':\n result.push(convertFileSearchToolToAdapterFormat(tool))\n break\n case 'google_maps':\n result.push(convertGoogleMapsToolToAdapterFormat(tool))\n break\n case 'google_search_retrieval':\n result.push(convertGoogleSearchRetrievalToolToAdapterFormat(tool))\n break\n case 'google_search':\n result.push(convertGoogleSearchToolToAdapterFormat(tool))\n break\n case 'url_context':\n result.push(convertUrlContextToolToAdapterFormat(tool))\n break\n default:\n // Collect function declarations to group together\n // Description is required for Gemini function declarations\n if (!tool.description) {\n throw new Error(\n `Tool ${tool.name} requires a description for Gemini adapter`,\n )\n }\n\n // Tool schemas are already converted to JSON Schema in the ai layer\n functionDeclarations.push({\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema ?? {\n type: 'object',\n properties: {},\n required: [],\n },\n })\n break\n }\n }\n\n // If we have function declarations, add them as a single tool\n if (functionDeclarations.length > 0) {\n result.push({\n functionDeclarations: functionDeclarations,\n })\n }\n\n return result\n}\n"],"names":[],"mappings":";;;;;;;AA6BO,SAAS,6BACd,OACkB;AAClB,MAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,WAAO,CAAA;AAAA,EACT;AACA,QAAM,SAA2B,CAAA;AACjC,QAAM,uBAID,CAAA;AAGL,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,KAAK;AAElB,YAAQ,MAAA;AAAA,MACN,KAAK;AACH,eAAO,KAAK,wCAA4C,CAAC;AACzD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,sCAAsC,IAAI,CAAC;AACvD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,qCAAqC,IAAI,CAAC;AACtD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,qCAAqC,IAAI,CAAC;AACtD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,gDAAgD,IAAI,CAAC;AACjE;AAAA,MACF,KAAK;AACH,eAAO,KAAK,uCAAuC,IAAI,CAAC;AACxD;AAAA,MACF,KAAK;AACH,eAAO,KAAK,qCAAyC,CAAC;AACtD;AAAA,MACF;AAGE,YAAI,CAAC,KAAK,aAAa;AACrB,gBAAM,IAAI;AAAA,YACR,QAAQ,KAAK,IAAI;AAAA,UAAA;AAAA,QAErB;AAGA,6BAAqB,KAAK;AAAA,UACxB,MAAM,KAAK;AAAA,UACX,aAAa,KAAK;AAAA,UAClB,YAAY,KAAK,eAAe;AAAA,YAC9B,MAAM;AAAA,YACN,YAAY,CAAA;AAAA,YACZ,UAAU,CAAA;AAAA,UAAC;AAAA,QACb,CACD;AACD;AAAA,IAAA;AAAA,EAEN;AAGA,MAAI,qBAAqB,SAAS,GAAG;AACnC,WAAO,KAAK;AAAA,MACV;AAAA,IAAA,CACD;AAAA,EACH;AAEA,SAAO;AACT;"}
@@ -0,0 +1,17 @@
1
+ import { GoogleGenAI } from '@google/genai';
2
+ export interface GeminiClientConfig {
3
+ apiKey: string;
4
+ }
5
+ /**
6
+ * Creates a Google Generative AI client instance
7
+ */
8
+ export declare function createGeminiClient(config: GeminiClientConfig): GoogleGenAI;
9
+ /**
10
+ * Gets Google API key from environment variables
11
+ * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found
12
+ */
13
+ export declare function getGeminiApiKeyFromEnv(): string;
14
+ /**
15
+ * Generates a unique ID with a prefix
16
+ */
17
+ export declare function generateId(prefix: string): string;
@@ -0,0 +1,25 @@
1
+ import { GoogleGenAI } from "@google/genai";
2
+ function createGeminiClient(config) {
3
+ return new GoogleGenAI({
4
+ apiKey: config.apiKey
5
+ });
6
+ }
7
+ function getGeminiApiKeyFromEnv() {
8
+ const env = typeof globalThis !== "undefined" && globalThis.window?.env ? globalThis.window.env : typeof process !== "undefined" ? process.env : void 0;
9
+ const key = env?.GOOGLE_API_KEY || env?.GEMINI_API_KEY;
10
+ if (!key) {
11
+ throw new Error(
12
+ "GOOGLE_API_KEY or GEMINI_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key."
13
+ );
14
+ }
15
+ return key;
16
+ }
17
+ function generateId(prefix) {
18
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`;
19
+ }
20
+ export {
21
+ createGeminiClient,
22
+ generateId,
23
+ getGeminiApiKeyFromEnv
24
+ };
25
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\n\nexport interface GeminiClientConfig {\n apiKey: string\n}\n\n/**\n * Creates a Google Generative AI client instance\n */\nexport function createGeminiClient(config: GeminiClientConfig): GoogleGenAI {\n return new GoogleGenAI({\n apiKey: config.apiKey,\n })\n}\n\n/**\n * Gets Google API key from environment variables\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found\n */\nexport function getGeminiApiKeyFromEnv(): string {\n const env =\n typeof globalThis !== 'undefined' && (globalThis as any).window?.env\n ? (globalThis as any).window.env\n : typeof process !== 'undefined'\n ? process.env\n : undefined\n const key = env?.GOOGLE_API_KEY || env?.GEMINI_API_KEY\n\n if (!key) {\n throw new Error(\n 'GOOGLE_API_KEY or GEMINI_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key.',\n )\n }\n\n return key\n}\n\n/**\n * Generates a unique ID with a prefix\n */\nexport function generateId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"names":[],"mappings":";AASO,SAAS,mBAAmB,QAAyC;AAC1E,SAAO,IAAI,YAAY;AAAA,IACrB,QAAQ,OAAO;AAAA,EAAA,CAChB;AACH;AAMO,SAAS,yBAAiC;AAC/C,QAAM,MACJ,OAAO,eAAe,eAAgB,WAAmB,QAAQ,MAC5D,WAAmB,OAAO,MAC3B,OAAO,YAAY,cACjB,QAAQ,MACR;AACR,QAAM,MAAM,KAAK,kBAAkB,KAAK;AAExC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AACT;AAKO,SAAS,WAAW,QAAwB;AACjD,SAAO,GAAG,MAAM,IAAI,KAAK,KAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AAC3E;"}
@@ -0,0 +1 @@
1
+ export { createGeminiClient, generateId, getGeminiApiKeyFromEnv, type GeminiClientConfig, } from './client.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-gemini",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "description": "Google Gemini adapter for TanStack AI",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -31,14 +31,15 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "@google/genai": "^1.30.0",
34
- "@tanstack/ai": "0.0.3"
34
+ "@tanstack/ai": "0.1.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@vitest/coverage-v8": "4.0.14",
38
- "vite": "^7.2.4"
38
+ "vite": "^7.2.7"
39
39
  },
40
40
  "peerDependencies": {
41
- "@tanstack/ai": "0.0.3"
41
+ "zod": "^4.0.0",
42
+ "@tanstack/ai": "0.1.0"
42
43
  },
43
44
  "scripts": {
44
45
  "build": "vite build",
@@ -0,0 +1,188 @@
1
+ import { BaseImageAdapter } from '@tanstack/ai/adapters'
2
+ import {
3
+ createGeminiClient,
4
+ generateId,
5
+ getGeminiApiKeyFromEnv,
6
+ } from '../utils'
7
+ import {
8
+ sizeToAspectRatio,
9
+ validateImageSize,
10
+ validateNumberOfImages,
11
+ validatePrompt,
12
+ } from '../image/image-provider-options'
13
+ import type { GEMINI_IMAGE_MODELS } from '../model-meta'
14
+ import type {
15
+ GeminiImageModelProviderOptionsByName,
16
+ GeminiImageModelSizeByName,
17
+ GeminiImageProviderOptions,
18
+ } from '../image/image-provider-options'
19
+ import type {
20
+ GeneratedImage,
21
+ ImageGenerationOptions,
22
+ ImageGenerationResult,
23
+ } from '@tanstack/ai'
24
+ import type {
25
+ GenerateImagesConfig,
26
+ GenerateImagesResponse,
27
+ GoogleGenAI,
28
+ } from '@google/genai'
29
+ import type { GeminiClientConfig } from '../utils'
30
+
31
+ /**
32
+ * Configuration for Gemini image adapter
33
+ */
34
+ export interface GeminiImageConfig extends GeminiClientConfig {}
35
+
36
+ /** Model type for Gemini Image */
37
+ export type GeminiImageModel = (typeof GEMINI_IMAGE_MODELS)[number]
38
+
39
+ /**
40
+ * Gemini Image Generation Adapter
41
+ *
42
+ * Tree-shakeable adapter for Gemini Imagen image generation functionality.
43
+ * Supports Imagen 3 and Imagen 4 models.
44
+ *
45
+ * Features:
46
+ * - Aspect ratio-based image sizing
47
+ * - Person generation controls
48
+ * - Safety filtering
49
+ * - Watermark options
50
+ */
51
+ export class GeminiImageAdapter<
52
+ TModel extends GeminiImageModel,
53
+ > extends BaseImageAdapter<
54
+ TModel,
55
+ GeminiImageProviderOptions,
56
+ GeminiImageModelProviderOptionsByName,
57
+ GeminiImageModelSizeByName
58
+ > {
59
+ readonly kind = 'image' as const
60
+ readonly name = 'gemini' as const
61
+
62
+ // Type-only property - never assigned at runtime
63
+ declare '~types': {
64
+ providerOptions: GeminiImageProviderOptions
65
+ modelProviderOptionsByName: GeminiImageModelProviderOptionsByName
66
+ modelSizeByName: GeminiImageModelSizeByName
67
+ }
68
+
69
+ private client: GoogleGenAI
70
+
71
+ constructor(config: GeminiImageConfig, model: TModel) {
72
+ super({}, model)
73
+ this.client = createGeminiClient(config)
74
+ }
75
+
76
+ async generateImages(
77
+ options: ImageGenerationOptions<GeminiImageProviderOptions>,
78
+ ): Promise<ImageGenerationResult> {
79
+ const { model, prompt, numberOfImages, size } = options
80
+
81
+ // Validate inputs
82
+ validatePrompt({ prompt, model })
83
+ validateImageSize(model, size)
84
+ validateNumberOfImages(model, numberOfImages)
85
+
86
+ // Build request config
87
+ const config = this.buildConfig(options)
88
+
89
+ const response = await this.client.models.generateImages({
90
+ model,
91
+ prompt,
92
+ config,
93
+ })
94
+
95
+ return this.transformResponse(model, response)
96
+ }
97
+
98
+ private buildConfig(
99
+ options: ImageGenerationOptions<GeminiImageProviderOptions>,
100
+ ): GenerateImagesConfig {
101
+ const { size, numberOfImages, modelOptions } = options
102
+
103
+ return {
104
+ numberOfImages: numberOfImages ?? 1,
105
+ // Map size to aspect ratio if provided (modelOptions.aspectRatio will override)
106
+ aspectRatio: size ? sizeToAspectRatio(size) : undefined,
107
+ ...modelOptions,
108
+ }
109
+ }
110
+
111
+ private transformResponse(
112
+ model: string,
113
+ response: GenerateImagesResponse,
114
+ ): ImageGenerationResult {
115
+ const images: Array<GeneratedImage> = (response.generatedImages ?? []).map(
116
+ (item) => ({
117
+ b64Json: item.image?.imageBytes,
118
+ revisedPrompt: item.enhancedPrompt,
119
+ }),
120
+ )
121
+
122
+ return {
123
+ id: generateId(this.name),
124
+ model,
125
+ images,
126
+ usage: undefined,
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Creates a Gemini image adapter with explicit API key.
133
+ * Type resolution happens here at the call site.
134
+ *
135
+ * @param model - The model name (e.g., 'imagen-3.0-generate-002')
136
+ * @param apiKey - Your Google API key
137
+ * @param config - Optional additional configuration
138
+ * @returns Configured Gemini image adapter instance with resolved types
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const adapter = createGeminiImage('imagen-3.0-generate-002', "your-api-key");
143
+ *
144
+ * const result = await generateImage({
145
+ * adapter,
146
+ * prompt: 'A cute baby sea otter'
147
+ * });
148
+ * ```
149
+ */
150
+ export function createGeminiImage<TModel extends GeminiImageModel>(
151
+ model: TModel,
152
+ apiKey: string,
153
+ config?: Omit<GeminiImageConfig, 'apiKey'>,
154
+ ): GeminiImageAdapter<TModel> {
155
+ return new GeminiImageAdapter({ apiKey, ...config }, model)
156
+ }
157
+
158
+ /**
159
+ * Creates a Gemini image adapter with automatic API key detection from environment variables.
160
+ * Type resolution happens here at the call site.
161
+ *
162
+ * Looks for `GOOGLE_API_KEY` or `GEMINI_API_KEY` in:
163
+ * - `process.env` (Node.js)
164
+ * - `window.env` (Browser with injected env)
165
+ *
166
+ * @param model - The model name (e.g., 'imagen-4.0-generate-001')
167
+ * @param config - Optional configuration (excluding apiKey which is auto-detected)
168
+ * @returns Configured Gemini image adapter instance with resolved types
169
+ * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found in environment
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * // Automatically uses GOOGLE_API_KEY from environment
174
+ * const adapter = geminiImage('imagen-4.0-generate-001');
175
+ *
176
+ * const result = await generateImage({
177
+ * adapter,
178
+ * prompt: 'A beautiful sunset over mountains'
179
+ * });
180
+ * ```
181
+ */
182
+ export function geminiImage<TModel extends GeminiImageModel>(
183
+ model: TModel,
184
+ config?: Omit<GeminiImageConfig, 'apiKey'>,
185
+ ): GeminiImageAdapter<TModel> {
186
+ const apiKey = getGeminiApiKeyFromEnv()
187
+ return createGeminiImage(model, apiKey, config)
188
+ }
@@ -0,0 +1,242 @@
1
+ import { FinishReason } from '@google/genai'
2
+ import {
3
+ createGeminiClient,
4
+ generateId,
5
+ getGeminiApiKeyFromEnv,
6
+ } from '../utils'
7
+
8
+ import type { GoogleGenAI } from '@google/genai'
9
+ import type { SummarizeAdapter } from '@tanstack/ai/adapters'
10
+ import type {
11
+ StreamChunk,
12
+ SummarizationOptions,
13
+ SummarizationResult,
14
+ } from '@tanstack/ai'
15
+
16
+ /**
17
+ * Available Gemini models for summarization
18
+ */
19
+ export const GeminiSummarizeModels = [
20
+ 'gemini-2.0-flash',
21
+ 'gemini-1.5-flash',
22
+ 'gemini-1.5-pro',
23
+ 'gemini-2.0-flash-lite',
24
+ ] as const
25
+
26
+ export type GeminiSummarizeModel = (typeof GeminiSummarizeModels)[number]
27
+
28
+ /**
29
+ * Provider-specific options for Gemini summarization
30
+ */
31
+ export interface GeminiSummarizeProviderOptions {
32
+ /** Generation configuration */
33
+ generationConfig?: {
34
+ temperature?: number
35
+ topP?: number
36
+ topK?: number
37
+ maxOutputTokens?: number
38
+ stopSequences?: Array<string>
39
+ }
40
+ /** Safety settings */
41
+ safetySettings?: Array<{
42
+ category: string
43
+ threshold: string
44
+ }>
45
+ }
46
+
47
+ export interface GeminiSummarizeAdapterOptions {
48
+ // Additional adapter options can be added here
49
+ }
50
+
51
+ /**
52
+ * Gemini Summarize Adapter
53
+ * A tree-shakeable summarization adapter for Google Gemini
54
+ */
55
+ export class GeminiSummarizeAdapter<
56
+ TModel extends GeminiSummarizeModel,
57
+ > implements SummarizeAdapter<TModel, GeminiSummarizeProviderOptions> {
58
+ readonly kind = 'summarize' as const
59
+ readonly name = 'gemini' as const
60
+ readonly model: TModel
61
+
62
+ // Type-only property - never assigned at runtime
63
+ declare '~types': {
64
+ providerOptions: GeminiSummarizeProviderOptions
65
+ }
66
+
67
+ private client: GoogleGenAI
68
+
69
+ constructor(
70
+ apiKeyOrClient: string | GoogleGenAI,
71
+ model: TModel,
72
+ _options: GeminiSummarizeAdapterOptions = {},
73
+ ) {
74
+ this.client =
75
+ typeof apiKeyOrClient === 'string'
76
+ ? createGeminiClient({ apiKey: apiKeyOrClient })
77
+ : apiKeyOrClient
78
+ this.model = model
79
+ }
80
+
81
+ async summarize(options: SummarizationOptions): Promise<SummarizationResult> {
82
+ const model = options.model
83
+
84
+ // Build the system prompt based on format
85
+ const formatInstructions = this.getFormatInstructions(options.style)
86
+ const lengthInstructions = options.maxLength
87
+ ? ` Keep the summary under ${options.maxLength} tokens.`
88
+ : ''
89
+
90
+ const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`
91
+
92
+ const response = await this.client.models.generateContent({
93
+ model,
94
+ contents: [
95
+ {
96
+ role: 'user',
97
+ parts: [
98
+ { text: `Please summarize the following:\n\n${options.text}` },
99
+ ],
100
+ },
101
+ ],
102
+ config: {
103
+ systemInstruction: systemPrompt,
104
+ },
105
+ })
106
+
107
+ const summary = response.text ?? ''
108
+ const inputTokens = response.usageMetadata?.promptTokenCount ?? 0
109
+ const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0
110
+
111
+ return {
112
+ id: generateId('sum'),
113
+ model,
114
+ summary,
115
+ usage: {
116
+ promptTokens: inputTokens,
117
+ completionTokens: outputTokens,
118
+ totalTokens: inputTokens + outputTokens,
119
+ },
120
+ }
121
+ }
122
+
123
+ async *summarizeStream(
124
+ options: SummarizationOptions,
125
+ ): AsyncIterable<StreamChunk> {
126
+ const model = options.model
127
+ const id = generateId('sum')
128
+ let accumulatedContent = ''
129
+ let inputTokens = 0
130
+ let outputTokens = 0
131
+
132
+ // Build the system prompt based on format
133
+ const formatInstructions = this.getFormatInstructions(options.style)
134
+ const lengthInstructions = options.maxLength
135
+ ? ` Keep the summary under ${options.maxLength} words.`
136
+ : ''
137
+
138
+ const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`
139
+
140
+ const result = await this.client.models.generateContentStream({
141
+ model,
142
+ contents: [
143
+ {
144
+ role: 'user',
145
+ parts: [
146
+ { text: `Please summarize the following:\n\n${options.text}` },
147
+ ],
148
+ },
149
+ ],
150
+ config: {
151
+ systemInstruction: systemPrompt,
152
+ },
153
+ })
154
+
155
+ for await (const chunk of result) {
156
+ // Track usage metadata
157
+ if (chunk.usageMetadata) {
158
+ inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens
159
+ outputTokens = chunk.usageMetadata.candidatesTokenCount ?? outputTokens
160
+ }
161
+
162
+ if (chunk.candidates?.[0]?.content?.parts) {
163
+ for (const part of chunk.candidates[0].content.parts) {
164
+ if (part.text) {
165
+ accumulatedContent += part.text
166
+ yield {
167
+ type: 'content',
168
+ id,
169
+ model,
170
+ timestamp: Date.now(),
171
+ delta: part.text,
172
+ content: accumulatedContent,
173
+ role: 'assistant',
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ // Check for finish reason
180
+ const finishReason = chunk.candidates?.[0]?.finishReason
181
+ if (
182
+ finishReason === FinishReason.STOP ||
183
+ finishReason === FinishReason.MAX_TOKENS ||
184
+ finishReason === FinishReason.SAFETY
185
+ ) {
186
+ yield {
187
+ type: 'done',
188
+ id,
189
+ model,
190
+ timestamp: Date.now(),
191
+ finishReason:
192
+ finishReason === FinishReason.STOP
193
+ ? 'stop'
194
+ : finishReason === FinishReason.MAX_TOKENS
195
+ ? 'length'
196
+ : 'content_filter',
197
+ usage: {
198
+ promptTokens: inputTokens,
199
+ completionTokens: outputTokens,
200
+ totalTokens: inputTokens + outputTokens,
201
+ },
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ private getFormatInstructions(
208
+ style?: 'paragraph' | 'bullet-points' | 'concise',
209
+ ): string {
210
+ switch (style) {
211
+ case 'bullet-points':
212
+ return 'Provide the summary as bullet points.'
213
+ case 'concise':
214
+ return 'Provide a very brief one or two sentence summary.'
215
+ case 'paragraph':
216
+ default:
217
+ return 'Provide the summary in paragraph form.'
218
+ }
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Creates a Gemini summarize adapter with explicit API key and model
224
+ */
225
+ export function createGeminiSummarize<TModel extends GeminiSummarizeModel>(
226
+ apiKey: string,
227
+ model: TModel,
228
+ options?: GeminiSummarizeAdapterOptions,
229
+ ): GeminiSummarizeAdapter<TModel> {
230
+ return new GeminiSummarizeAdapter(apiKey, model, options)
231
+ }
232
+
233
+ /**
234
+ * Creates a Gemini summarize adapter with API key from environment and required model
235
+ */
236
+ export function geminiSummarize<TModel extends GeminiSummarizeModel>(
237
+ model: TModel,
238
+ options?: GeminiSummarizeAdapterOptions,
239
+ ): GeminiSummarizeAdapter<TModel> {
240
+ const apiKey = getGeminiApiKeyFromEnv()
241
+ return new GeminiSummarizeAdapter(apiKey, model, options)
242
+ }