@tanstack/ai-gemini 0.8.8 → 0.9.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 (47) hide show
  1. package/dist/esm/adapters/summarize.js +5 -4
  2. package/dist/esm/adapters/summarize.js.map +1 -1
  3. package/dist/esm/adapters/text.d.ts +9 -4
  4. package/dist/esm/adapters/text.js +102 -35
  5. package/dist/esm/adapters/text.js.map +1 -1
  6. package/dist/esm/index.d.ts +1 -1
  7. package/dist/esm/model-meta.d.ts +38 -10
  8. package/dist/esm/model-meta.js.map +1 -1
  9. package/dist/esm/tools/code-execution-tool.d.ts +6 -3
  10. package/dist/esm/tools/code-execution-tool.js +8 -0
  11. package/dist/esm/tools/code-execution-tool.js.map +1 -1
  12. package/dist/esm/tools/computer-use-tool.d.ts +6 -3
  13. package/dist/esm/tools/computer-use-tool.js +11 -0
  14. package/dist/esm/tools/computer-use-tool.js.map +1 -1
  15. package/dist/esm/tools/file-search-tool.d.ts +6 -3
  16. package/dist/esm/tools/file-search-tool.js +9 -1
  17. package/dist/esm/tools/file-search-tool.js.map +1 -1
  18. package/dist/esm/tools/function-declaration-tool.js +29 -0
  19. package/dist/esm/tools/function-declaration-tool.js.map +1 -0
  20. package/dist/esm/tools/google-maps-tool.d.ts +6 -3
  21. package/dist/esm/tools/google-maps-tool.js +9 -1
  22. package/dist/esm/tools/google-maps-tool.js.map +1 -1
  23. package/dist/esm/tools/google-search-retriveal-tool.d.ts +6 -3
  24. package/dist/esm/tools/google-search-retriveal-tool.js +9 -1
  25. package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -1
  26. package/dist/esm/tools/google-search-tool.d.ts +6 -3
  27. package/dist/esm/tools/google-search-tool.js +9 -1
  28. package/dist/esm/tools/google-search-tool.js.map +1 -1
  29. package/dist/esm/tools/index.d.ts +9 -0
  30. package/dist/esm/tools/index.js +21 -0
  31. package/dist/esm/tools/index.js.map +1 -0
  32. package/dist/esm/tools/url-context-tool.d.ts +6 -3
  33. package/dist/esm/tools/url-context-tool.js +9 -1
  34. package/dist/esm/tools/url-context-tool.js.map +1 -1
  35. package/package.json +8 -3
  36. package/src/adapters/summarize.ts +8 -4
  37. package/src/adapters/text.ts +142 -40
  38. package/src/index.ts +1 -0
  39. package/src/model-meta.ts +59 -72
  40. package/src/tools/code-execution-tool.ts +10 -4
  41. package/src/tools/computer-use-tool.ts +13 -5
  42. package/src/tools/file-search-tool.ts +13 -5
  43. package/src/tools/google-maps-tool.ts +13 -5
  44. package/src/tools/google-search-retriveal-tool.ts +15 -6
  45. package/src/tools/google-search-tool.ts +13 -5
  46. package/src/tools/index.ts +51 -0
  47. package/src/tools/url-context-tool.ts +10 -4
@@ -0,0 +1,21 @@
1
+ import { codeExecutionTool } from "./code-execution-tool.js";
2
+ import { computerUseTool } from "./computer-use-tool.js";
3
+ import { fileSearchTool } from "./file-search-tool.js";
4
+ import { functionDeclarationTools } from "./function-declaration-tool.js";
5
+ import { googleMapsTool } from "./google-maps-tool.js";
6
+ import { googleSearchRetrievalTool } from "./google-search-retriveal-tool.js";
7
+ import { googleSearchTool } from "./google-search-tool.js";
8
+ import { urlContextTool } from "./url-context-tool.js";
9
+ import { convertToolsToProviderFormat } from "./tool-converter.js";
10
+ export {
11
+ codeExecutionTool,
12
+ computerUseTool,
13
+ convertToolsToProviderFormat,
14
+ fileSearchTool,
15
+ functionDeclarationTools,
16
+ googleMapsTool,
17
+ googleSearchRetrievalTool,
18
+ googleSearchTool,
19
+ urlContextTool
20
+ };
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
@@ -1,7 +1,10 @@
1
- import { Tool } from '@tanstack/ai';
2
- export interface UrlContextTool {
1
+ import { ProviderTool, Tool } from '@tanstack/ai';
2
+ export interface UrlContextToolConfig {
3
3
  }
4
+ /** @deprecated Renamed to `UrlContextToolConfig`. Will be removed in a future release. */
5
+ export type UrlContextTool = UrlContextToolConfig;
6
+ export type GeminiUrlContextTool = ProviderTool<'gemini', 'url_context'>;
4
7
  export declare function convertUrlContextToolToAdapterFormat(_tool: Tool): {
5
8
  urlContext: {};
6
9
  };
7
- export declare function urlContextTool(): Tool;
10
+ export declare function urlContextTool(): GeminiUrlContextTool;
@@ -3,7 +3,15 @@ function convertUrlContextToolToAdapterFormat(_tool) {
3
3
  urlContext: {}
4
4
  };
5
5
  }
