@tanstack/ai-gemini 0.10.7 → 0.10.9

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 (49) hide show
  1. package/dist/esm/adapters/audio.d.ts +1 -1
  2. package/dist/esm/adapters/audio.js +3 -2
  3. package/dist/esm/adapters/audio.js.map +1 -1
  4. package/dist/esm/adapters/image.d.ts +1 -1
  5. package/dist/esm/adapters/image.js +13 -8
  6. package/dist/esm/adapters/image.js.map +1 -1
  7. package/dist/esm/adapters/text.d.ts +1 -1
  8. package/dist/esm/adapters/text.js +45 -25
  9. package/dist/esm/adapters/text.js.map +1 -1
  10. package/dist/esm/adapters/tts.d.ts +1 -1
  11. package/dist/esm/adapters/tts.js +6 -5
  12. package/dist/esm/adapters/tts.js.map +1 -1
  13. package/dist/esm/image/image-provider-options.js +3 -2
  14. package/dist/esm/image/image-provider-options.js.map +1 -1
  15. package/dist/esm/model-meta.d.ts +23 -1
  16. package/dist/esm/model-meta.js +5 -1
  17. package/dist/esm/model-meta.js.map +1 -1
  18. package/dist/esm/tools/code-execution-tool.js +3 -2
  19. package/dist/esm/tools/code-execution-tool.js.map +1 -1
  20. package/dist/esm/tools/computer-use-tool.d.ts +2 -2
  21. package/dist/esm/tools/computer-use-tool.js +15 -6
  22. package/dist/esm/tools/computer-use-tool.js.map +1 -1
  23. package/dist/esm/tools/file-search-tool.js +3 -2
  24. package/dist/esm/tools/file-search-tool.js.map +1 -1
  25. package/dist/esm/tools/function-declaration-tool.js +1 -2
  26. package/dist/esm/tools/function-declaration-tool.js.map +1 -1
  27. package/dist/esm/tools/google-maps-tool.js +3 -2
  28. package/dist/esm/tools/google-maps-tool.js.map +1 -1
  29. package/dist/esm/tools/google-search-retriveal-tool.js +3 -2
  30. package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -1
  31. package/dist/esm/tools/google-search-tool.js +3 -2
  32. package/dist/esm/tools/google-search-tool.js.map +1 -1
  33. package/dist/esm/tools/url-context-tool.js +3 -2
  34. package/dist/esm/tools/url-context-tool.js.map +1 -1
  35. package/package.json +3 -3
  36. package/src/adapters/audio.ts +2 -2
  37. package/src/adapters/image.ts +13 -6
  38. package/src/adapters/text.ts +69 -37
  39. package/src/adapters/tts.ts +7 -6
  40. package/src/image/image-provider-options.ts +3 -2
  41. package/src/model-meta.ts +43 -0
  42. package/src/tools/code-execution-tool.ts +3 -3
  43. package/src/tools/computer-use-tool.ts +18 -7
  44. package/src/tools/file-search-tool.ts +3 -3
  45. package/src/tools/function-declaration-tool.ts +1 -2
  46. package/src/tools/google-maps-tool.ts +3 -3
  47. package/src/tools/google-search-retriveal-tool.ts +3 -3
  48. package/src/tools/google-search-tool.ts +3 -3
  49. package/src/tools/url-context-tool.ts +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"google-search-retriveal-tool.js","sources":["../../../src/tools/google-search-retriveal-tool.ts"],"sourcesContent":["import type { GoogleSearchRetrieval } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type GoogleSearchRetrievalToolConfig = GoogleSearchRetrieval\n\n/** @deprecated Renamed to `GoogleSearchRetrievalToolConfig`. Will be removed in a future release. */\nexport type GoogleSearchRetrievalTool = GoogleSearchRetrievalToolConfig\n\nexport type GeminiGoogleSearchRetrievalTool = ProviderTool<\n 'gemini',\n 'google_search_retrieval'\n>\n\nexport function convertGoogleSearchRetrievalToolToAdapterFormat(tool: Tool) {\n const metadata = tool.metadata as GoogleSearchRetrievalToolConfig\n return {\n googleSearchRetrieval: metadata,\n }\n}\n\nexport function googleSearchRetrievalTool(\n config?: GoogleSearchRetrievalToolConfig,\n): GeminiGoogleSearchRetrievalTool {\n // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.\n return {\n name: 'google_search_retrieval',\n description: '',\n metadata: config,\n } as unknown as GeminiGoogleSearchRetrievalTool\n}\n"],"names":[],"mappings":"AAaO,SAAS,gDAAgD,MAAY;AAC1E,QAAM,WAAW,KAAK;AACtB,SAAO;AAAA,IACL,uBAAuB;AAAA,EAAA;AAE3B;AAEO,SAAS,0BACd,QACiC;AAEjC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EAAA;AAEd;"}
