@tanstack/ai-gemini 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/adapters/summarize.d.ts +23 -48
- package/dist/esm/adapters/summarize.js +9 -159
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/text.d.ts +2 -2
- package/dist/esm/adapters/text.js +107 -102
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -3
- package/dist/esm/message-types.d.ts +13 -0
- package/package.json +3 -3
- package/src/adapters/summarize.ts +37 -252
- package/src/adapters/text.ts +124 -111
- package/src/index.ts +2 -5
- package/src/message-types.ts +14 -0
|
@@ -89,3 +89,16 @@ export interface GeminiMessageMetadataByModality {
|
|
|
89
89
|
video: GeminiVideoMetadata;
|
|
90
90
|
document: GeminiDocumentMetadata;
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Provider-specific metadata that round-trips with each Gemini tool call.
|
|
94
|
+
*
|
|
95
|
+
* `thoughtSignature` is emitted by Gemini 3.x (and 2.5 thinking) models on
|
|
96
|
+
* the Part containing the `functionCall`. The same signature must be echoed
|
|
97
|
+
* back at the Part level on the next turn or the API rejects the request
|
|
98
|
+
* with `400 INVALID_ARGUMENT: "Function call is missing a thought_signature"`.
|
|
99
|
+
*
|
|
100
|
+
* @see https://ai.google.dev/gemini-api/docs/thinking
|
|
101
|
+
*/
|
|
102
|
+
export interface GeminiToolCallMetadata {
|
|
103
|
+
thoughtSignature?: string;
|
|
104
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-gemini",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "Google Gemini adapter for TanStack AI",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"@tanstack/ai-utils": "0.2.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@tanstack/ai": "^0.
|
|
41
|
+
"@tanstack/ai": "^0.17.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@vitest/coverage-v8": "4.0.14",
|
|
45
45
|
"vite": "^7.2.7",
|
|
46
46
|
"zod": "^4.2.0",
|
|
47
|
-
"@tanstack/ai": "0.
|
|
47
|
+
"@tanstack/ai": "0.17.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "vite build",
|
|
@@ -1,274 +1,59 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from '../utils'
|
|
7
|
-
import type { GoogleGenAI } from '@google/genai'
|
|
1
|
+
import { ChatStreamSummarizeAdapter } from '@tanstack/ai/adapters'
|
|
2
|
+
import { getGeminiApiKeyFromEnv } from '../utils'
|
|
3
|
+
import { GeminiTextAdapter } from './text'
|
|
4
|
+
import type { InferTextProviderOptions } from '@tanstack/ai/adapters'
|
|
5
|
+
import type { GEMINI_MODELS } from '../model-meta'
|
|
8
6
|
import type { GeminiClientConfig } from '../utils'
|
|
9
|
-
import type { SummarizeAdapter } from '@tanstack/ai/adapters'
|
|
10
|
-
import type {
|
|
11
|
-
StreamChunk,
|
|
12
|
-
SummarizationOptions,
|
|
13
|
-
SummarizationResult,
|
|
14
|
-
} from '@tanstack/ai'
|
|
15
|
-
|
|
16
|
-
/** Cast an event object to StreamChunk. */
|
|
17
|
-
const asChunk = (chunk: Record<string, unknown>) =>
|
|
18
|
-
chunk as unknown as StreamChunk
|
|
19
7
|
|
|
20
8
|
/**
|
|
21
9
|
* Configuration for Gemini summarize adapter
|
|
22
10
|
*/
|
|
23
11
|
export interface GeminiSummarizeConfig extends GeminiClientConfig {}
|
|
24
|
-
/**
|
|
25
|
-
* Available Gemini models for summarization
|
|
26
|
-
*/
|
|
27
|
-
export const GeminiSummarizeModels = [
|
|
28
|
-
'gemini-3.1-flash-lite-preview',
|
|
29
|
-
'gemini-2.0-flash',
|
|
30
|
-
'gemini-1.5-flash',
|
|
31
|
-
'gemini-1.5-pro',
|
|
32
|
-
'gemini-2.0-flash-lite',
|
|
33
|
-
] as const
|
|
34
|
-
|
|
35
|
-
export type GeminiSummarizeModel = (typeof GeminiSummarizeModels)[number]
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Provider-specific options for Gemini summarization
|
|
39
|
-
*/
|
|
40
|
-
export interface GeminiSummarizeProviderOptions {
|
|
41
|
-
/** Generation configuration */
|
|
42
|
-
generationConfig?: {
|
|
43
|
-
temperature?: number
|
|
44
|
-
topP?: number
|
|
45
|
-
topK?: number
|
|
46
|
-
maxOutputTokens?: number
|
|
47
|
-
stopSequences?: Array<string>
|
|
48
|
-
}
|
|
49
|
-
/** Safety settings */
|
|
50
|
-
safetySettings?: Array<{
|
|
51
|
-
category: string
|
|
52
|
-
threshold: string
|
|
53
|
-
}>
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface GeminiSummarizeAdapterOptions {
|
|
57
|
-
// Additional adapter options can be added here
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Gemini Summarize Adapter
|
|
62
|
-
* A tree-shakeable summarization adapter for Google Gemini
|
|
63
|
-
*/
|
|
64
|
-
export class GeminiSummarizeAdapter<
|
|
65
|
-
TModel extends GeminiSummarizeModel,
|
|
66
|
-
> implements SummarizeAdapter<TModel, GeminiSummarizeProviderOptions> {
|
|
67
|
-
readonly kind = 'summarize' as const
|
|
68
|
-
readonly name = 'gemini' as const
|
|
69
|
-
readonly model: TModel
|
|
70
|
-
|
|
71
|
-
// Type-only property - never assigned at runtime
|
|
72
|
-
declare '~types': {
|
|
73
|
-
providerOptions: GeminiSummarizeProviderOptions
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private client: GoogleGenAI
|
|
77
|
-
|
|
78
|
-
constructor(config: GeminiSummarizeConfig, model: TModel) {
|
|
79
|
-
this.client = createGeminiClient(config)
|
|
80
|
-
this.model = model
|
|
81
|
-
}
|
|
82
12
|
|
|
83
|
-
|
|
84
|
-
const { logger } = options
|
|
85
|
-
const model = options.model
|
|
86
|
-
|
|
87
|
-
logger.request(`activity=summarize provider=gemini`, {
|
|
88
|
-
provider: 'gemini',
|
|
89
|
-
model,
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// Build the system prompt based on format
|
|
93
|
-
const formatInstructions = this.getFormatInstructions(options.style)
|
|
94
|
-
const lengthInstructions = options.maxLength
|
|
95
|
-
? ` Keep the summary under ${options.maxLength} tokens.`
|
|
96
|
-
: ''
|
|
97
|
-
|
|
98
|
-
const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const response = await this.client.models.generateContent({
|
|
102
|
-
model,
|
|
103
|
-
contents: [
|
|
104
|
-
{
|
|
105
|
-
role: 'user',
|
|
106
|
-
parts: [
|
|
107
|
-
{ text: `Please summarize the following:\n\n${options.text}` },
|
|
108
|
-
],
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
config: {
|
|
112
|
-
systemInstruction: systemPrompt,
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const summary = response.text ?? ''
|
|
117
|
-
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0
|
|
118
|
-
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
id: generateId('sum'),
|
|
122
|
-
model,
|
|
123
|
-
summary,
|
|
124
|
-
usage: {
|
|
125
|
-
promptTokens: inputTokens,
|
|
126
|
-
completionTokens: outputTokens,
|
|
127
|
-
totalTokens: inputTokens + outputTokens,
|
|
128
|
-
},
|
|
129
|
-
}
|
|
130
|
-
} catch (error) {
|
|
131
|
-
logger.errors('gemini.summarize fatal', {
|
|
132
|
-
error,
|
|
133
|
-
source: 'gemini.summarize',
|
|
134
|
-
})
|
|
135
|
-
throw error
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async *summarizeStream(
|
|
140
|
-
options: SummarizationOptions,
|
|
141
|
-
): AsyncIterable<StreamChunk> {
|
|
142
|
-
const { logger } = options
|
|
143
|
-
const model = options.model
|
|
144
|
-
const id = generateId('sum')
|
|
145
|
-
let accumulatedContent = ''
|
|
146
|
-
let inputTokens = 0
|
|
147
|
-
let outputTokens = 0
|
|
148
|
-
|
|
149
|
-
// Build the system prompt based on format
|
|
150
|
-
const formatInstructions = this.getFormatInstructions(options.style)
|
|
151
|
-
const lengthInstructions = options.maxLength
|
|
152
|
-
? ` Keep the summary under ${options.maxLength} words.`
|
|
153
|
-
: ''
|
|
154
|
-
|
|
155
|
-
const systemPrompt = `You are a helpful assistant that summarizes text. ${formatInstructions}${lengthInstructions}`
|
|
156
|
-
|
|
157
|
-
logger.request(`activity=summarize provider=gemini`, {
|
|
158
|
-
provider: 'gemini',
|
|
159
|
-
model,
|
|
160
|
-
stream: true,
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
const result = await this.client.models.generateContentStream({
|
|
165
|
-
model,
|
|
166
|
-
contents: [
|
|
167
|
-
{
|
|
168
|
-
role: 'user',
|
|
169
|
-
parts: [
|
|
170
|
-
{ text: `Please summarize the following:\n\n${options.text}` },
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
],
|
|
174
|
-
config: {
|
|
175
|
-
systemInstruction: systemPrompt,
|
|
176
|
-
},
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
for await (const chunk of result) {
|
|
180
|
-
logger.provider(`provider=gemini`, { chunk })
|
|
181
|
-
// Track usage metadata
|
|
182
|
-
if (chunk.usageMetadata) {
|
|
183
|
-
inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens
|
|
184
|
-
outputTokens =
|
|
185
|
-
chunk.usageMetadata.candidatesTokenCount ?? outputTokens
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (chunk.candidates?.[0]?.content?.parts) {
|
|
189
|
-
for (const part of chunk.candidates[0].content.parts) {
|
|
190
|
-
if (part.text) {
|
|
191
|
-
accumulatedContent += part.text
|
|
192
|
-
yield asChunk({
|
|
193
|
-
type: 'TEXT_MESSAGE_CONTENT',
|
|
194
|
-
messageId: id,
|
|
195
|
-
model,
|
|
196
|
-
timestamp: Date.now(),
|
|
197
|
-
delta: part.text,
|
|
198
|
-
content: accumulatedContent,
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Check for finish reason
|
|
205
|
-
const finishReason = chunk.candidates?.[0]?.finishReason
|
|
206
|
-
if (
|
|
207
|
-
finishReason === FinishReason.STOP ||
|
|
208
|
-
finishReason === FinishReason.MAX_TOKENS ||
|
|
209
|
-
finishReason === FinishReason.SAFETY
|
|
210
|
-
) {
|
|
211
|
-
yield asChunk({
|
|
212
|
-
type: 'RUN_FINISHED',
|
|
213
|
-
runId: id,
|
|
214
|
-
model,
|
|
215
|
-
timestamp: Date.now(),
|
|
216
|
-
finishReason:
|
|
217
|
-
finishReason === FinishReason.STOP
|
|
218
|
-
? 'stop'
|
|
219
|
-
: finishReason === FinishReason.MAX_TOKENS
|
|
220
|
-
? 'length'
|
|
221
|
-
: 'content_filter',
|
|
222
|
-
usage: {
|
|
223
|
-
promptTokens: inputTokens,
|
|
224
|
-
completionTokens: outputTokens,
|
|
225
|
-
totalTokens: inputTokens + outputTokens,
|
|
226
|
-
},
|
|
227
|
-
})
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
} catch (error) {
|
|
231
|
-
logger.errors('gemini.summarize fatal', {
|
|
232
|
-
error,
|
|
233
|
-
source: 'gemini.summarize',
|
|
234
|
-
})
|
|
235
|
-
throw error
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private getFormatInstructions(
|
|
240
|
-
style?: 'paragraph' | 'bullet-points' | 'concise',
|
|
241
|
-
): string {
|
|
242
|
-
switch (style) {
|
|
243
|
-
case 'bullet-points':
|
|
244
|
-
return 'Provide the summary as bullet points.'
|
|
245
|
-
case 'concise':
|
|
246
|
-
return 'Provide a very brief one or two sentence summary.'
|
|
247
|
-
case 'paragraph':
|
|
248
|
-
default:
|
|
249
|
-
return 'Provide the summary in paragraph form.'
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
13
|
+
export type GeminiSummarizeModel = (typeof GEMINI_MODELS)[number]
|
|
253
14
|
|
|
254
15
|
/**
|
|
255
|
-
* Creates a Gemini summarize adapter with explicit API key and model
|
|
16
|
+
* Creates a Gemini summarize adapter with explicit API key and model.
|
|
17
|
+
*
|
|
18
|
+
* Note: keeps the historical (apiKey, model, config) argument order to
|
|
19
|
+
* avoid breaking existing callers.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const adapter = createGeminiSummarize('AIza...', 'gemini-2.0-flash');
|
|
24
|
+
* ```
|
|
256
25
|
*/
|
|
257
26
|
export function createGeminiSummarize<TModel extends GeminiSummarizeModel>(
|
|
258
27
|
apiKey: string,
|
|
259
28
|
model: TModel,
|
|
260
29
|
config?: Omit<GeminiSummarizeConfig, 'apiKey'>,
|
|
261
|
-
):
|
|
262
|
-
|
|
30
|
+
): ChatStreamSummarizeAdapter<
|
|
31
|
+
TModel,
|
|
32
|
+
InferTextProviderOptions<GeminiTextAdapter<TModel>>
|
|
33
|
+
> {
|
|
34
|
+
return new ChatStreamSummarizeAdapter(
|
|
35
|
+
new GeminiTextAdapter({ ...config, apiKey }, model),
|
|
36
|
+
model,
|
|
37
|
+
'gemini',
|
|
38
|
+
)
|
|
263
39
|
}
|
|
264
40
|
|
|
265
41
|
/**
|
|
266
|
-
* Creates a Gemini summarize adapter with API key from
|
|
42
|
+
* Creates a Gemini summarize adapter with API key from `GOOGLE_API_KEY` /
|
|
43
|
+
* `GEMINI_API_KEY` environment variables.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const adapter = geminiSummarize('gemini-2.0-flash');
|
|
48
|
+
* await summarize({ adapter, text: 'Long article text...' });
|
|
49
|
+
* ```
|
|
267
50
|
*/
|
|
268
51
|
export function geminiSummarize<TModel extends GeminiSummarizeModel>(
|
|
269
52
|
model: TModel,
|
|
270
53
|
config?: Omit<GeminiSummarizeConfig, 'apiKey'>,
|
|
271
|
-
):
|
|
272
|
-
|
|
273
|
-
|
|
54
|
+
): ChatStreamSummarizeAdapter<
|
|
55
|
+
TModel,
|
|
56
|
+
InferTextProviderOptions<GeminiTextAdapter<TModel>>
|
|
57
|
+
> {
|
|
58
|
+
return createGeminiSummarize(getGeminiApiKeyFromEnv(), model, config)
|
|
274
59
|
}
|