@tanstack/ai-gemini 0.23.0 → 0.24.1
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 +10 -1
- package/dist/esm/adapters/image.d.ts +14 -7
- package/dist/esm/adapters/image.js +27 -56
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/video.js +2 -1
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/experimental/text-interactions/adapter.d.ts +8 -5
- package/dist/esm/experimental/text-interactions/adapter.js +20 -18
- package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
- package/dist/esm/image/image-provider-options.d.ts +167 -20
- package/dist/esm/image/image-provider-options.js +52 -5
- package/dist/esm/image/image-provider-options.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/model-meta.d.ts +15 -2
- package/dist/esm/model-meta.js +82 -1
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/realtime/adapter.js +1 -5
- package/dist/esm/realtime/adapter.js.map +1 -1
- package/dist/esm/realtime/client.js.map +1 -1
- package/dist/esm/realtime/token.js +3 -2
- package/dist/esm/realtime/token.js.map +1 -1
- package/dist/esm/realtime/utils.js +4 -1
- package/dist/esm/realtime/utils.js.map +1 -1
- package/dist/esm/tools/code-execution-tool.js +3 -3
- package/dist/esm/tools/code-execution-tool.js.map +1 -1
- package/dist/esm/tools/computer-use-tool.js +4 -4
- package/dist/esm/tools/computer-use-tool.js.map +1 -1
- package/dist/esm/tools/file-search-tool.js +4 -4
- package/dist/esm/tools/file-search-tool.js.map +1 -1
- package/dist/esm/tools/gemini-provider-tool.d.ts +16 -0
- package/dist/esm/tools/gemini-provider-tool.js +42 -0
- package/dist/esm/tools/gemini-provider-tool.js.map +1 -0
- package/dist/esm/tools/google-maps-tool.js +4 -4
- package/dist/esm/tools/google-maps-tool.js.map +1 -1
- package/dist/esm/tools/google-search-retriveal-tool.js +4 -4
- package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -1
- package/dist/esm/tools/google-search-tool.js +4 -4
- package/dist/esm/tools/google-search-tool.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/tools/url-context-tool.js +3 -3
- package/dist/esm/tools/url-context-tool.js.map +1 -1
- package/dist/esm/usage.js +1 -3
- package/dist/esm/usage.js.map +1 -1
- package/package.json +5 -5
- package/src/adapters/image.ts +123 -36
- package/src/experimental/text-interactions/adapter.ts +31 -18
- package/src/image/image-provider-options.ts +226 -25
- package/src/index.ts +28 -0
- package/src/model-meta.ts +118 -1
- package/src/tools/code-execution-tool.ts +9 -6
- package/src/tools/computer-use-tool.ts +19 -13
- package/src/tools/file-search-tool.ts +13 -7
- package/src/tools/gemini-provider-tool.ts +60 -0
- package/src/tools/google-maps-tool.ts +13 -7
- package/src/tools/google-search-retriveal-tool.ts +15 -7
- package/src/tools/google-search-tool.ts +13 -7
- package/src/tools/tool-converter.ts +5 -4
- package/src/tools/url-context-tool.ts +9 -6
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getGeminiProviderToolKind } from "./gemini-provider-tool.js";
|
|
1
2
|
import { convertCodeExecutionToolToAdapterFormat } from "./code-execution-tool.js";
|
|
2
3
|
import { convertComputerUseToolToAdapterFormat } from "./computer-use-tool.js";
|
|
3
4
|
import { convertFileSearchToolToAdapterFormat } from "./file-search-tool.js";
|
|
@@ -5,6 +6,7 @@ import { convertGoogleMapsToolToAdapterFormat } from "./google-maps-tool.js";
|
|
|
5
6
|
import { convertGoogleSearchRetrievalToolToAdapterFormat } from "./google-search-retriveal-tool.js";
|
|
6
7
|
import { convertGoogleSearchToolToAdapterFormat } from "./google-search-tool.js";
|
|
7
8
|
import { convertUrlContextToolToAdapterFormat } from "./url-context-tool.js";
|
|
9
|
+
import { assertUniqueToolNames } from "@tanstack/ai/adapter-internals";
|
|
8
10
|
//#region src/tools/tool-converter.ts
|
|
9
11
|
/**
|
|
10
12
|
* Converts standard Tool format to Gemini-specific tool format
|
|
@@ -27,9 +29,10 @@ import { convertUrlContextToolToAdapterFormat } from "./url-context-tool.js";
|
|
|
27
29
|
*/
|
|
28
30
|
function convertToolsToProviderFormat(tools) {
|
|
29
31
|
if (!tools || tools.length === 0) return [];
|
|
32
|
+
assertUniqueToolNames(tools);
|
|
30
33
|
const result = [];
|
|
31
34
|
const functionDeclarations = [];
|
|
32
|
-
for (const tool of tools) switch (tool
|
|
35
|
+
for (const tool of tools) switch (getGeminiProviderToolKind(tool)) {
|
|
33
36
|
case "code_execution":
|
|
34
37
|
result.push(convertCodeExecutionToolToAdapterFormat(tool));
|
|
35
38
|
break;
|
|
@@ -51,7 +54,7 @@ function convertToolsToProviderFormat(tools) {
|
|
|
51
54
|
case "url_context":
|
|
52
55
|
result.push(convertUrlContextToolToAdapterFormat(tool));
|
|
53
56
|
break;
|
|
54
|
-
|
|
57
|
+
case void 0:
|
|
55
58
|
if (!tool.description) throw new Error(`Tool ${tool.name} requires a description for Gemini adapter`);
|
|
56
59
|
functionDeclarations.push({
|
|
57
60
|
name: tool.name,
|
|
@@ -62,7 +65,6 @@ function convertToolsToProviderFormat(tools) {
|
|
|
62
65
|
required: []
|
|
63
66
|
}
|
|
64
67
|
});
|
|
65
|
-
break;
|
|
66
68
|
}
|
|
67
69
|
if (functionDeclarations.length > 0) result.push({ functionDeclarations });
|
|
68
70
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-converter.js","names":[],"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
|
|
1
|
+
{"version":3,"file":"tool-converter.js","names":[],"sources":["../../../src/tools/tool-converter.ts"],"sourcesContent":["import { assertUniqueToolNames } from '@tanstack/ai/adapter-internals'\nimport { convertCodeExecutionToolToAdapterFormat } from './code-execution-tool'\nimport { convertComputerUseToolToAdapterFormat } from './computer-use-tool'\nimport { convertFileSearchToolToAdapterFormat } from './file-search-tool'\nimport { getGeminiProviderToolKind } from './gemini-provider-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 assertUniqueToolNames(tools)\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 switch (getGeminiProviderToolKind(tool)) {\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 case undefined:\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,6BACd,OACkB;CAClB,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B,OAAO,CAAC;CAEV,sBAAsB,KAAK;CAC3B,MAAM,SAA2B,CAAC;CAClC,MAAM,uBAID,CAAC;CAGN,KAAK,MAAM,QAAQ,OACjB,QAAQ,0BAA0B,IAAI,GAAtC;EACE,KAAK;GACH,OAAO,KAAK,wCAAwC,IAAI,CAAC;GACzD;EACF,KAAK;GACH,OAAO,KAAK,sCAAsC,IAAI,CAAC;GACvD;EACF,KAAK;GACH,OAAO,KAAK,qCAAqC,IAAI,CAAC;GACtD;EACF,KAAK;GACH,OAAO,KAAK,qCAAqC,IAAI,CAAC;GACtD;EACF,KAAK;GACH,OAAO,KAAK,gDAAgD,IAAI,CAAC;GACjE;EACF,KAAK;GACH,OAAO,KAAK,uCAAuC,IAAI,CAAC;GACxD;EACF,KAAK;GACH,OAAO,KAAK,qCAAqC,IAAI,CAAC;GACtD;EACF,KAAK,KAAA;GAGH,IAAI,CAAC,KAAK,aACR,MAAM,IAAI,MACR,QAAQ,KAAK,KAAK,2CACpB;GAIF,qBAAqB,KAAK;IACxB,MAAM,KAAK;IACX,aAAa,KAAK;IAClB,YAAY,KAAK,eAAe;KAC9B,MAAM;KACN,YAAY,CAAC;KACb,UAAU,CAAC;IACb;GACF,CAAC;CAEL;CAIF,IAAI,qBAAqB,SAAS,GAChC,OAAO,KAAK,EACY,qBACxB,CAAC;CAGH,OAAO;AACT"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { brandGeminiProviderTool } from "./gemini-provider-tool.js";
|
|
2
2
|
//#region src/tools/url-context-tool.ts
|
|
3
3
|
function convertUrlContextToolToAdapterFormat(_tool) {
|
|
4
4
|
return { urlContext: {} };
|
|
5
5
|
}
|
|
6
6
|
function urlContextTool() {
|
|
7
|
-
return
|
|
7
|
+
return brandGeminiProviderTool({
|
|
8
8
|
name: "url_context",
|
|
9
9
|
description: "",
|
|
10
10
|
metadata: {}
|
|
11
|
-
});
|
|
11
|
+
}, "url_context");
|
|
12
12
|
}
|
|
13
13
|
//#endregion
|
|
14
14
|
export { convertUrlContextToolToAdapterFormat, urlContextTool };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-context-tool.js","names":[],"sources":["../../../src/tools/url-context-tool.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"url-context-tool.js","names":[],"sources":["../../../src/tools/url-context-tool.ts"],"sourcesContent":["import { brandGeminiProviderTool } from './gemini-provider-tool'\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 brandGeminiProviderTool<GeminiUrlContextTool>(\n {\n name: 'url_context',\n description: '',\n metadata: {},\n },\n 'url_context',\n )\n}\n"],"mappings":";;AAUA,SAAgB,qCAAqC,OAAa;CAChE,OAAO,EACL,YAAY,CAAC,EACf;AACF;AAEA,SAAgB,iBAAuC;CACrD,OAAO,wBACL;EACE,MAAM;EACN,aAAa;EACb,UAAU,CAAC;CACb,GACA,aACF;AACF"}
|
package/dist/esm/usage.js
CHANGED
|
@@ -25,9 +25,7 @@ function flattenModalityTokenCounts(modalities) {
|
|
|
25
25
|
case "VIDEO":
|
|
26
26
|
result.videoTokens = (result.videoTokens ?? 0) + count;
|
|
27
27
|
break;
|
|
28
|
-
case "DOCUMENT":
|
|
29
|
-
result.documentTokens = (result.documentTokens ?? 0) + count;
|
|
30
|
-
break;
|
|
28
|
+
case "DOCUMENT": result.documentTokens = (result.documentTokens ?? 0) + count;
|
|
31
29
|
}
|
|
32
30
|
}
|
|
33
31
|
return result;
|
package/dist/esm/usage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.js","names":[],"sources":["../../src/usage.ts"],"sourcesContent":["import { buildBaseUsage } from '@tanstack/ai'\nimport type { TokenUsage } from '@tanstack/ai'\nimport type {\n GenerateContentResponseUsageMetadata,\n ModalityTokenCount,\n} from '@google/genai'\n\n/**\n * Flattened modality token counts for normalized usage reporting.\n * Maps Gemini's ModalityTokenCount array to individual fields.\n */\nexport interface FlattenedModalityTokens {\n /** Text tokens */\n textTokens?: number\n /** Image tokens */\n imageTokens?: number\n /** Audio tokens */\n audioTokens?: number\n /** Video tokens */\n videoTokens?: number\n /** Document tokens (e.g. PDF inputs) */\n documentTokens?: number\n}\n\n/**\n * Flattens Gemini's ModalityTokenCount array into individual token fields.\n * Extracts TEXT, IMAGE, AUDIO, VIDEO, DOCUMENT modality counts into a\n * normalized structure.\n */\nexport function flattenModalityTokenCounts(\n modalities?: Array<ModalityTokenCount>,\n): FlattenedModalityTokens {\n if (!modalities || modalities.length === 0) {\n return {}\n }\n\n const result: FlattenedModalityTokens = {}\n\n for (const item of modalities) {\n if (!item.modality || item.tokenCount === undefined) {\n continue\n }\n\n const modality = item.modality.toUpperCase()\n const count = item.tokenCount\n\n switch (modality) {\n case 'TEXT':\n result.textTokens = (result.textTokens ?? 0) + count\n break\n case 'IMAGE':\n result.imageTokens = (result.imageTokens ?? 0) + count\n break\n case 'AUDIO':\n result.audioTokens = (result.audioTokens ?? 0) + count\n break\n case 'VIDEO':\n result.videoTokens = (result.videoTokens ?? 0) + count\n break\n case 'DOCUMENT':\n result.documentTokens = (result.documentTokens ?? 0) + count\n break\n }\n }\n\n return result\n}\n\n/**\n * Checks if a FlattenedModalityTokens object has any values set.\n */\nexport function hasModalityTokens(tokens: FlattenedModalityTokens): boolean {\n return (\n tokens.textTokens !== undefined ||\n tokens.imageTokens !== undefined ||\n tokens.audioTokens !== undefined ||\n tokens.videoTokens !== undefined ||\n tokens.documentTokens !== undefined\n )\n}\n\n/**\n * Gemini-specific provider usage details.\n * These fields are unique to Gemini and placed in providerUsageDetails.\n */\nexport type GeminiProviderUsageDetails = {\n /**\n * The traffic type for this request.\n * Can indicate whether request was handled by different service tiers.\n */\n trafficType?: string\n /**\n * Number of tokens in the results from tool executions,\n * which are provided back to the model as input.\n */\n toolUsePromptTokenCount?: number\n /**\n * Detailed breakdown by modality of the token counts from\n * the results of tool executions.\n */\n toolUsePromptTokensDetails?: Array<{\n modality: string\n tokenCount: number\n }>\n /**\n * Detailed breakdown of cache tokens by modality.\n * More granular than the normalized cachedTokens field.\n */\n cacheTokensDetails?: Array<{\n modality: string\n tokenCount: number\n }>\n}\n\n/**\n * Build normalized TokenUsage from Gemini's usageMetadata.\n * Handles modality breakdowns and thinking tokens. Returns `undefined` when the\n * provider reported no usage metadata, so callers omit the field rather than\n * fabricating zeroed totals.\n */\nexport function buildGeminiUsage(\n usageMetadata: GenerateContentResponseUsageMetadata | undefined | null,\n): TokenUsage<GeminiProviderUsageDetails> | undefined {\n if (!usageMetadata) return undefined\n\n const promptTokens = usageMetadata.promptTokenCount ?? 0\n const completionTokens = usageMetadata.candidatesTokenCount ?? 0\n\n const result = buildBaseUsage<GeminiProviderUsageDetails>({\n promptTokens: promptTokens,\n completionTokens: completionTokens,\n totalTokens:\n usageMetadata.totalTokenCount ?? promptTokens + completionTokens,\n })\n\n // Add prompt token details\n // Flatten modality breakdown for prompt\n const promptModalities = flattenModalityTokenCounts(\n usageMetadata.promptTokensDetails,\n )\n const cachedTokens = usageMetadata.cachedContentTokenCount\n\n const promptTokensDetails = {\n ...(hasModalityTokens(promptModalities) ? promptModalities : {}),\n ...(cachedTokens !== undefined && cachedTokens > 0 ? { cachedTokens } : {}),\n }\n\n // Add completion token details\n // Flatten modality breakdown for candidates (output)\n const completionModalities = flattenModalityTokenCounts(\n usageMetadata.candidatesTokensDetails,\n )\n const thoughtsTokens = usageMetadata.thoughtsTokenCount\n\n const completionTokensDetails = {\n ...(hasModalityTokens(completionModalities) ? completionModalities : {}),\n // Map thoughtsTokenCount to reasoningTokens for consistency with OpenAI\n ...(thoughtsTokens !== undefined && thoughtsTokens > 0\n ? { reasoningTokens: thoughtsTokens }\n : {}),\n }\n\n // Add provider-specific details\n const providerDetails: GeminiProviderUsageDetails = {\n ...(usageMetadata.trafficType\n ? { trafficType: usageMetadata.trafficType }\n : {}),\n ...(usageMetadata.toolUsePromptTokenCount !== undefined &&\n usageMetadata.toolUsePromptTokenCount > 0\n ? { toolUsePromptTokenCount: usageMetadata.toolUsePromptTokenCount }\n : {}),\n ...(usageMetadata.toolUsePromptTokensDetails &&\n usageMetadata.toolUsePromptTokensDetails.length > 0\n ? {\n toolUsePromptTokensDetails:\n usageMetadata.toolUsePromptTokensDetails.map((item) => ({\n modality: item.modality || 'UNKNOWN',\n tokenCount: item.tokenCount ?? 0,\n })),\n }\n : {}),\n ...(usageMetadata.cacheTokensDetails &&\n usageMetadata.cacheTokensDetails.length > 0\n ? {\n cacheTokensDetails: usageMetadata.cacheTokensDetails.map((item) => ({\n modality: item.modality || 'UNKNOWN',\n tokenCount: item.tokenCount ?? 0,\n })),\n }\n : {}),\n }\n\n // Add prompt token details if available\n if (Object.keys(promptTokensDetails).length > 0) {\n result.promptTokensDetails = promptTokensDetails\n }\n // Add provider details if available\n if (Object.keys(providerDetails).length > 0) {\n result.providerUsageDetails = providerDetails\n }\n // Add completion token details if available\n if (Object.keys(completionTokensDetails).length > 0) {\n result.completionTokensDetails = completionTokensDetails\n }\n\n return result\n}\n"],"mappings":";;;;;;;AA6BA,SAAgB,2BACd,YACyB;CACzB,IAAI,CAAC,cAAc,WAAW,WAAW,GACvC,OAAO,CAAC;CAGV,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,QAAQ,YAAY;EAC7B,IAAI,CAAC,KAAK,YAAY,KAAK,eAAe,KAAA,GACxC;EAGF,MAAM,WAAW,KAAK,SAAS,YAAY;EAC3C,MAAM,QAAQ,KAAK;EAEnB,QAAQ,UAAR;GACE,KAAK;IACH,OAAO,cAAc,OAAO,cAAc,KAAK;IAC/C;GACF,KAAK;IACH,OAAO,eAAe,OAAO,eAAe,KAAK;IACjD;GACF,KAAK;IACH,OAAO,eAAe,OAAO,eAAe,KAAK;IACjD;GACF,KAAK;IACH,OAAO,eAAe,OAAO,eAAe,KAAK;IACjD;GACF,KAAK
|
|
1
|
+
{"version":3,"file":"usage.js","names":[],"sources":["../../src/usage.ts"],"sourcesContent":["import { buildBaseUsage } from '@tanstack/ai'\nimport type { TokenUsage } from '@tanstack/ai'\nimport type {\n GenerateContentResponseUsageMetadata,\n ModalityTokenCount,\n} from '@google/genai'\n\n/**\n * Flattened modality token counts for normalized usage reporting.\n * Maps Gemini's ModalityTokenCount array to individual fields.\n */\nexport interface FlattenedModalityTokens {\n /** Text tokens */\n textTokens?: number\n /** Image tokens */\n imageTokens?: number\n /** Audio tokens */\n audioTokens?: number\n /** Video tokens */\n videoTokens?: number\n /** Document tokens (e.g. PDF inputs) */\n documentTokens?: number\n}\n\n/**\n * Flattens Gemini's ModalityTokenCount array into individual token fields.\n * Extracts TEXT, IMAGE, AUDIO, VIDEO, DOCUMENT modality counts into a\n * normalized structure.\n */\nexport function flattenModalityTokenCounts(\n modalities?: Array<ModalityTokenCount>,\n): FlattenedModalityTokens {\n if (!modalities || modalities.length === 0) {\n return {}\n }\n\n const result: FlattenedModalityTokens = {}\n\n for (const item of modalities) {\n if (!item.modality || item.tokenCount === undefined) {\n continue\n }\n\n const modality = item.modality.toUpperCase()\n const count = item.tokenCount\n\n switch (modality) {\n case 'TEXT':\n result.textTokens = (result.textTokens ?? 0) + count\n break\n case 'IMAGE':\n result.imageTokens = (result.imageTokens ?? 0) + count\n break\n case 'AUDIO':\n result.audioTokens = (result.audioTokens ?? 0) + count\n break\n case 'VIDEO':\n result.videoTokens = (result.videoTokens ?? 0) + count\n break\n case 'DOCUMENT':\n result.documentTokens = (result.documentTokens ?? 0) + count\n break\n }\n }\n\n return result\n}\n\n/**\n * Checks if a FlattenedModalityTokens object has any values set.\n */\nexport function hasModalityTokens(tokens: FlattenedModalityTokens): boolean {\n return (\n tokens.textTokens !== undefined ||\n tokens.imageTokens !== undefined ||\n tokens.audioTokens !== undefined ||\n tokens.videoTokens !== undefined ||\n tokens.documentTokens !== undefined\n )\n}\n\n/**\n * Gemini-specific provider usage details.\n * These fields are unique to Gemini and placed in providerUsageDetails.\n */\nexport type GeminiProviderUsageDetails = {\n /**\n * The traffic type for this request.\n * Can indicate whether request was handled by different service tiers.\n */\n trafficType?: string\n /**\n * Number of tokens in the results from tool executions,\n * which are provided back to the model as input.\n */\n toolUsePromptTokenCount?: number\n /**\n * Detailed breakdown by modality of the token counts from\n * the results of tool executions.\n */\n toolUsePromptTokensDetails?: Array<{\n modality: string\n tokenCount: number\n }>\n /**\n * Detailed breakdown of cache tokens by modality.\n * More granular than the normalized cachedTokens field.\n */\n cacheTokensDetails?: Array<{\n modality: string\n tokenCount: number\n }>\n}\n\n/**\n * Build normalized TokenUsage from Gemini's usageMetadata.\n * Handles modality breakdowns and thinking tokens. Returns `undefined` when the\n * provider reported no usage metadata, so callers omit the field rather than\n * fabricating zeroed totals.\n */\nexport function buildGeminiUsage(\n usageMetadata: GenerateContentResponseUsageMetadata | undefined | null,\n): TokenUsage<GeminiProviderUsageDetails> | undefined {\n if (!usageMetadata) return undefined\n\n const promptTokens = usageMetadata.promptTokenCount ?? 0\n const completionTokens = usageMetadata.candidatesTokenCount ?? 0\n\n const result = buildBaseUsage<GeminiProviderUsageDetails>({\n promptTokens: promptTokens,\n completionTokens: completionTokens,\n totalTokens:\n usageMetadata.totalTokenCount ?? promptTokens + completionTokens,\n })\n\n // Add prompt token details\n // Flatten modality breakdown for prompt\n const promptModalities = flattenModalityTokenCounts(\n usageMetadata.promptTokensDetails,\n )\n const cachedTokens = usageMetadata.cachedContentTokenCount\n\n const promptTokensDetails = {\n ...(hasModalityTokens(promptModalities) ? promptModalities : {}),\n ...(cachedTokens !== undefined && cachedTokens > 0 ? { cachedTokens } : {}),\n }\n\n // Add completion token details\n // Flatten modality breakdown for candidates (output)\n const completionModalities = flattenModalityTokenCounts(\n usageMetadata.candidatesTokensDetails,\n )\n const thoughtsTokens = usageMetadata.thoughtsTokenCount\n\n const completionTokensDetails = {\n ...(hasModalityTokens(completionModalities) ? completionModalities : {}),\n // Map thoughtsTokenCount to reasoningTokens for consistency with OpenAI\n ...(thoughtsTokens !== undefined && thoughtsTokens > 0\n ? { reasoningTokens: thoughtsTokens }\n : {}),\n }\n\n // Add provider-specific details\n const providerDetails: GeminiProviderUsageDetails = {\n ...(usageMetadata.trafficType\n ? { trafficType: usageMetadata.trafficType }\n : {}),\n ...(usageMetadata.toolUsePromptTokenCount !== undefined &&\n usageMetadata.toolUsePromptTokenCount > 0\n ? { toolUsePromptTokenCount: usageMetadata.toolUsePromptTokenCount }\n : {}),\n ...(usageMetadata.toolUsePromptTokensDetails &&\n usageMetadata.toolUsePromptTokensDetails.length > 0\n ? {\n toolUsePromptTokensDetails:\n usageMetadata.toolUsePromptTokensDetails.map((item) => ({\n modality: item.modality || 'UNKNOWN',\n tokenCount: item.tokenCount ?? 0,\n })),\n }\n : {}),\n ...(usageMetadata.cacheTokensDetails &&\n usageMetadata.cacheTokensDetails.length > 0\n ? {\n cacheTokensDetails: usageMetadata.cacheTokensDetails.map((item) => ({\n modality: item.modality || 'UNKNOWN',\n tokenCount: item.tokenCount ?? 0,\n })),\n }\n : {}),\n }\n\n // Add prompt token details if available\n if (Object.keys(promptTokensDetails).length > 0) {\n result.promptTokensDetails = promptTokensDetails\n }\n // Add provider details if available\n if (Object.keys(providerDetails).length > 0) {\n result.providerUsageDetails = providerDetails\n }\n // Add completion token details if available\n if (Object.keys(completionTokensDetails).length > 0) {\n result.completionTokensDetails = completionTokensDetails\n }\n\n return result\n}\n"],"mappings":";;;;;;;AA6BA,SAAgB,2BACd,YACyB;CACzB,IAAI,CAAC,cAAc,WAAW,WAAW,GACvC,OAAO,CAAC;CAGV,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,QAAQ,YAAY;EAC7B,IAAI,CAAC,KAAK,YAAY,KAAK,eAAe,KAAA,GACxC;EAGF,MAAM,WAAW,KAAK,SAAS,YAAY;EAC3C,MAAM,QAAQ,KAAK;EAEnB,QAAQ,UAAR;GACE,KAAK;IACH,OAAO,cAAc,OAAO,cAAc,KAAK;IAC/C;GACF,KAAK;IACH,OAAO,eAAe,OAAO,eAAe,KAAK;IACjD;GACF,KAAK;IACH,OAAO,eAAe,OAAO,eAAe,KAAK;IACjD;GACF,KAAK;IACH,OAAO,eAAe,OAAO,eAAe,KAAK;IACjD;GACF,KAAK,YACH,OAAO,kBAAkB,OAAO,kBAAkB,KAAK;EAE3D;CACF;CAEA,OAAO;AACT;;;;AAKA,SAAgB,kBAAkB,QAA0C;CAC1E,OACE,OAAO,eAAe,KAAA,KACtB,OAAO,gBAAgB,KAAA,KACvB,OAAO,gBAAgB,KAAA,KACvB,OAAO,gBAAgB,KAAA,KACvB,OAAO,mBAAmB,KAAA;AAE9B;;;;;;;AAyCA,SAAgB,iBACd,eACoD;CACpD,IAAI,CAAC,eAAe,OAAO,KAAA;CAE3B,MAAM,eAAe,cAAc,oBAAoB;CACvD,MAAM,mBAAmB,cAAc,wBAAwB;CAE/D,MAAM,SAAS,eAA2C;EAC1C;EACI;EAClB,aACE,cAAc,mBAAmB,eAAe;CACpD,CAAC;CAID,MAAM,mBAAmB,2BACvB,cAAc,mBAChB;CACA,MAAM,eAAe,cAAc;CAEnC,MAAM,sBAAsB;EAC1B,GAAI,kBAAkB,gBAAgB,IAAI,mBAAmB,CAAC;EAC9D,GAAI,iBAAiB,KAAA,KAAa,eAAe,IAAI,EAAE,aAAa,IAAI,CAAC;CAC3E;CAIA,MAAM,uBAAuB,2BAC3B,cAAc,uBAChB;CACA,MAAM,iBAAiB,cAAc;CAErC,MAAM,0BAA0B;EAC9B,GAAI,kBAAkB,oBAAoB,IAAI,uBAAuB,CAAC;EAEtE,GAAI,mBAAmB,KAAA,KAAa,iBAAiB,IACjD,EAAE,iBAAiB,eAAe,IAClC,CAAC;CACP;CAGA,MAAM,kBAA8C;EAClD,GAAI,cAAc,cACd,EAAE,aAAa,cAAc,YAAY,IACzC,CAAC;EACL,GAAI,cAAc,4BAA4B,KAAA,KAC9C,cAAc,0BAA0B,IACpC,EAAE,yBAAyB,cAAc,wBAAwB,IACjE,CAAC;EACL,GAAI,cAAc,8BAClB,cAAc,2BAA2B,SAAS,IAC9C,EACE,4BACE,cAAc,2BAA2B,KAAK,UAAU;GACtD,UAAU,KAAK,YAAY;GAC3B,YAAY,KAAK,cAAc;EACjC,EAAE,EACN,IACA,CAAC;EACL,GAAI,cAAc,sBAClB,cAAc,mBAAmB,SAAS,IACtC,EACE,oBAAoB,cAAc,mBAAmB,KAAK,UAAU;GAClE,UAAU,KAAK,YAAY;GAC3B,YAAY,KAAK,cAAc;EACjC,EAAE,EACJ,IACA,CAAC;CACP;CAGA,IAAI,OAAO,KAAK,mBAAmB,CAAC,CAAC,SAAS,GAC5C,OAAO,sBAAsB;CAG/B,IAAI,OAAO,KAAK,eAAe,CAAC,CAAC,SAAS,GACxC,OAAO,uBAAuB;CAGhC,IAAI,OAAO,KAAK,uBAAuB,CAAC,CAAC,SAAS,GAChD,OAAO,0BAA0B;CAGnC,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-gemini",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Google Gemini adapter for TanStack AI chat, images, speech, audio generation, and structured outputs.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"@tanstack/ai-utils": "^0.4.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@tanstack/ai": "^0.
|
|
62
|
+
"@tanstack/ai": "^0.46.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@vitest/coverage-v8": "4.
|
|
66
|
-
"vite": "^8.1
|
|
65
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
66
|
+
"vite": "^8.2.1",
|
|
67
67
|
"zod": "^4.2.0",
|
|
68
|
-
"@tanstack/ai": "0.
|
|
68
|
+
"@tanstack/ai": "0.46.0"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "vite build",
|
package/src/adapters/image.ts
CHANGED
|
@@ -7,18 +7,20 @@ import {
|
|
|
7
7
|
} from '../utils'
|
|
8
8
|
import { buildGeminiUsage } from '../usage'
|
|
9
9
|
import {
|
|
10
|
+
isGeminiNativeImageModel,
|
|
10
11
|
parseNativeImageSize,
|
|
11
12
|
sizeToAspectRatio,
|
|
12
13
|
validateImageSize,
|
|
13
14
|
validateNumberOfImages,
|
|
14
15
|
validatePrompt,
|
|
15
16
|
} from '../image/image-provider-options'
|
|
16
|
-
import type {
|
|
17
|
+
import type { GeminiImageModels } from '../model-meta'
|
|
17
18
|
import type {
|
|
19
|
+
GeminiAnyImageProviderOptions,
|
|
18
20
|
GeminiImageModelInputModalitiesByName,
|
|
19
21
|
GeminiImageModelProviderOptionsByName,
|
|
20
22
|
GeminiImageModelSizeByName,
|
|
21
|
-
|
|
23
|
+
GeminiNativeImageProviderOptions,
|
|
22
24
|
} from '../image/image-provider-options'
|
|
23
25
|
import type {
|
|
24
26
|
GeneratedImage,
|
|
@@ -35,6 +37,7 @@ import type {
|
|
|
35
37
|
GenerateImagesConfig,
|
|
36
38
|
GenerateImagesResponse,
|
|
37
39
|
GoogleGenAI,
|
|
40
|
+
ImageConfig,
|
|
38
41
|
Part,
|
|
39
42
|
} from '@google/genai'
|
|
40
43
|
import type { GeminiClientConfig } from '../utils/client'
|
|
@@ -45,7 +48,7 @@ import type { GeminiClientConfig } from '../utils/client'
|
|
|
45
48
|
export interface GeminiImageConfig extends GeminiClientConfig {}
|
|
46
49
|
|
|
47
50
|
/** Model type for Gemini Image */
|
|
48
|
-
export type GeminiImageModel =
|
|
51
|
+
export type GeminiImageModel = GeminiImageModels
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
54
|
* Gemini Image Generation Adapter
|
|
@@ -65,7 +68,7 @@ export class GeminiImageAdapter<
|
|
|
65
68
|
TModel extends GeminiImageModel,
|
|
66
69
|
> extends BaseImageAdapter<
|
|
67
70
|
TModel,
|
|
68
|
-
|
|
71
|
+
GeminiAnyImageProviderOptions,
|
|
69
72
|
GeminiImageModelProviderOptionsByName,
|
|
70
73
|
GeminiImageModelSizeByName,
|
|
71
74
|
GeminiImageModelInputModalitiesByName
|
|
@@ -75,7 +78,7 @@ export class GeminiImageAdapter<
|
|
|
75
78
|
|
|
76
79
|
// Type-only property - never assigned at runtime
|
|
77
80
|
declare '~types': {
|
|
78
|
-
providerOptions:
|
|
81
|
+
providerOptions: GeminiAnyImageProviderOptions
|
|
79
82
|
modelProviderOptionsByName: GeminiImageModelProviderOptionsByName
|
|
80
83
|
modelSizeByName: GeminiImageModelSizeByName
|
|
81
84
|
modelInputModalitiesByName: GeminiImageModelInputModalitiesByName
|
|
@@ -89,7 +92,7 @@ export class GeminiImageAdapter<
|
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
async generateImages(
|
|
92
|
-
options: ImageGenerationOptions<
|
|
95
|
+
options: ImageGenerationOptions<GeminiAnyImageProviderOptions>,
|
|
93
96
|
): Promise<ImageGenerationResult> {
|
|
94
97
|
const { model, logger } = options
|
|
95
98
|
|
|
@@ -121,7 +124,7 @@ export class GeminiImageAdapter<
|
|
|
121
124
|
)
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
if (
|
|
127
|
+
if (isGeminiNativeImageModel(model)) {
|
|
125
128
|
return await this.generateWithGeminiApi(options, resolved)
|
|
126
129
|
}
|
|
127
130
|
|
|
@@ -155,29 +158,40 @@ export class GeminiImageAdapter<
|
|
|
155
158
|
}
|
|
156
159
|
}
|
|
157
160
|
|
|
158
|
-
private isGeminiImageModel(model: string): boolean {
|
|
159
|
-
return model.startsWith('gemini-')
|
|
160
|
-
}
|
|
161
|
-
|
|
162
161
|
private async generateWithGeminiApi(
|
|
163
|
-
options: ImageGenerationOptions<
|
|
162
|
+
options: ImageGenerationOptions<GeminiNativeImageProviderOptions>,
|
|
164
163
|
resolved: ResolvedMediaPrompt,
|
|
165
164
|
): Promise<ImageGenerationResult> {
|
|
166
165
|
const { model, size, numberOfImages, modelOptions } = options
|
|
167
166
|
|
|
168
167
|
const parsedSize = size ? parseNativeImageSize(size) : undefined
|
|
169
168
|
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
169
|
+
// The portable `size` option is the baseline; modelOptions.imageConfig is
|
|
170
|
+
// the provider escape hatch and wins per field, so a caller passing only
|
|
171
|
+
// `imageConfig.imageSize` keeps the aspectRatio derived from `size`.
|
|
172
|
+
const imageConfig: ImageConfig = {
|
|
173
|
+
...(parsedSize?.aspectRatio && { aspectRatio: parsedSize.aspectRatio }),
|
|
174
|
+
...(parsedSize?.resolution && { imageSize: parsedSize.resolution }),
|
|
175
|
+
...modelOptions?.imageConfig,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Named picks, never a wholesale spread: the Imagen-shaped fields of
|
|
179
|
+
// GeminiImageProviderOptions (personGeneration, safetyFilterLevel,
|
|
180
|
+
// addWatermark, outputMimeType, …) are only valid on GenerateImagesConfig
|
|
181
|
+
// and would be rejected by generateContent. Picking by name means no
|
|
182
|
+
// Imagen field can reach this path even if one slips past the per-model
|
|
183
|
+
// provider-options map.
|
|
184
|
+
const nativeConfig: GenerateContentConfig = {
|
|
185
|
+
...(modelOptions?.seed !== undefined && { seed: modelOptions.seed }),
|
|
186
|
+
...(modelOptions?.safetySettings !== undefined && {
|
|
187
|
+
safetySettings: modelOptions.safetySettings,
|
|
188
|
+
}),
|
|
189
|
+
...(modelOptions?.thinkingConfig !== undefined && {
|
|
190
|
+
thinkingConfig: modelOptions.thinkingConfig,
|
|
191
|
+
}),
|
|
192
|
+
...(modelOptions?.systemInstruction !== undefined && {
|
|
193
|
+
systemInstruction: modelOptions.systemInstruction,
|
|
194
|
+
}),
|
|
181
195
|
}
|
|
182
196
|
|
|
183
197
|
const config: GenerateContentConfig = {
|
|
@@ -186,16 +200,7 @@ export class GeminiImageAdapter<
|
|
|
186
200
|
// IMPORTANT: responseModalities is a protected default — set it AFTER
|
|
187
201
|
// nativeConfig so nothing can silently disable image output.
|
|
188
202
|
responseModalities: ['TEXT', 'IMAGE'],
|
|
189
|
-
...(
|
|
190
|
-
imageConfig: {
|
|
191
|
-
...(parsedSize.aspectRatio && {
|
|
192
|
-
aspectRatio: parsedSize.aspectRatio,
|
|
193
|
-
}),
|
|
194
|
-
...(parsedSize.resolution && {
|
|
195
|
-
imageSize: parsedSize.resolution,
|
|
196
|
-
}),
|
|
197
|
-
},
|
|
198
|
-
}),
|
|
203
|
+
...(Object.keys(imageConfig).length > 0 && { imageConfig }),
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
const contents = this.buildContents(resolved, numberOfImages)
|
|
@@ -321,7 +326,7 @@ export class GeminiImageAdapter<
|
|
|
321
326
|
}
|
|
322
327
|
|
|
323
328
|
private buildImagenConfig(
|
|
324
|
-
options: ImageGenerationOptions<
|
|
329
|
+
options: ImageGenerationOptions<GeminiAnyImageProviderOptions>,
|
|
325
330
|
): GenerateImagesConfig {
|
|
326
331
|
const { size, numberOfImages, modelOptions } = options
|
|
327
332
|
|
|
@@ -329,11 +334,62 @@ export class GeminiImageAdapter<
|
|
|
329
334
|
// vendor `GenerateImagesConfig` fields are `field?: T` (no `| undefined`),
|
|
330
335
|
// so we can only assign the property when we actually have a value.
|
|
331
336
|
const sizeAspectRatio = size ? sizeToAspectRatio(size) : undefined
|
|
337
|
+
|
|
338
|
+
// Named picks, never a wholesale spread — the mirror image of the native
|
|
339
|
+
// path below. A native-only field (safetySettings, thinkingConfig,
|
|
340
|
+
// imageConfig, systemInstruction) belongs to GenerateContentConfig and is
|
|
341
|
+
// rejected by generateImages with 400 INVALID_ARGUMENT, so it must not be
|
|
342
|
+
// able to reach here even when the caller's `modelOptions` was typed
|
|
343
|
+
// against both shapes at once (e.g. an adapter inferred from a union of
|
|
344
|
+
// model names).
|
|
332
345
|
return {
|
|
333
346
|
numberOfImages: numberOfImages ?? 1,
|
|
334
|
-
// Map size to aspect ratio if provided
|
|
347
|
+
// Map size to aspect ratio if provided; modelOptions.aspectRatio,
|
|
348
|
+
// picked after it, overrides.
|
|
335
349
|
...(sizeAspectRatio !== undefined && { aspectRatio: sizeAspectRatio }),
|
|
336
|
-
...modelOptions
|
|
350
|
+
...(modelOptions?.aspectRatio !== undefined && {
|
|
351
|
+
aspectRatio: modelOptions.aspectRatio,
|
|
352
|
+
}),
|
|
353
|
+
...(modelOptions?.personGeneration !== undefined && {
|
|
354
|
+
personGeneration: modelOptions.personGeneration,
|
|
355
|
+
}),
|
|
356
|
+
...(modelOptions?.safetyFilterLevel !== undefined && {
|
|
357
|
+
safetyFilterLevel: modelOptions.safetyFilterLevel,
|
|
358
|
+
}),
|
|
359
|
+
...(modelOptions?.seed !== undefined && { seed: modelOptions.seed }),
|
|
360
|
+
...(modelOptions?.addWatermark !== undefined && {
|
|
361
|
+
addWatermark: modelOptions.addWatermark,
|
|
362
|
+
}),
|
|
363
|
+
...(modelOptions?.language !== undefined && {
|
|
364
|
+
language: modelOptions.language,
|
|
365
|
+
}),
|
|
366
|
+
...(modelOptions?.negativePrompt !== undefined && {
|
|
367
|
+
negativePrompt: modelOptions.negativePrompt,
|
|
368
|
+
}),
|
|
369
|
+
...(modelOptions?.outputMimeType !== undefined && {
|
|
370
|
+
outputMimeType: modelOptions.outputMimeType,
|
|
371
|
+
}),
|
|
372
|
+
...(modelOptions?.outputCompressionQuality !== undefined && {
|
|
373
|
+
outputCompressionQuality: modelOptions.outputCompressionQuality,
|
|
374
|
+
}),
|
|
375
|
+
...(modelOptions?.guidanceScale !== undefined && {
|
|
376
|
+
guidanceScale: modelOptions.guidanceScale,
|
|
377
|
+
}),
|
|
378
|
+
...(modelOptions?.enhancePrompt !== undefined && {
|
|
379
|
+
enhancePrompt: modelOptions.enhancePrompt,
|
|
380
|
+
}),
|
|
381
|
+
...(modelOptions?.includeSafetyAttributes !== undefined && {
|
|
382
|
+
includeSafetyAttributes: modelOptions.includeSafetyAttributes,
|
|
383
|
+
}),
|
|
384
|
+
...(modelOptions?.includeRaiReason !== undefined && {
|
|
385
|
+
includeRaiReason: modelOptions.includeRaiReason,
|
|
386
|
+
}),
|
|
387
|
+
...(modelOptions?.outputGcsUri !== undefined && {
|
|
388
|
+
outputGcsUri: modelOptions.outputGcsUri,
|
|
389
|
+
}),
|
|
390
|
+
...(modelOptions?.labels !== undefined && {
|
|
391
|
+
labels: modelOptions.labels,
|
|
392
|
+
}),
|
|
337
393
|
}
|
|
338
394
|
}
|
|
339
395
|
|
|
@@ -392,6 +448,18 @@ export class GeminiImageAdapter<
|
|
|
392
448
|
}
|
|
393
449
|
}
|
|
394
450
|
|
|
451
|
+
/** @deprecated Shut down 2026-06-25. Use `gemini-3.1-flash-image`. */
|
|
452
|
+
export function createGeminiImage(
|
|
453
|
+
model: 'gemini-3.1-flash-image-preview',
|
|
454
|
+
apiKey: string,
|
|
455
|
+
config?: Omit<GeminiImageConfig, 'apiKey'>,
|
|
456
|
+
): GeminiImageAdapter<'gemini-3.1-flash-image-preview'>
|
|
457
|
+
/** @deprecated Shut down 2026-06-25. Use `gemini-3-pro-image`. */
|
|
458
|
+
export function createGeminiImage(
|
|
459
|
+
model: 'gemini-3-pro-image-preview',
|
|
460
|
+
apiKey: string,
|
|
461
|
+
config?: Omit<GeminiImageConfig, 'apiKey'>,
|
|
462
|
+
): GeminiImageAdapter<'gemini-3-pro-image-preview'>
|
|
395
463
|
/**
|
|
396
464
|
* Creates a Gemini image adapter with explicit API key.
|
|
397
465
|
* Type resolution happens here at the call site.
|
|
@@ -411,6 +479,11 @@ export class GeminiImageAdapter<
|
|
|
411
479
|
* });
|
|
412
480
|
* ```
|
|
413
481
|
*/
|
|
482
|
+
export function createGeminiImage<TModel extends GeminiImageModel>(
|
|
483
|
+
model: TModel,
|
|
484
|
+
apiKey: string,
|
|
485
|
+
config?: Omit<GeminiImageConfig, 'apiKey'>,
|
|
486
|
+
): GeminiImageAdapter<TModel>
|
|
414
487
|
export function createGeminiImage<TModel extends GeminiImageModel>(
|
|
415
488
|
model: TModel,
|
|
416
489
|
apiKey: string,
|
|
@@ -419,6 +492,16 @@ export function createGeminiImage<TModel extends GeminiImageModel>(
|
|
|
419
492
|
return new GeminiImageAdapter({ apiKey, ...config }, model)
|
|
420
493
|
}
|
|
421
494
|
|
|
495
|
+
/** @deprecated Shut down 2026-06-25. Use `gemini-3.1-flash-image`. */
|
|
496
|
+
export function geminiImage(
|
|
497
|
+
model: 'gemini-3.1-flash-image-preview',
|
|
498
|
+
config?: Omit<GeminiImageConfig, 'apiKey'>,
|
|
499
|
+
): GeminiImageAdapter<'gemini-3.1-flash-image-preview'>
|
|
500
|
+
/** @deprecated Shut down 2026-06-25. Use `gemini-3-pro-image`. */
|
|
501
|
+
export function geminiImage(
|
|
502
|
+
model: 'gemini-3-pro-image-preview',
|
|
503
|
+
config?: Omit<GeminiImageConfig, 'apiKey'>,
|
|
504
|
+
): GeminiImageAdapter<'gemini-3-pro-image-preview'>
|
|
422
505
|
/**
|
|
423
506
|
* Creates a Gemini image adapter with automatic API key detection from environment variables.
|
|
424
507
|
* Type resolution happens here at the call site.
|
|
@@ -443,6 +526,10 @@ export function createGeminiImage<TModel extends GeminiImageModel>(
|
|
|
443
526
|
* });
|
|
444
527
|
* ```
|
|
445
528
|
*/
|
|
529
|
+
export function geminiImage<TModel extends GeminiImageModel>(
|
|
530
|
+
model: TModel,
|
|
531
|
+
config?: Omit<GeminiImageConfig, 'apiKey'>,
|
|
532
|
+
): GeminiImageAdapter<TModel>
|
|
446
533
|
export function geminiImage<TModel extends GeminiImageModel>(
|
|
447
534
|
model: TModel,
|
|
448
535
|
config?: Omit<GeminiImageConfig, 'apiKey'>,
|
|
@@ -6,6 +6,11 @@ import {
|
|
|
6
6
|
generateId,
|
|
7
7
|
getGeminiApiKeyFromEnv,
|
|
8
8
|
} from '../../utils/client'
|
|
9
|
+
import {
|
|
10
|
+
getGeminiProviderToolKind,
|
|
11
|
+
getGeminiProviderToolMetadata,
|
|
12
|
+
} from '../../tools/gemini-provider-tool'
|
|
13
|
+
import { assertUniqueToolNames } from '@tanstack/ai/adapter-internals'
|
|
9
14
|
import type { InternalLogger } from '@tanstack/ai/adapter-internals'
|
|
10
15
|
import type {
|
|
11
16
|
GeminiChatModelToolCapabilitiesByName,
|
|
@@ -122,9 +127,9 @@ type ResolveInputModalities<TModel extends string> =
|
|
|
122
127
|
|
|
123
128
|
/**
|
|
124
129
|
* Resolve tool capabilities for a specific model. Reuses the chat-model
|
|
125
|
-
* capability map: `google_maps` / `google_search_retrieval`
|
|
126
|
-
*
|
|
127
|
-
*
|
|
130
|
+
* capability map: `google_maps` / `google_search_retrieval` are rejected at
|
|
131
|
+
* runtime by `convertToolsToInteractionsFormat`, but per-model gating happens
|
|
132
|
+
* here at compile time.
|
|
128
133
|
*/
|
|
129
134
|
type ResolveToolCapabilities<TModel extends string> =
|
|
130
135
|
TModel extends keyof GeminiChatModelToolCapabilitiesByName
|
|
@@ -153,8 +158,11 @@ type ResolveToolCapabilities<TModel extends string> =
|
|
|
153
158
|
* corresponding per-tool variants) carrying the raw Interactions delta;
|
|
154
159
|
* see {@link GeminiInteractionsCustomEvent}. `computer_use` is accepted
|
|
155
160
|
* in the request but the Interactions API does not currently stream
|
|
156
|
-
* per-delta CUSTOM events for it. `google_search_retrieval
|
|
157
|
-
* `google_maps
|
|
161
|
+
* per-delta CUSTOM events for it. The `google_search_retrieval` and
|
|
162
|
+
* `google_maps` provider-tool factories are not supported on this adapter and
|
|
163
|
+
* throw a targeted error. There is no Gemini `mcp_server` factory, so a tool
|
|
164
|
+
* merely *named* `mcp_server` is an ordinary function and is sent as a function
|
|
165
|
+
* declaration like any other.
|
|
158
166
|
*
|
|
159
167
|
* @experimental Interactions API is in Beta per Google; shapes may change.
|
|
160
168
|
* @see https://ai.google.dev/gemini-api/docs/interactions
|
|
@@ -781,20 +789,29 @@ function convertToolsToInteractionsFormat<TTool extends Tool>(
|
|
|
781
789
|
tools: Array<TTool> | undefined,
|
|
782
790
|
): Array<InteractionsTool> | undefined {
|
|
783
791
|
if (!tools || tools.length === 0) return undefined
|
|
792
|
+
assertUniqueToolNames(tools)
|
|
784
793
|
|
|
785
794
|
const result: Array<InteractionsTool> = []
|
|
786
795
|
|
|
787
796
|
for (const tool of tools) {
|
|
788
|
-
switch (tool
|
|
797
|
+
switch (getGeminiProviderToolKind(tool)) {
|
|
789
798
|
case 'google_search': {
|
|
790
|
-
const metadata = (tool
|
|
791
|
-
|
|
799
|
+
const metadata = (getGeminiProviderToolMetadata(tool) ?? {}) as {
|
|
800
|
+
searchTypes?: {
|
|
801
|
+
webSearch?: unknown
|
|
802
|
+
imageSearch?: unknown
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
const searchTypes: Array<'web_search' | 'image_search'> = []
|
|
806
|
+
if (metadata.searchTypes?.webSearch !== undefined) {
|
|
807
|
+
searchTypes.push('web_search')
|
|
808
|
+
}
|
|
809
|
+
if (metadata.searchTypes?.imageSearch !== undefined) {
|
|
810
|
+
searchTypes.push('image_search')
|
|
792
811
|
}
|
|
793
812
|
result.push({
|
|
794
813
|
type: 'google_search',
|
|
795
|
-
...(
|
|
796
|
-
? { search_types: metadata.search_types }
|
|
797
|
-
: {}),
|
|
814
|
+
...(searchTypes.length > 0 ? { search_types: searchTypes } : {}),
|
|
798
815
|
})
|
|
799
816
|
break
|
|
800
817
|
}
|
|
@@ -807,7 +824,7 @@ function convertToolsToInteractionsFormat<TTool extends Tool>(
|
|
|
807
824
|
break
|
|
808
825
|
}
|
|
809
826
|
case 'file_search': {
|
|
810
|
-
const metadata = (tool
|
|
827
|
+
const metadata = (getGeminiProviderToolMetadata(tool) ?? {}) as {
|
|
811
828
|
fileSearchStoreNames?: Array<string>
|
|
812
829
|
topK?: number
|
|
813
830
|
metadataFilter?: string
|
|
@@ -825,7 +842,7 @@ function convertToolsToInteractionsFormat<TTool extends Tool>(
|
|
|
825
842
|
break
|
|
826
843
|
}
|
|
827
844
|
case 'computer_use': {
|
|
828
|
-
const metadata = (tool
|
|
845
|
+
const metadata = (getGeminiProviderToolMetadata(tool) ?? {}) as {
|
|
829
846
|
environment?: string
|
|
830
847
|
excludedPredefinedFunctions?: Array<string>
|
|
831
848
|
}
|
|
@@ -856,11 +873,7 @@ function convertToolsToInteractionsFormat<TTool extends Tool>(
|
|
|
856
873
|
throw new Error(
|
|
857
874
|
'`google_maps` is not yet supported on the Gemini Interactions API. Use `geminiText()` for Google Maps grounding.',
|
|
858
875
|
)
|
|
859
|
-
case
|
|
860
|
-
throw new Error(
|
|
861
|
-
'`mcp_server` is not yet supported on the `geminiTextInteractions()` adapter.',
|
|
862
|
-
)
|
|
863
|
-
default: {
|
|
876
|
+
case undefined: {
|
|
864
877
|
if (!tool.description) {
|
|
865
878
|
throw new Error(
|
|
866
879
|
`Tool ${tool.name} requires a description for the Gemini Interactions adapter`,
|