1
+ {"version":3,"file":"google-search-retriveal-tool.js","sources":["../../../src/tools/google-search-retriveal-tool.ts"],"sourcesContent":["import { brandProviderTool } from '@tanstack/ai'\nimport type { GoogleSearchRetrieval } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type GoogleSearchRetrievalToolConfig = GoogleSearchRetrieval\n\n/** @deprecated Renamed to `GoogleSearchRetrievalToolConfig`. Will be removed in a future release. */\nexport type GoogleSearchRetrievalTool = GoogleSearchRetrievalToolConfig\n\nexport type GeminiGoogleSearchRetrievalTool = ProviderTool<\n 'gemini',\n 'google_search_retrieval'\n>\n\nexport function convertGoogleSearchRetrievalToolToAdapterFormat(tool: Tool) {\n const metadata = tool.metadata as GoogleSearchRetrievalToolConfig\n return {\n googleSearchRetrieval: metadata,\n }\n}\n\nexport function googleSearchRetrievalTool(\n config?: GoogleSearchRetrievalToolConfig,\n): GeminiGoogleSearchRetrievalTool {\n return brandProviderTool<GeminiGoogleSearchRetrievalTool>({\n name: 'google_search_retrieval',\n description: '',\n metadata: config,\n })\n}\n"],"names":[],"mappings":";AAcO,SAAS,gDAAgD,MAAY;AAC1E,QAAM,WAAW,KAAK;AACtB,SAAO;AAAA,IACL,uBAAuB;AAAA,EAAA;AAE3B;AAEO,SAAS,0BACd,QACiC;AACjC,SAAO,kBAAmD;AAAA,IACxD,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EAAA,CACX;AACH;"}
@@ -1,3 +1,4 @@
1
+ import { brandProviderTool } from "@tanstack/ai";
1
2
  function convertGoogleSearchToolToAdapterFormat(tool) {
2
3
  const metadata = tool.metadata;
3
4
  return {
@@ -5,11 +6,11 @@ function convertGoogleSearchToolToAdapterFormat(tool) {
5
6
  };
6
7
  }
7
8
  function googleSearchTool(config) {
8
- return {
9
+ return brandProviderTool({
9
10
  name: "google_search",
10
11
  description: "",
11
12
  metadata: config
12
- };
13
+ });
13
14
  }
14
15
  export {
15
16
  convertGoogleSearchToolToAdapterFormat,
@@ -1 +1 @@
1
- {"version":3,"file":"google-search-tool.js","sources":["../../../src/tools/google-search-tool.ts"],"sourcesContent":["import type { GoogleSearch } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type GoogleSearchToolConfig = GoogleSearch\n\n/** @deprecated Renamed to `GoogleSearchToolConfig`. Will be removed in a future release. */\nexport type GoogleSearchTool = GoogleSearchToolConfig\n\nexport type GeminiGoogleSearchTool = ProviderTool<'gemini', 'google_search'>\n\nexport function convertGoogleSearchToolToAdapterFormat(tool: Tool) {\n const metadata = tool.metadata as GoogleSearchToolConfig\n return {\n googleSearch: metadata,\n }\n}\n\nexport function googleSearchTool(\n config?: GoogleSearchToolConfig,\n): GeminiGoogleSearchTool {\n // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.\n return {\n name: 'google_search',\n description: '',\n metadata: config,\n } as unknown as GeminiGoogleSearchTool\n}\n"],"names":[],"mappings":"AAUO,SAAS,uCAAuC,MAAY;AACjE,QAAM,WAAW,KAAK;AACtB,SAAO;AAAA,IACL,cAAc;AAAA,EAAA;AAElB;AAEO,SAAS,iBACd,QACwB;AAExB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EAAA;AAEd;"}
1
+ {"version":3,"file":"google-search-tool.js","sources":["../../../src/tools/google-search-tool.ts"],"sourcesContent":["import { brandProviderTool } from '@tanstack/ai'\nimport type { GoogleSearch } from '@google/genai'\nimport type { ProviderTool, Tool } from '@tanstack/ai'\n\nexport type GoogleSearchToolConfig = GoogleSearch\n\n/** @deprecated Renamed to `GoogleSearchToolConfig`. Will be removed in a future release. */\nexport type GoogleSearchTool = GoogleSearchToolConfig\n\nexport type GeminiGoogleSearchTool = ProviderTool<'gemini', 'google_search'>\n\nexport function convertGoogleSearchToolToAdapterFormat(tool: Tool) {\n const metadata = tool.metadata as GoogleSearchToolConfig\n return {\n googleSearch: metadata,\n }\n}\n\nexport function googleSearchTool(\n config?: GoogleSearchToolConfig,\n): GeminiGoogleSearchTool {\n return brandProviderTool<GeminiGoogleSearchTool>({\n name: 'google_search',\n description: '',\n metadata: config,\n })\n}\n"],"names":[],"mappings":";AAWO,SAAS,uCAAuC,MAAY;AACjE,QAAM,WAAW,KAAK;AACtB,SAAO;AAAA,IACL,cAAc;AAAA,EAAA;AAElB;AAEO,SAAS,iBACd,QACwB;AACxB,SAAO,kBAA0C;AAAA,IAC/C,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EAAA,CACX;AACH;"}
@@ -1,14 +1,15 @@
1
+ import { brandProviderTool } from "@tanstack/ai";
1
2
  function convertUrlContextToolToAdapterFormat(_tool) {
2
3
  return {
3
4
  urlContext: {}
4
5
  };
5
6
  }
6
7
  function urlContextTool() {
7
- return {
8
+ return brandProviderTool({
8
9
  name: "url_context",
9
10
  description: "",
10
11
  metadata: {}
11
- };
12
+ });
12
13
  }
13
14
  export {
14
15
  convertUrlContextToolToAdapterFormat,
@@ -1 +1 @@
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;"}
1
+ {"version":3,"file":"url-context-tool.js","sources":["../../../src/tools/url-context-tool.ts"],"sourcesContent":["import { brandProviderTool } from '@tanstack/ai'\nimport 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 return brandProviderTool<GeminiUrlContextTool>({\n name: 'url_context',\n description: '',\n metadata: {},\n })\n}\n"],"names":[],"mappings":";AAUO,SAAS,qCAAqC,OAAa;AAChE,SAAO;AAAA,IACL,YAAY,CAAA;AAAA,EAAC;AAEjB;AAEO,SAAS,iBAAuC;AACrD,SAAO,kBAAwC;AAAA,IAC7C,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU,CAAA;AAAA,EAAC,CACZ;AACH;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-gemini",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
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.20.0"
41
+ "@tanstack/ai": "^0.21.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@vitest/coverage-v8": "4.0.14",
45
45
  "vite": "^7.3.3",
46
46
  "zod": "^4.2.0",
47
- "@tanstack/ai": "0.20.0"
47
+ "@tanstack/ai": "0.21.0"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "vite build",
@@ -66,7 +66,7 @@ export class GeminiAudioAdapter<
66
66
  > extends BaseAudioAdapter<TModel, GeminiAudioProviderOptions> {
67
67
  readonly name = 'gemini' as const
68
68
 
69
- private client: GoogleGenAI
69
+ private readonly client: GoogleGenAI
70
70
 
71
71
  constructor(config: GeminiAudioConfig, model: TModel) {
72
72
  super(model, config)
@@ -120,7 +120,7 @@ export class GeminiAudioAdapter<
120
120
  model,
121
121
  audio: {
122
122
  b64Json: audioPart.inlineData.data,
123
- contentType,
123
+ ...(contentType !== undefined && { contentType }),
124
124
  },
125
125
  }
126
126
  } catch (error) {
@@ -61,7 +61,7 @@ export class GeminiImageAdapter<
61
61
  GeminiImageModelProviderOptionsByName,
62
62
  GeminiImageModelSizeByName
63
63
  > {
64
- readonly kind = 'image' as const
64
+ override readonly kind = 'image' as const
65
65
  readonly name = 'gemini' as const
66
66
 
67
67
  // Type-only property - never assigned at runtime
@@ -71,7 +71,7 @@ export class GeminiImageAdapter<
71
71
  modelSizeByName: GeminiImageModelSizeByName
72
72
  }
73
73
 
74
- private client: GoogleGenAI
74
+ private readonly client: GoogleGenAI
75
75
 
76
76
  constructor(config: GeminiImageConfig, model: TModel) {
77
77
  super(model, config)
@@ -214,7 +214,6 @@ export class GeminiImageAdapter<
214
214
  id: generateId(this.name),
215
215
  model,
216
216
  images,
217
- usage: undefined,
218
217
  }
219
218
  }
220
219
 
@@ -223,10 +222,14 @@ export class GeminiImageAdapter<
223
222
  ): GenerateImagesConfig {
224
223
  const { size, numberOfImages, modelOptions } = options
225
224
 
225
+ // Build with conditional spreads — under exactOptionalPropertyTypes the
226
+ // vendor `GenerateImagesConfig` fields are `field?: T` (no `| undefined`),
227
+ // so we can only assign the property when we actually have a value.
228
+ const sizeAspectRatio = size ? sizeToAspectRatio(size) : undefined
226
229
  return {
227
230
  numberOfImages: numberOfImages ?? 1,
228
231
  // Map size to aspect ratio if provided (modelOptions.aspectRatio will override)
229
- aspectRatio: size ? sizeToAspectRatio(size) : undefined,
232
+ ...(sizeAspectRatio !== undefined && { aspectRatio: sizeAspectRatio }),
230
233
  ...modelOptions,
231
234
  }
232
235
  }
@@ -242,7 +245,12 @@ export class GeminiImageAdapter<
242
245
  for (const item of entries) {
243
246
  const b64Json = item.image?.imageBytes
244
247
  if (b64Json) {
245
- images.push({ b64Json, revisedPrompt: item.enhancedPrompt })
248
+ images.push({
249
+ b64Json,
250
+ ...(item.enhancedPrompt !== undefined && {
251
+ revisedPrompt: item.enhancedPrompt,
252
+ }),
253
+ })
246
254
  continue
247
255
  }
248
256
  // Imagen can drop individual entries with a raiFilteredReason when
@@ -277,7 +285,6 @@ export class GeminiImageAdapter<
277
285
  id: generateId(this.name),
278
286
  model,
279
287
  images,
280
- usage: undefined,
281
288
  }
282
289
  }
283
290
  }
@@ -106,10 +106,10 @@ export class GeminiTextAdapter<
106
106
  TToolCapabilities,
107
107
  GeminiToolCallMetadata
108
108
  > {
109
- readonly kind = 'text' as const
109
+ override readonly kind = 'text' as const
110
110
  readonly name = 'gemini' as const
111
111
 
112
- private client: GoogleGenAI
112
+ private readonly client: GoogleGenAI
113
113
 
114
114
  constructor(config: GeminiTextConfig, model: TModel) {
115
115
  super({}, model)
@@ -315,10 +315,12 @@ export class GeminiTextAdapter<
315
315
 
316
316
  accumulatedThinking += part.text
317
317
 
318
- // Spec REASONING content event
318
+ // Spec REASONING content event — reasoningMessageId is set in the
319
+ // hasEmittedStepStarted block above (entered on the same `part.thought` path)
320
+ if (!reasoningMessageId) continue
319
321
  yield {
320
322
  type: EventType.REASONING_MESSAGE_CONTENT,
321
- messageId: reasoningMessageId!,
323
+ messageId: reasoningMessageId,
322
324
  delta: part.text,
323
325
  model,
324
326
  timestamp: Date.now(),
@@ -399,7 +401,11 @@ export class GeminiTextAdapter<
399
401
  : JSON.stringify(functionArgs),
400
402
  index: nextToolIndex++,
401
403
  started: false,
402
- thoughtSignature: partThoughtSignature,
404
+ // Only set thoughtSignature when present — under EOPT, the
405
+ // optional field cannot accept an explicit `undefined`.
406
+ ...(partThoughtSignature !== undefined && {
407
+ thoughtSignature: partThoughtSignature,
408
+ }),
403
409
  }
404
410
  toolCallMap.set(toolCallId, toolCallData)
405
411
  } else {
@@ -617,13 +623,16 @@ export class GeminiTextAdapter<
617
623
  model,
618
624
  timestamp: Date.now(),
619
625
  finishReason: toolCallMap.size > 0 ? 'tool_calls' : 'stop',
620
- usage: chunk.usageMetadata
621
- ? {
622
- promptTokens: chunk.usageMetadata.promptTokenCount ?? 0,
623
- completionTokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
624
- totalTokens: chunk.usageMetadata.totalTokenCount ?? 0,
625
- }
626
- : undefined,
626
+ // RunFinishedEvent.usage is `usage?: {...}` (no `| undefined`) under
627
+ // exactOptionalPropertyTypes; only include it when usageMetadata is
628
+ // present rather than assigning an explicit `undefined`.
629
+ ...(chunk.usageMetadata && {
630
+ usage: {
631
+ promptTokens: chunk.usageMetadata.promptTokenCount ?? 0,
632
+ completionTokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
633
+ totalTokens: chunk.usageMetadata.totalTokenCount ?? 0,
634
+ },
635
+ }),
627
636
  }
628
637
  }
629
638
  }