6
+ function urlContextTool() {
7
+ return {
8
+ name: "url_context",
9
+ description: "",
10
+ metadata: {}
11
+ };
12
+ }
6
13
  export {
7
- convertUrlContextToolToAdapterFormat
14
+ convertUrlContextToolToAdapterFormat,
15
+ urlContextTool
8
16
  };
9
17
  //# sourceMappingURL=url-context-tool.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"url-context-tool.js","sources":["../../../src/tools/url-context-tool.ts"],"sourcesContent":["import type { Tool } from '@tanstack/ai'\n\nexport interface UrlContextTool {}\n\nexport function convertUrlContextToolToAdapterFormat(_tool: Tool) {\n return {\n urlContext: {},\n }\n}\n\nexport function urlContextTool(): Tool {\n return {\n name: 'url_context',\n description: '',\n metadata: {},\n }\n}\n"],"names":[],"mappings":"AAIO,SAAS,qCAAqC,OAAa;AAChE,SAAO;AAAA,IACL,YAAY,CAAA;AAAA,EAAC;AAEjB;"}
1
+ {"version":3,"file":"url-context-tool.js","sources":["../../../src/tools/url-context-tool.ts"],"sourcesContent":["import type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport interface UrlContextToolConfig {}\n\n/** @deprecated Renamed to `UrlContextToolConfig`. Will be removed in a future release. */\nexport type UrlContextTool = UrlContextToolConfig\n\nexport type GeminiUrlContextTool = ProviderTool<'gemini', 'url_context'>\n\nexport function convertUrlContextToolToAdapterFormat(_tool: Tool) {\n return {\n urlContext: {},\n }\n}\n\nexport function urlContextTool(): GeminiUrlContextTool {\n // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.\n return {\n name: 'url_context',\n description: '',\n metadata: {},\n } as unknown as GeminiUrlContextTool\n}\n"],"names":[],"mappings":"AASO,SAAS,qCAAqC,OAAa;AAChE,SAAO;AAAA,IACL,YAAY,CAAA;AAAA,EAAC;AAEjB;AAEO,SAAS,iBAAuC;AAErD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU,CAAA;AAAA,EAAC;AAEf;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-gemini",
3
- "version": "0.8.8",
3
+ "version": "0.9.0",
4
4
  "description": "Google Gemini adapter for TanStack AI",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -16,6 +16,10 @@
16
16
  ".": {
17
17
  "types": "./dist/esm/index.d.ts",
18
18
  "import": "./dist/esm/index.js"
19
+ },
20
+ "./tools": {
21
+ "types": "./dist/esm/tools/index.d.ts",
22
+ "import": "./dist/esm/tools/index.js"
19
23
  }
20
24
  },
21
25
  "files": [
@@ -33,12 +37,13 @@
33
37
  "@google/genai": "^1.43.0"
34
38
  },
35
39
  "peerDependencies": {
36
- "@tanstack/ai": "^0.10.3"
40
+ "@tanstack/ai": "^0.12.0"
37
41
  },
38
42
  "devDependencies": {
39
43
  "@vitest/coverage-v8": "4.0.14",
40
44
  "vite": "^7.2.7",
41
- "@tanstack/ai": "0.10.3"
45
+ "zod": "^4.2.0",
46
+ "@tanstack/ai": "0.12.0"
42
47
  },
