@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.
@@ -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.2",
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.15.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.15.0"
47
+ "@tanstack/ai": "0.17.0"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "vite build",
@@ -1,274 +1,59 @@
1
- import { FinishReason } from '@google/genai'
2
- import {
3
- createGeminiClient,
4
- generateId,
5
- getGeminiApiKeyFromEnv,
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
- async summarize(options: SummarizationOptions): Promise<SummarizationResult> {
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
- ): GeminiSummarizeAdapter<TModel> {
262
- return new GeminiSummarizeAdapter({ ...config, apiKey }, model)
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 environment and required model
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
- ): GeminiSummarizeAdapter<TModel> {
272
- const apiKey = getGeminiApiKeyFromEnv()
273
- return new GeminiSummarizeAdapter({ ...config, apiKey }, model)
54
+ ): ChatStreamSummarizeAdapter<
55
+ TModel,
56
+ InferTextProviderOptions<GeminiTextAdapter<TModel>>
57
+ > {
58
+ return createGeminiSummarize(getGeminiApiKeyFromEnv(), model, config)
274
59
  }