@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.
- package/README.md +26 -0
- package/dist/esm/adapters/image.d.ts +83 -0
- package/dist/esm/adapters/image.js +60 -0
- package/dist/esm/adapters/image.js.map +1 -0
- package/dist/esm/adapters/summarize.d.ts +53 -0
- package/dist/esm/adapters/summarize.js +139 -0
- package/dist/esm/adapters/summarize.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +63 -0
- package/dist/esm/{gemini-adapter.js → adapters/text.js} +86 -132
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/adapters/tts.d.ts +129 -0
- package/dist/esm/adapters/tts.js +78 -0
- package/dist/esm/adapters/tts.js.map +1 -0
- package/dist/esm/image/image-provider-options.d.ts +139 -0
- package/dist/esm/image/image-provider-options.js +53 -0
- package/dist/esm/image/image-provider-options.js.map +1 -0
- package/dist/esm/index.d.ts +15 -2
- package/dist/esm/index.js +22 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model-meta.d.ts +128 -1
- package/dist/esm/model-meta.js +71 -5
- package/dist/esm/model-meta.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/utils/client.d.ts +17 -0
- package/dist/esm/utils/client.js +25 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/utils/index.d.ts +1 -0
- package/package.json +5 -4
- package/src/adapters/image.ts +188 -0
- package/src/adapters/summarize.ts +242 -0
- package/src/{gemini-adapter.ts → adapters/text.ts} +196 -245
- package/src/adapters/tts.ts +223 -0
- package/src/image/image-provider-options.ts +239 -0
- package/src/index.ts +66 -2
- package/src/model-meta.ts +87 -62
- package/src/tools/tool-converter.ts +6 -5
- package/src/utils/client.ts +43 -0
- package/src/utils/index.ts +6 -0
- package/dist/esm/gemini-adapter.d.ts +0 -71
- 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 {
|
|
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
|
+
"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
|
|
34
|
+
"@tanstack/ai": "0.1.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@vitest/coverage-v8": "4.0.14",
|
|
38
|
-
"vite": "^7.2.
|
|
38
|
+
"vite": "^7.2.7"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"
|
|
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
|
+
}
|