43
48
  "scripts": {
44
49
  "build": "vite build",
@@ -13,6 +13,10 @@ import type {
13
13
  SummarizationResult,
14
14
  } from '@tanstack/ai'
15
15
 
16
+ /** Cast an event object to StreamChunk. */
17
+ const asChunk = (chunk: Record<string, unknown>) =>
18
+ chunk as unknown as StreamChunk
19
+
16
20
  /**
17
21
  * Configuration for Gemini summarize adapter
18
22
  */
@@ -161,14 +165,14 @@ export class GeminiSummarizeAdapter<
161
165
  for (const part of chunk.candidates[0].content.parts) {
162
166
  if (part.text) {
163
167
  accumulatedContent += part.text
164
- yield {
168
+ yield asChunk({
165
169
  type: 'TEXT_MESSAGE_CONTENT',
166
170
  messageId: id,
167
171
  model,
168
172
  timestamp: Date.now(),
169
173
  delta: part.text,
170
174
  content: accumulatedContent,
171
- }
175
+ })
172
176
  }
173
177
  }
174
178
  }
@@ -180,7 +184,7 @@ export class GeminiSummarizeAdapter<
180
184
  finishReason === FinishReason.MAX_TOKENS ||
181
185
  finishReason === FinishReason.SAFETY
182
186
  ) {
183
- yield {
187
+ yield asChunk({
184
188
  type: 'RUN_FINISHED',
185
189
  runId: id,
186
190
  model,
@@ -196,7 +200,7 @@ export class GeminiSummarizeAdapter<
196
200
  completionTokens: outputTokens,
197
201
  totalTokens: inputTokens + outputTokens,
198
202
  },
199
- }
203
+ })
200
204
  }
201
205
  }
202
206
  }