@@ -707,10 +716,7 @@ export class GeminiTextAdapter<
707
716
  >)
708
717
  : {}
709
718
  } catch {
710
- parsedArgs = toolCall.function.arguments as unknown as Record<
711
- string,
712
- unknown
713
- >
719
+ parsedArgs = {}
714
720
  }
715
721
 
716
722
  const thoughtSignature = (
@@ -744,7 +750,7 @@ export class GeminiTextAdapter<
744
750
  response: {
745
751
  content: msg.content || '',
746
752
  },
747
- } as any,
753
+ },
748
754
  })
749
755
  }
750
756
 
@@ -819,31 +825,57 @@ export class GeminiTextAdapter<
819
825
  private mapCommonOptionsToGemini(
820
826
  options: TextOptions<GeminiTextProviderOptions>,
821
827
  ) {
822
- const modelOpts = options.modelOptions
823
- const thinkingConfig = modelOpts?.thinkingConfig
828
+ // Separate `thinkingConfig` from the other model options so the loose
829
+ // local `thinkingLevel?: keyof typeof ThinkingLevel` type doesn't leak
830
+ // into the SDK config object via the `...modelOpts` spread — we re-add a
831
+ // properly-typed `ThinkingConfig` below.
832
+ const { thinkingConfig, ...modelOpts } = options.modelOptions ?? {}
833
+ // Build the thinkingConfig payload only when the caller actually supplied
834
+ // one. Our local `thinkingLevel` is typed as `keyof typeof ThinkingLevel`
835
+ // (string union) so users can pass plain strings; the SDK target is the
836
+ // `ThinkingLevel` enum, and every field is `field?: T` under EOPT — so we
837
+ // re-emit fields via conditional spreads.
838
+ const mappedThinkingConfig = thinkingConfig
839
+ ? {
840
+ ...(thinkingConfig.includeThoughts !== undefined && {
841
+ includeThoughts: thinkingConfig.includeThoughts,
842
+ }),
843
+ ...(thinkingConfig.thinkingBudget !== undefined && {
844
+ thinkingBudget: thinkingConfig.thinkingBudget,
845
+ }),
846
+ ...(thinkingConfig.thinkingLevel
847
+ ? {
848
+ thinkingLevel: thinkingConfig.thinkingLevel as ThinkingLevel,
849
+ }
850
+ : {}),
851
+ }
852
+ : undefined
853
+
854
+ const normalizedPrompts = normalizeSystemPrompts(options.systemPrompts)
855
+ const systemInstruction =
856
+ normalizedPrompts.length > 0
857
+ ? normalizedPrompts.map((p) => p.content).join('\n')
858
+ : undefined
859
+
860
+ // Vendor `GenerateContentConfig` fields are `field?: T` (no `| undefined`)
861
+ // under EOPT, so spread each common option only when present rather than
862
+ // emitting `field: undefined`s into the wire payload.
824
863
  const requestOptions: GenerateContentParameters = {
825
864
  model: options.model,
826
865
  contents: this.formatMessages(options.messages),
827
866
  config: {
828
867
  ...modelOpts,
829
- temperature: options.temperature,
830
- topP: options.topP,
831
- maxOutputTokens: options.maxTokens,
832
- thinkingConfig: thinkingConfig
833
- ? {
834
- ...thinkingConfig,
835
- thinkingLevel: thinkingConfig.thinkingLevel
836
- ? // Enum is provided by the SDK, we use it for the type but cast it to string constants, here we just cast them back
837
- (thinkingConfig.thinkingLevel as ThinkingLevel)
838
- : undefined,
839
- }
840
- : undefined,
841
- systemInstruction: (() => {
842
- const prompts = normalizeSystemPrompts(options.systemPrompts)
843
- return prompts.length > 0
844
- ? prompts.map((p) => p.content).join('\n')
845
- : undefined
846
- })(),
868
+ ...(options.temperature !== undefined && {
869
+ temperature: options.temperature,
870
+ }),
871
+ ...(options.topP !== undefined && { topP: options.topP }),
872
+ ...(options.maxTokens !== undefined && {
873
+ maxOutputTokens: options.maxTokens,
874
+ }),
875
+ ...(mappedThinkingConfig !== undefined && {
876
+ thinkingConfig: mappedThinkingConfig,
877
+ }),
878
+ ...(systemInstruction !== undefined && { systemInstruction }),
847
879
  tools: convertToolsToProviderFormat(options.tools),
848
880
  },
849
881
  }
@@ -121,7 +121,7 @@ export class GeminiTTSAdapter<
121
121
  > extends BaseTTSAdapter<TModel, GeminiTTSProviderOptions> {
122
122
  readonly name = 'gemini' as const
123
123
 
124
- private client: GoogleGenAI
124
+ private readonly client: GoogleGenAI
125
125
 
126
126
  constructor(config: GeminiTTSConfig, model: TModel) {
127
127
  super(model, config)
@@ -251,8 +251,9 @@ export class GeminiTTSAdapter<
251
251
  }
252
252
 
253
253
  // Strip any mime parameters (e.g. `audio/ogg;codec=opus`) before pulling
254
- // the subtype out as the file format.
255
- const format = mimeType.split(';')[0]!.split('/')[1] || 'wav'
254
+ // the subtype out as the file format. `String.split` always returns at
255
+ // least one element, so [0] is defined.
256
+ const format = (mimeType.split(';')[0] ?? '').split('/')[1] || 'wav'
256
257
 
257
258
  return {
258
259
  id: generateId(this.name),
@@ -275,7 +276,7 @@ function parsePcmMimeType(
275
276
  mimeType: string,
276
277
  ): { sampleRate: number; channels: number; bitsPerSample: number } | undefined {
277
278
  const normalized = mimeType.toLowerCase()
278
- const subtype = normalized.split(';')[0]!.split('/')[1] ?? ''
279
+ const subtype = (normalized.split(';')[0] ?? '').split('/')[1] ?? ''
279
280
  // Exclude containerized wav (e.g. `audio/wav;codec=pcm`) — those already
280
281
  // carry a RIFF header and must not be re-wrapped.
281
282
  if (subtype.includes('wav')) return undefined
@@ -351,8 +352,8 @@ function wrapPcmBase64AsWav(
351
352
  }
352
353
  let binary = ''
353
354
  const bytes = new Uint8Array(buffer)
354
- for (let i = 0; i < bytes.byteLength; i += 1) {
355
- binary += String.fromCharCode(bytes[i]!)
355
+ for (const byte of bytes) {
356
+ binary += String.fromCharCode(byte)
356
357
  }
357
358
  return btoa(binary)
358
359
  }
@@ -304,6 +304,7 @@ export function parseNativeImageSize(
304
304
  size: string,
305
305
  ): { aspectRatio: string; resolution: string } | undefined {
306
306
  const match = size.match(/^(\d+:\d+)_(.+)$/)
307
- if (!match) return undefined
308
- return { aspectRatio: match[1]!, resolution: match[2]! }
307
+ const [, aspectRatio, resolution] = match ?? []
308
+ if (aspectRatio === undefined || resolution === undefined) return undefined
309
+ return { aspectRatio, resolution }
309
310
  }
package/src/model-meta.ts CHANGED
@@ -965,6 +965,40 @@ const VEO_2 = {
965
965
  [VEO_2.name]: VEO_2,
966
966
  } as const */
967
967
 
968
+ const GEMINI_3_5_FLASH = {
969
+ name: 'gemini-3.5-flash',
970
+ max_input_tokens: 1_048_576,
971
+ max_output_tokens: 65_536,
972
+ supports: {
973
+ input: ['text', 'image', 'video', 'document', 'audio'],
974
+ output: ['text'],
975
+ capabilities: [
976
+ 'batch_api',
977
+ 'caching',
978
+ 'function_calling',
979
+ 'structured_output',
980
+ 'thinking',
981
+ ],
982
+ tools: ['code_execution', 'file_search', 'google_search', 'url_context'],
983
+ },
984
+ pricing: {
985
+ input: {
986
+ normal: 1.5,
987
+ cached: 0.15,
988
+ },
989
+ output: {
990
+ normal: 9,
991
+ },
992
+ },
993
+ } as const satisfies ModelMeta<
994
+ GeminiToolConfigOptions &
995
+ GeminiSafetyOptions &
996
+ GeminiCommonConfigOptions &
997
+ GeminiCachedContentOptions &
998
+ GeminiStructuredOutputOptions &
999
+ GeminiThinkingOptions
1000
+ >
1001
+
968
1002
  export const GEMINI_MODELS = [
969
1003
  GEMINI_3_1_PRO.name,
970
1004
  GEMINI_3_PRO.name,
@@ -977,6 +1011,8 @@ export const GEMINI_MODELS = [
977
1011
  GEMINI_2_5_FLASH_LITE_PREVIEW.name,
978
1012
  GEMINI_2_FLASH.name,
979
1013
  GEMINI_2_FLASH_LITE.name,
1014
+
1015
+ GEMINI_3_5_FLASH.name,
980
1016
  ] as const
981
1017
 
982
1018
  export type GeminiModels = (typeof GEMINI_MODELS)[number]
@@ -1135,6 +1171,12 @@ export type GeminiChatModelProviderOptionsByName = {
1135
1171
  GeminiCommonConfigOptions &
1136
1172
  GeminiCachedContentOptions &
1137
1173
  GeminiStructuredOutputOptions
1174
+ [GEMINI_3_5_FLASH.name]: GeminiToolConfigOptions &
1175
+ GeminiSafetyOptions &
1176
+ GeminiCommonConfigOptions &
1177
+ GeminiCachedContentOptions &
1178
+ GeminiStructuredOutputOptions &
1179
+ GeminiThinkingOptions
1138
1180
  }
1139
1181
 
1140
1182
  /**
@@ -1183,4 +1225,5 @@ export type GeminiModelInputModalitiesByName = {
1183
1225
  [GEMINI_2_5_FLASH_PREVIEW.name]: typeof GEMINI_2_5_FLASH_PREVIEW.supports.input
1184
1226
  [GEMINI_2_FLASH.name]: typeof GEMINI_2_FLASH.supports.input
1185
1227
  [GEMINI_2_FLASH_LITE.name]: typeof GEMINI_2_FLASH_LITE.supports.input
1228
+ [GEMINI_3_5_FLASH.name]: typeof GEMINI_3_5_FLASH.supports.input
1186
1229
  }
@@ -1,3 +1,4 @@
1
+ import { brandProviderTool } from '@tanstack/ai'
1
2
  import type { ProviderTool, Tool } from '@tanstack/ai'
2
3
 
3
4
  export interface CodeExecutionToolConfig {}
@@ -14,10 +15,9 @@ export function convertCodeExecutionToolToAdapterFormat(_tool: Tool) {
14
15
  }
15
16
 
16
17
  export function codeExecutionTool(): GeminiCodeExecutionTool {
17
- // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.
18
- return {
18
+ return brandProviderTool<GeminiCodeExecutionTool>({
19
19
  name: 'code_execution',
20
20
  description: '',
21
21
  metadata: {},
22
- } as unknown as GeminiCodeExecutionTool
22
+ })
23
23
  }
@@ -1,3 +1,4 @@
1
+ import { brandProviderTool } from '@tanstack/ai'
1
2
  import type { ComputerUse } from '@google/genai'
2
3
  import type { ProviderTool, Tool } from '@tanstack/ai'
3
4
 
@@ -10,10 +11,17 @@ export type GeminiComputerUseTool = ProviderTool<'gemini', 'computer_use'>
10
11
 
11
12
  export function convertComputerUseToolToAdapterFormat(tool: Tool) {
12
13
  const metadata = tool.metadata as ComputerUseToolConfig
14
+ // Vendor `ComputerUse` fields are `field?: T` (no `| undefined`) under EOPT,
15
+ // so spread each field conditionally rather than emitting explicit
16
+ // `undefined`s on the wire payload.
13
17
  return {
14
18
  computerUse: {
15
- environment: metadata.environment,
16
- excludedPredefinedFunctions: metadata.excludedPredefinedFunctions,
19
+ ...(metadata.environment !== undefined && {
20
+ environment: metadata.environment,
21
+ }),
22
+ ...(metadata.excludedPredefinedFunctions !== undefined && {
23
+ excludedPredefinedFunctions: metadata.excludedPredefinedFunctions,
24
+ }),
17
25
  },
18
26
  }
19
27
  }
@@ -21,13 +29,16 @@ export function convertComputerUseToolToAdapterFormat(tool: Tool) {
21
29
  export function computerUseTool(
22
30
  config: ComputerUseToolConfig,
23
31
  ): GeminiComputerUseTool {
24
- // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.
25
- return {
32
+ return brandProviderTool<GeminiComputerUseTool>({
26
33
  name: 'computer_use',
27
34
  description: '',
28
35
  metadata: {
29
- environment: config.environment,
30
- excludedPredefinedFunctions: config.excludedPredefinedFunctions,
36
+ ...(config.environment !== undefined && {
37
+ environment: config.environment,
38
+ }),
39
+ ...(config.excludedPredefinedFunctions !== undefined && {
40
+ excludedPredefinedFunctions: config.excludedPredefinedFunctions,
41
+ }),
31
42
  },
32
- } as unknown as GeminiComputerUseTool
43
+ })
33
44
  }
@@ -1,3 +1,4 @@
1
+ import { brandProviderTool } from '@tanstack/ai'
1
2
  import type { FileSearch } from '@google/genai'
2
3
  import type { ProviderTool, Tool } from '@tanstack/ai'
3
4
 
@@ -18,10 +19,9 @@ export function convertFileSearchToolToAdapterFormat(tool: Tool) {
18
19
  export function fileSearchTool(
19
20
  config: FileSearchToolConfig,
20
21
  ): GeminiFileSearchTool {
21
- // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.
22
- return {
22
+ return brandProviderTool<GeminiFileSearchTool>({
23
23
  name: 'file_search',
24
24
  description: '',
25
25
  metadata: config,
26
- } as unknown as GeminiFileSearchTool
26
+ })
27
27
  }
@@ -4,8 +4,7 @@ export type FunctionDeclarationTool = FunctionDeclaration
4
4
 
5
5
  const validateFunctionDeclarationTool = (tool: FunctionDeclarationTool) => {
6
6
  const nameRegex = /^[a-zA-Z0-9_:.-]{1,64}$/
7
- const valid = nameRegex.test(tool.name!)
8
- if (!valid) {
7
+ if (tool.name === undefined || !nameRegex.test(tool.name)) {
9
8
  throw new Error(
10
9
  `Invalid function name: ${tool.name}. Must be 1-64 characters long and contain only a-z, A-Z, 0-9, underscores, colons, dots, and dashes.`,
11
10
  )
@@ -1,3 +1,4 @@
1
+ import { brandProviderTool } from '@tanstack/ai'
1
2
  import type { GoogleMaps } from '@google/genai'
2
3
  import type { ProviderTool, Tool } from '@tanstack/ai'
3
4
 
@@ -18,10 +19,9 @@ export function convertGoogleMapsToolToAdapterFormat(tool: Tool) {
18
19
  export function googleMapsTool(
19
20
  config?: GoogleMapsToolConfig,
20
21
  ): GeminiGoogleMapsTool {
21
- // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.
22
- return {
22
+ return brandProviderTool<GeminiGoogleMapsTool>({
23
23
  name: 'google_maps',
24
24
  description: '',
25
25
  metadata: config,
26
- } as unknown as GeminiGoogleMapsTool
26
+ })
27
27
  }
@@ -1,3 +1,4 @@
1
+ import { brandProviderTool } from '@tanstack/ai'
1
2
  import type { GoogleSearchRetrieval } from '@google/genai'
2
3
  import type { ProviderTool, Tool } from '@tanstack/ai'
3
4
 
@@ -21,10 +22,9 @@ export function convertGoogleSearchRetrievalToolToAdapterFormat(tool: Tool) {
21
22
  export function googleSearchRetrievalTool(
22
23
  config?: GoogleSearchRetrievalToolConfig,
23
24
  ): GeminiGoogleSearchRetrievalTool {
24
- // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.
25
- return {
25
+ return brandProviderTool<GeminiGoogleSearchRetrievalTool>({
26
26
  name: 'google_search_retrieval',
27
27
  description: '',
28
28
  metadata: config,
29
- } as unknown as GeminiGoogleSearchRetrievalTool
29
+ })
30
30
  }
@@ -1,3 +1,4 @@
1
+ import { brandProviderTool } from '@tanstack/ai'
1
2
  import type { GoogleSearch } from '@google/genai'
2
3
  import type { ProviderTool, Tool } from '@tanstack/ai'
3
4
 
@@ -18,10 +19,9 @@ export function convertGoogleSearchToolToAdapterFormat(tool: Tool) {
18
19
  export function googleSearchTool(
19
20
  config?: GoogleSearchToolConfig,
20
21
  ): GeminiGoogleSearchTool {
21
- // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.
22
- return {
22
+ return brandProviderTool<GeminiGoogleSearchTool>({
23
23
  name: 'google_search',
24
24
  description: '',
25
25
  metadata: config,
26
- } as unknown as GeminiGoogleSearchTool
26
+ })
27
27
  }
@@ -1,3 +1,4 @@
1
+ import { brandProviderTool } from '@tanstack/ai'
1
2
  import type { ProviderTool, Tool } from '@tanstack/ai'
2
3
 
3
4
  export interface UrlContextToolConfig {}
@@ -14,10 +15,9 @@ export function convertUrlContextToolToAdapterFormat(_tool: Tool) {
14
15
  }
15
16
 
16
17
  export function urlContextTool(): GeminiUrlContextTool {
17
- // Phantom-brand cast: '~provider'/'~toolKind' are type-only and never assigned at runtime.
18
- return {
18
+ return brandProviderTool<GeminiUrlContextTool>({
19
19
  name: 'url_context',
20
20
  description: '',
21
21
  metadata: {},
22
- } as unknown as GeminiUrlContextTool
22
+ })
23
23
  }