@@ -9,6 +9,7 @@ import {
9
9
  import type {
10
10
  GEMINI_MODELS,
11
11
  GeminiChatModelProviderOptionsByName,
12
+ GeminiChatModelToolCapabilitiesByName,
12
13
  GeminiModelInputModalitiesByName,
13
14
  } from '../model-meta'
14
15
  import type {
@@ -34,6 +35,11 @@ import type { ExternalTextProviderOptions } from '../text/text-provider-options'
34
35
  import type { GeminiMessageMetadataByModality } from '../message-types'
35
36
  import type { GeminiClientConfig } from '../utils'
36
37
 
38
+ /** Cast an event object to StreamChunk. Adapters construct events with string
39
+ * literal types which are structurally compatible with the EventType enum. */
40
+ const asChunk = (chunk: Record<string, unknown>) =>
41
+ chunk as unknown as StreamChunk
42
+
37
43
  /**
38
44
  * Configuration for Gemini text adapter
39
45
  */
@@ -66,6 +72,15 @@ type ResolveInputModalities<TModel extends string> =
66
72
  ? GeminiModelInputModalitiesByName[TModel]
67
73
  : readonly ['text', 'image', 'audio', 'video', 'document']
68
74
 
75
+ /**
76
+ * Resolve tool capabilities for a specific model.
77
+ * If the model has explicit tools in the map, use those; otherwise use empty tuple.
78
+ */
79
+ type ResolveToolCapabilities<TModel extends string> =
80
+ TModel extends keyof GeminiChatModelToolCapabilitiesByName
81
+ ? NonNullable<GeminiChatModelToolCapabilitiesByName[TModel]>
82
+ : readonly []
83
+
69
84
  // ===========================
70
85
  // Adapter Implementation
71
86
  // ===========================
@@ -78,14 +93,17 @@ type ResolveInputModalities<TModel extends string> =
78
93
  */
79
94
  export class GeminiTextAdapter<
80
95
  TModel extends (typeof GEMINI_MODELS)[number],
81
- TProviderOptions extends object = ResolveProviderOptions<TModel>,
96
+ TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,
82
97
  TInputModalities extends ReadonlyArray<Modality> =
83
98
  ResolveInputModalities<TModel>,
99
+ TToolCapabilities extends ReadonlyArray<string> =
100
+ ResolveToolCapabilities<TModel>,
84
101
  > extends BaseTextAdapter<
85
102
  TModel,
86
103
  TProviderOptions,
87
104
  TInputModalities,
88
- GeminiMessageMetadataByModality
105
+ GeminiMessageMetadataByModality,
106
+ TToolCapabilities
89
107
  > {
90
108
  readonly kind = 'text' as const
91
109
  readonly name = 'gemini' as const
@@ -106,20 +124,24 @@ export class GeminiTextAdapter<
106
124
  const result =
107
125
  await this.client.models.generateContentStream(mappedOptions)
108
126
 
109
- yield* this.processStreamChunks(result, options.model)
127
+ yield* this.processStreamChunks(result, options)
110
128
  } catch (error) {
111
129
  const timestamp = Date.now()
112
- yield {
130
+ yield asChunk({
113
131
  type: 'RUN_ERROR',
114
132
  model: options.model,
115
133
  timestamp,
134
+ message:
135
+ error instanceof Error
136
+ ? error.message
137
+ : 'An unknown error occurred during the chat stream.',
116
138
  error: {
117
139
  message:
118
140
  error instanceof Error
119
141
  ? error.message
120
142
  : 'An unknown error occurred during the chat stream.',
121
143
  },
122
- }
144
+ })
123
145
  }
124
146
  }
125
147
 
@@ -191,8 +213,9 @@ export class GeminiTextAdapter<
191
213
 
192
214
  private async *processStreamChunks(
193
215
  result: AsyncGenerator<GenerateContentResponse, unknown, unknown>,
194
- model: string,
216
+ options: TextOptions<GeminiTextProviderOptions>,
195
217
  ): AsyncIterable<StreamChunk> {
218
+ const model = options.model
196
219
  const timestamp = Date.now()
197
220
  let accumulatedContent = ''
198
221
  let accumulatedThinking = ''
@@ -209,9 +232,12 @@ export class GeminiTextAdapter<
209
232
  let nextToolIndex = 0
210
233
 
211
234
  // AG-UI lifecycle tracking
212
- const runId = generateId(this.name)
235
+ const runId = options.runId ?? generateId(this.name)
236
+ const threadId = options.threadId ?? generateId(this.name)
213
237
  const messageId = generateId(this.name)
214
238
  let stepId: string | null = null
239
+ let reasoningMessageId: string | null = null
240
+ let hasClosedReasoning = false
215
241
  let hasEmittedRunStarted = false
216
242
  let hasEmittedTextMessageStart = false
217
243
  let hasEmittedStepStarted = false
@@ -220,12 +246,13 @@ export class GeminiTextAdapter<
220
246
  // Emit RUN_STARTED on first chunk
221
247
  if (!hasEmittedRunStarted) {
222
248
  hasEmittedRunStarted = true
223
- yield {
249
+ yield asChunk({
224
250
  type: 'RUN_STARTED',
225
251
  runId,
252
+ threadId,
226
253
  model,
227
254
  timestamp,
228
- }
255
+ })
229
256
  }
230
257
 
231
258
  if (chunk.candidates?.[0]?.content?.parts) {
@@ -234,51 +261,99 @@ export class GeminiTextAdapter<
234
261
  for (const part of parts) {
235
262
  if (part.text) {
236
263
  if (part.thought) {
237
- // Emit STEP_STARTED on first thinking content
264
+ // Emit STEP_STARTED and REASONING events on first thinking content
238
265
  if (!hasEmittedStepStarted) {
239
266
  hasEmittedStepStarted = true
240
267
  stepId = generateId(this.name)
241
- yield {
268
+ reasoningMessageId = generateId(this.name)
269
+
270
+ // Spec REASONING events
271
+ yield asChunk({
272
+ type: 'REASONING_START',
273
+ messageId: reasoningMessageId,
274
+ model,
275
+ timestamp,
276
+ })
277
+ yield asChunk({
278
+ type: 'REASONING_MESSAGE_START',
279
+ messageId: reasoningMessageId,
280
+ role: 'reasoning' as const,
281
+ model,
282
+ timestamp,
283
+ })
284
+
285
+ // Legacy STEP events (kept during transition)
286
+ yield asChunk({
242
287
  type: 'STEP_STARTED',
288
+ stepName: stepId,
243
289
  stepId,
244
290
  model,
245
291
  timestamp,
246
292
  stepType: 'thinking',
247
- }
293
+ })
248
294
  }
249
295
 
250
296
  accumulatedThinking += part.text
251
- yield {
297
+
298
+ // Spec REASONING content event
299
+ yield asChunk({
300
+ type: 'REASONING_MESSAGE_CONTENT',
301
+ messageId: reasoningMessageId!,
302
+ delta: part.text,
303
+ model,
304
+ timestamp,
305
+ })
306
+
307
+ // Legacy STEP event
308
+ yield asChunk({
252
309
  type: 'STEP_FINISHED',
310
+ stepName: stepId || generateId(this.name),
253
311
  stepId: stepId || generateId(this.name),
254
312
  model,
255
313
  timestamp,
256
314
  delta: part.text,
257
315
  content: accumulatedThinking,
258
- }
316
+ })
259
317
  } else if (part.text.trim()) {
318
+ // Close reasoning before text starts
319
+ if (reasoningMessageId && !hasClosedReasoning) {
320
+ hasClosedReasoning = true
321
+ yield asChunk({
322
+ type: 'REASONING_MESSAGE_END',
323
+ messageId: reasoningMessageId,
324
+ model,
325
+ timestamp,
326
+ })
327
+ yield asChunk({
328
+ type: 'REASONING_END',
329
+ messageId: reasoningMessageId,
330
+ model,
331
+ timestamp,
332
+ })
333
+ }
334
+
260
335
  // Skip whitespace-only text parts (e.g. "\n" during auto-continuation)
261
336
  // Emit TEXT_MESSAGE_START on first text content
262
337
  if (!hasEmittedTextMessageStart) {
263
338
  hasEmittedTextMessageStart = true
264
- yield {
339
+ yield asChunk({
265
340
  type: 'TEXT_MESSAGE_START',
266
341
  messageId,
267
342
  model,
268
343
  timestamp,
269
344
  role: 'assistant',
270
- }
345
+ })
271
346
  }
272
347
 
273
348
  accumulatedContent += part.text
274
- yield {
349
+ yield asChunk({
275
350
  type: 'TEXT_MESSAGE_CONTENT',
276
351
  messageId,
277
352
  model,
278
353
  timestamp,
279
354
  delta: part.text,
280
355
  content: accumulatedContent,
281
- }
356
+ })
282
357
  }
283
358
  }
284
359
 
@@ -323,9 +398,10 @@ export class GeminiTextAdapter<
323
398
  // Emit TOOL_CALL_START if not already started
324
399
  if (!toolCallData.started) {
325
400
  toolCallData.started = true
326
- yield {
401
+ yield asChunk({
327
402
  type: 'TOOL_CALL_START',
328
403
  toolCallId,
404
+ toolCallName: toolCallData.name,
329
405
  toolName: toolCallData.name,
330
406
  model,
331
407
  timestamp,
@@ -335,18 +411,18 @@ export class GeminiTextAdapter<
335
411
  thoughtSignature: toolCallData.thoughtSignature,
336
412
  },
337
413
  }),
338
- }
414
+ })
339
415
  }
340
416
 
341
417
  // Emit TOOL_CALL_ARGS
342
- yield {
418
+ yield asChunk({
343
419
  type: 'TOOL_CALL_ARGS',
344
420
  toolCallId,
345
421
  model,
346
422
  timestamp,
347
423
  delta: toolCallData.args,
348
424
  args: toolCallData.args,
349
- }
425
+ })
350
426
  }
351
427
  }
352
428
  } else if (chunk.data && chunk.data.trim()) {
@@ -354,24 +430,24 @@ export class GeminiTextAdapter<
354
430
  // Emit TEXT_MESSAGE_START on first text content
355
431
  if (!hasEmittedTextMessageStart) {
356
432
  hasEmittedTextMessageStart = true
357
- yield {
433
+ yield asChunk({
358
434
  type: 'TEXT_MESSAGE_START',
359
435
  messageId,
360
436
  model,
361
437
  timestamp,
362
438
  role: 'assistant',
363
- }
439
+ })
364
440
  }
365
441
 
366
442
  accumulatedContent += chunk.data
367
- yield {
443
+ yield asChunk({
368
444
  type: 'TEXT_MESSAGE_CONTENT',
369
445
  messageId,
370
446
  model,
371
447
  timestamp,
372
448
  delta: chunk.data,
373
449
  content: accumulatedContent,
374
- }
450
+ })
375
451
  }
376
452
 
377
453
  if (chunk.candidates?.[0]?.finishReason) {
@@ -400,14 +476,15 @@ export class GeminiTextAdapter<
400
476
  })
401
477
 
402
478
  // Emit TOOL_CALL_START
403
- yield {
479
+ yield asChunk({
404
480
  type: 'TOOL_CALL_START',
405
481
  toolCallId,
482
+ toolCallName: functionCall.name || '',
406
483
  toolName: functionCall.name || '',
407
484
  model,
408
485
  timestamp,
409
486
  index: nextToolIndex - 1,
410
- }
487
+ })
411
488
 
412
489
  // Emit TOOL_CALL_END with parsed input
413
490
  let parsedInput: unknown = {}
@@ -422,14 +499,15 @@ export class GeminiTextAdapter<
422
499
  parsedInput = {}
423
500
  }
424
501
 
425
- yield {
502
+ yield asChunk({
426
503
  type: 'TOOL_CALL_END',
427
504
  toolCallId,
505
+ toolCallName: functionCall.name || '',
428
506
  toolName: functionCall.name || '',
429
507
  model,
430
508
  timestamp,
431
509
  input: parsedInput,
432
- }
510
+ })
433
511
  }
434
512
  }
435
513
  }
@@ -445,14 +523,15 @@ export class GeminiTextAdapter<
445
523
  parsedInput = {}
446
524
  }
447
525
 
448
- yield {
526
+ yield asChunk({
449
527
  type: 'TOOL_CALL_END',
450
528
  toolCallId,
529
+ toolCallName: toolCallData.name,
451
530
  toolName: toolCallData.name,
452
531
  model,
453
532
  timestamp,
454
533
  input: parsedInput,
455
- }
534
+ })
456
535
  }
457
536
 
458
537
  // Reset so a new TEXT_MESSAGE_START is emitted if text follows tool calls
@@ -461,32 +540,53 @@ export class GeminiTextAdapter<
461
540
  }
462
541
 
463
542
  if (finishReason === FinishReason.MAX_TOKENS) {
464
- yield {
543
+ yield asChunk({
465
544
  type: 'RUN_ERROR',
466
545
  runId,
467
546
  model,
468
547
  timestamp,
548
+ message:
549
+ 'The response was cut off because the maximum token limit was reached.',
550
+ code: 'max_tokens',
469
551
  error: {
470
552
  message:
471
553
  'The response was cut off because the maximum token limit was reached.',
472
554
  code: 'max_tokens',
473
555
  },
474
- }
556
+ })
557
+ }
558
+
559
+ // Close reasoning events if still open
560
+ if (reasoningMessageId && !hasClosedReasoning) {
561
+ hasClosedReasoning = true
562
+ yield asChunk({
563
+ type: 'REASONING_MESSAGE_END',
564
+ messageId: reasoningMessageId,
565
+ model,
566
+ timestamp,
567
+ })
568
+ yield asChunk({
569
+ type: 'REASONING_END',
570
+ messageId: reasoningMessageId,
571
+ model,
572
+ timestamp,
573
+ })
475
574
  }
476
575
 
477
576
  // Emit TEXT_MESSAGE_END if we had text content
478
577
  if (hasEmittedTextMessageStart) {
479
- yield {
578
+ yield asChunk({
480
579
  type: 'TEXT_MESSAGE_END',
481
580
  messageId,
482
581
  model,
483
582
  timestamp,
484
- }
583
+ })
485
584
  }
486
585
 
487
- yield {
586
+ yield asChunk({
488
587
  type: 'RUN_FINISHED',
489
588
  runId,
589
+ threadId,
490
590
  model,
491
591
  timestamp,
492
592
  finishReason: toolCallMap.size > 0 ? 'tool_calls' : 'stop',
@@ -497,7 +597,7 @@ export class GeminiTextAdapter<
497
597
  totalTokens: chunk.usageMetadata.totalTokenCount ?? 0,
498
598
  }
499
599
  : undefined,
500
- }
600
+ })
501
601
  }
502
602
  }
503
603
  }
@@ -723,7 +823,8 @@ export function createGeminiChat<TModel extends (typeof GEMINI_MODELS)[number]>(
723
823
  ): GeminiTextAdapter<
724
824
  TModel,
725
825
  ResolveProviderOptions<TModel>,
726
- ResolveInputModalities<TModel>
826
+ ResolveInputModalities<TModel>,
827
+ ResolveToolCapabilities<TModel>
727
828
  > {
728
829
  return new GeminiTextAdapter({ apiKey, ...config }, model)
729
830
  }
@@ -738,7 +839,8 @@ export function geminiText<TModel extends (typeof GEMINI_MODELS)[number]>(
738
839
  ): GeminiTextAdapter<
739
840
  TModel,
740
841
  ResolveProviderOptions<TModel>,
741
- ResolveInputModalities<TModel>
842
+ ResolveInputModalities<TModel>,
843
+ ResolveToolCapabilities<TModel>
742
844
  > {
743
845
  const apiKey = getGeminiApiKeyFromEnv()
744
846
  return createGeminiChat(model, apiKey, config)
package/src/index.ts CHANGED
@@ -67,6 +67,7 @@ export type { GeminiTTSVoice } from './model-meta'
67
67
 
68
68
  export type {
69
69
  GeminiChatModelProviderOptionsByName,
70
+ GeminiChatModelToolCapabilitiesByName,
70
71
  GeminiModelInputModalitiesByName,
71
72
  } from './model-meta'
72
73
  export type {