@tanstack/ai-gemini 0.20.0 → 0.21.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 (62) hide show
  1. package/README.md +15 -1
  2. package/dist/esm/adapters/audio.js +113 -61
  3. package/dist/esm/adapters/audio.js.map +1 -1
  4. package/dist/esm/adapters/image.js +224 -225
  5. package/dist/esm/adapters/image.js.map +1 -1
  6. package/dist/esm/adapters/summarize.js +33 -12
  7. package/dist/esm/adapters/summarize.js.map +1 -1
  8. package/dist/esm/adapters/text.js +574 -650
  9. package/dist/esm/adapters/text.js.map +1 -1
  10. package/dist/esm/adapters/tts.js +197 -178
  11. package/dist/esm/adapters/tts.js.map +1 -1
  12. package/dist/esm/adapters/video.js +456 -409
  13. package/dist/esm/adapters/video.js.map +1 -1
  14. package/dist/esm/experimental/index.js +1 -6
  15. package/dist/esm/experimental/text-interactions/adapter.js +877 -1014
  16. package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
  17. package/dist/esm/image/image-provider-options.js +74 -54
  18. package/dist/esm/image/image-provider-options.js.map +1 -1
  19. package/dist/esm/index.js +4 -36
  20. package/dist/esm/model-meta.d.ts +53 -5
  21. package/dist/esm/model-meta.js +733 -129
  22. package/dist/esm/model-meta.js.map +1 -1
  23. package/dist/esm/realtime/adapter.js +242 -228
  24. package/dist/esm/realtime/adapter.js.map +1 -1
  25. package/dist/esm/realtime/client.js +314 -381
  26. package/dist/esm/realtime/client.js.map +1 -1
  27. package/dist/esm/realtime/index.js +3 -0
  28. package/dist/esm/realtime/token.js +50 -36
  29. package/dist/esm/realtime/token.js.map +1 -1
  30. package/dist/esm/realtime/utils.js +202 -245
  31. package/dist/esm/realtime/utils.js.map +1 -1
  32. package/dist/esm/tools/code-execution-tool.js +11 -13
  33. package/dist/esm/tools/code-execution-tool.js.map +1 -1
  34. package/dist/esm/tools/computer-use-tool.js +18 -28
  35. package/dist/esm/tools/computer-use-tool.js.map +1 -1
  36. package/dist/esm/tools/file-search-tool.js +11 -14
  37. package/dist/esm/tools/file-search-tool.js.map +1 -1
  38. package/dist/esm/tools/function-declaration-tool.js +11 -25
  39. package/dist/esm/tools/function-declaration-tool.js.map +1 -1
  40. package/dist/esm/tools/google-maps-tool.js +11 -14
  41. package/dist/esm/tools/google-maps-tool.js.map +1 -1
  42. package/dist/esm/tools/google-search-retriveal-tool.js +11 -14
  43. package/dist/esm/tools/google-search-retriveal-tool.js.map +1 -1
  44. package/dist/esm/tools/google-search-tool.js +11 -14
  45. package/dist/esm/tools/google-search-tool.js.map +1 -1
  46. package/dist/esm/tools/index.js +2 -13
  47. package/dist/esm/tools/tool-converter.js +64 -57
  48. package/dist/esm/tools/tool-converter.js.map +1 -1
  49. package/dist/esm/tools/url-context-tool.js +11 -13
  50. package/dist/esm/tools/url-context-tool.js.map +1 -1
  51. package/dist/esm/usage.js +81 -87
  52. package/dist/esm/usage.js.map +1 -1
  53. package/dist/esm/utils/client.js +31 -24
  54. package/dist/esm/utils/client.js.map +1 -1
  55. package/dist/esm/utils/index.js +2 -0
  56. package/dist/esm/video/video-provider-options.js +73 -20
  57. package/dist/esm/video/video-provider-options.js.map +1 -1
  58. package/package.json +7 -7
  59. package/src/model-meta.ts +120 -10
  60. package/dist/esm/experimental/index.js.map +0 -1
  61. package/dist/esm/index.js.map +0 -1
  62. package/dist/esm/tools/index.js.map +0 -1
package/dist/esm/usage.js CHANGED
@@ -1,94 +1,88 @@
1
1
  import { buildBaseUsage } from "@tanstack/ai";
2
+ //#region src/usage.ts
3
+ /**
4
+ * Flattens Gemini's ModalityTokenCount array into individual token fields.
5
+ * Extracts TEXT, IMAGE, AUDIO, VIDEO, DOCUMENT modality counts into a
6
+ * normalized structure.
7
+ */
2
8
  function flattenModalityTokenCounts(modalities) {
3
- if (!modalities || modalities.length === 0) {
4
- return {};
5
- }
6
- const result = {};
7
- for (const item of modalities) {
8
- if (!item.modality || item.tokenCount === void 0) {
9
- continue;
10
- }
11
- const modality = item.modality.toUpperCase();
12
- const count = item.tokenCount;
13
- switch (modality) {
14
- case "TEXT":
15
- result.textTokens = (result.textTokens ?? 0) + count;
16
- break;
17
- case "IMAGE":
18
- result.imageTokens = (result.imageTokens ?? 0) + count;
19
- break;
20
- case "AUDIO":
21
- result.audioTokens = (result.audioTokens ?? 0) + count;
22
- break;
23
- case "VIDEO":
24
- result.videoTokens = (result.videoTokens ?? 0) + count;
25
- break;
26
- case "DOCUMENT":
27
- result.documentTokens = (result.documentTokens ?? 0) + count;
28
- break;
29
- }
30
- }
31
- return result;
9
+ if (!modalities || modalities.length === 0) return {};
10
+ const result = {};
11
+ for (const item of modalities) {
12
+ if (!item.modality || item.tokenCount === void 0) continue;
13
+ const modality = item.modality.toUpperCase();
14
+ const count = item.tokenCount;
15
+ switch (modality) {
16
+ case "TEXT":
17
+ result.textTokens = (result.textTokens ?? 0) + count;
18
+ break;
19
+ case "IMAGE":
20
+ result.imageTokens = (result.imageTokens ?? 0) + count;
21
+ break;
22
+ case "AUDIO":
23
+ result.audioTokens = (result.audioTokens ?? 0) + count;
24
+ break;
25
+ case "VIDEO":
26
+ result.videoTokens = (result.videoTokens ?? 0) + count;
27
+ break;
28
+ case "DOCUMENT":
29
+ result.documentTokens = (result.documentTokens ?? 0) + count;
30
+ break;
31
+ }
32
+ }
33
+ return result;
32
34
  }
35
+ /**
36
+ * Checks if a FlattenedModalityTokens object has any values set.
37
+ */
33
38
  function hasModalityTokens(tokens) {
34
- return tokens.textTokens !== void 0 || tokens.imageTokens !== void 0 || tokens.audioTokens !== void 0 || tokens.videoTokens !== void 0 || tokens.documentTokens !== void 0;
39
+ return tokens.textTokens !== void 0 || tokens.imageTokens !== void 0 || tokens.audioTokens !== void 0 || tokens.videoTokens !== void 0 || tokens.documentTokens !== void 0;
35
40
  }
41
+ /**
42
+ * Build normalized TokenUsage from Gemini's usageMetadata.
43
+ * Handles modality breakdowns and thinking tokens. Returns `undefined` when the
44
+ * provider reported no usage metadata, so callers omit the field rather than
45
+ * fabricating zeroed totals.
46
+ */
36
47
  function buildGeminiUsage(usageMetadata) {
37
- if (!usageMetadata) return void 0;
38
- const promptTokens = usageMetadata.promptTokenCount ?? 0;
39
- const completionTokens = usageMetadata.candidatesTokenCount ?? 0;
40
- const result = buildBaseUsage({
41
- promptTokens,
42
- completionTokens,
43
- totalTokens: usageMetadata.totalTokenCount ?? promptTokens + completionTokens
44
- });
45
- const promptModalities = flattenModalityTokenCounts(
46
- usageMetadata.promptTokensDetails
47
- );
48
- const cachedTokens = usageMetadata.cachedContentTokenCount;
49
- const promptTokensDetails = {
50
- ...hasModalityTokens(promptModalities) ? promptModalities : {},
51
- ...cachedTokens !== void 0 && cachedTokens > 0 ? { cachedTokens } : {}
52
- };
53
- const completionModalities = flattenModalityTokenCounts(
54
- usageMetadata.candidatesTokensDetails
55
- );
56
- const thoughtsTokens = usageMetadata.thoughtsTokenCount;
57
- const completionTokensDetails = {
58
- ...hasModalityTokens(completionModalities) ? completionModalities : {},
59
- // Map thoughtsTokenCount to reasoningTokens for consistency with OpenAI
60
- ...thoughtsTokens !== void 0 && thoughtsTokens > 0 ? { reasoningTokens: thoughtsTokens } : {}
61
- };
62
- const providerDetails = {
63
- ...usageMetadata.trafficType ? { trafficType: usageMetadata.trafficType } : {},
64
- ...usageMetadata.toolUsePromptTokenCount !== void 0 && usageMetadata.toolUsePromptTokenCount > 0 ? { toolUsePromptTokenCount: usageMetadata.toolUsePromptTokenCount } : {},
65
- ...usageMetadata.toolUsePromptTokensDetails && usageMetadata.toolUsePromptTokensDetails.length > 0 ? {
66
- toolUsePromptTokensDetails: usageMetadata.toolUsePromptTokensDetails.map((item) => ({
67
- modality: item.modality || "UNKNOWN",
68
- tokenCount: item.tokenCount ?? 0
69
- }))
70
- } : {},
71
- ...usageMetadata.cacheTokensDetails && usageMetadata.cacheTokensDetails.length > 0 ? {
72
- cacheTokensDetails: usageMetadata.cacheTokensDetails.map((item) => ({
73
- modality: item.modality || "UNKNOWN",
74
- tokenCount: item.tokenCount ?? 0
75
- }))
76
- } : {}
77
- };
78
- if (Object.keys(promptTokensDetails).length > 0) {
79
- result.promptTokensDetails = promptTokensDetails;
80
- }
81
- if (Object.keys(providerDetails).length > 0) {
82
- result.providerUsageDetails = providerDetails;
83
- }
84
- if (Object.keys(completionTokensDetails).length > 0) {
85
- result.completionTokensDetails = completionTokensDetails;
86
- }
87
- return result;
48
+ if (!usageMetadata) return void 0;
49
+ const promptTokens = usageMetadata.promptTokenCount ?? 0;
50
+ const completionTokens = usageMetadata.candidatesTokenCount ?? 0;
51
+ const result = buildBaseUsage({
52
+ promptTokens,
53
+ completionTokens,
54
+ totalTokens: usageMetadata.totalTokenCount ?? promptTokens + completionTokens
55
+ });
56
+ const promptModalities = flattenModalityTokenCounts(usageMetadata.promptTokensDetails);
57
+ const cachedTokens = usageMetadata.cachedContentTokenCount;
58
+ const promptTokensDetails = {
59
+ ...hasModalityTokens(promptModalities) ? promptModalities : {},
60
+ ...cachedTokens !== void 0 && cachedTokens > 0 ? { cachedTokens } : {}
61
+ };
62
+ const completionModalities = flattenModalityTokenCounts(usageMetadata.candidatesTokensDetails);
63
+ const thoughtsTokens = usageMetadata.thoughtsTokenCount;
64
+ const completionTokensDetails = {
65
+ ...hasModalityTokens(completionModalities) ? completionModalities : {},
66
+ ...thoughtsTokens !== void 0 && thoughtsTokens > 0 ? { reasoningTokens: thoughtsTokens } : {}
67
+ };
68
+ const providerDetails = {
69
+ ...usageMetadata.trafficType ? { trafficType: usageMetadata.trafficType } : {},
70
+ ...usageMetadata.toolUsePromptTokenCount !== void 0 && usageMetadata.toolUsePromptTokenCount > 0 ? { toolUsePromptTokenCount: usageMetadata.toolUsePromptTokenCount } : {},
71
+ ...usageMetadata.toolUsePromptTokensDetails && usageMetadata.toolUsePromptTokensDetails.length > 0 ? { toolUsePromptTokensDetails: usageMetadata.toolUsePromptTokensDetails.map((item) => ({
72
+ modality: item.modality || "UNKNOWN",
73
+ tokenCount: item.tokenCount ?? 0
74
+ })) } : {},
75
+ ...usageMetadata.cacheTokensDetails && usageMetadata.cacheTokensDetails.length > 0 ? { cacheTokensDetails: usageMetadata.cacheTokensDetails.map((item) => ({
76
+ modality: item.modality || "UNKNOWN",
77
+ tokenCount: item.tokenCount ?? 0
78
+ })) } : {}
79
+ };
80
+ if (Object.keys(promptTokensDetails).length > 0) result.promptTokensDetails = promptTokensDetails;
81
+ if (Object.keys(providerDetails).length > 0) result.providerUsageDetails = providerDetails;
82
+ if (Object.keys(completionTokensDetails).length > 0) result.completionTokensDetails = completionTokensDetails;
83
+ return result;
88
84
  }
89
- export {
90
- buildGeminiUsage,
91
- flattenModalityTokenCounts,
92
- hasModalityTokens
93
- };
94
- //# sourceMappingURL=usage.js.map
85
+ //#endregion
86
+ export { buildGeminiUsage, flattenModalityTokenCounts, hasModalityTokens };
87
+
88
+ //# sourceMappingURL=usage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"usage.js","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"],"names":[],"mappings":";AA6BO,SAAS,2BACd,YACyB;AACzB,MAAI,CAAC,cAAc,WAAW,WAAW,GAAG;AAC1C,WAAO,CAAA;AAAA,EACT;AAEA,QAAM,SAAkC,CAAA;AAExC,aAAW,QAAQ,YAAY;AAC7B,QAAI,CAAC,KAAK,YAAY,KAAK,eAAe,QAAW;AACnD;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,SAAS,YAAA;AAC/B,UAAM,QAAQ,KAAK;AAEnB,YAAQ,UAAA;AAAA,MACN,KAAK;AACH,eAAO,cAAc,OAAO,cAAc,KAAK;AAC/C;AAAA,MACF,KAAK;AACH,eAAO,eAAe,OAAO,eAAe,KAAK;AACjD;AAAA,MACF,KAAK;AACH,eAAO,eAAe,OAAO,eAAe,KAAK;AACjD;AAAA,MACF,KAAK;AACH,eAAO,eAAe,OAAO,eAAe,KAAK;AACjD;AAAA,MACF,KAAK;AACH,eAAO,kBAAkB,OAAO,kBAAkB,KAAK;AACvD;AAAA,IAAA;AAAA,EAEN;AAEA,SAAO;AACT;AAKO,SAAS,kBAAkB,QAA0C;AAC1E,SACE,OAAO,eAAe,UACtB,OAAO,gBAAgB,UACvB,OAAO,gBAAgB,UACvB,OAAO,gBAAgB,UACvB,OAAO,mBAAmB;AAE9B;AAyCO,SAAS,iBACd,eACoD;AACpD,MAAI,CAAC,cAAe,QAAO;AAE3B,QAAM,eAAe,cAAc,oBAAoB;AACvD,QAAM,mBAAmB,cAAc,wBAAwB;AAE/D,QAAM,SAAS,eAA2C;AAAA,IACxD;AAAA,IACA;AAAA,IACA,aACE,cAAc,mBAAmB,eAAe;AAAA,EAAA,CACnD;AAID,QAAM,mBAAmB;AAAA,IACvB,cAAc;AAAA,EAAA;AAEhB,QAAM,eAAe,cAAc;AAEnC,QAAM,sBAAsB;AAAA,IAC1B,GAAI,kBAAkB,gBAAgB,IAAI,mBAAmB,CAAA;AAAA,IAC7D,GAAI,iBAAiB,UAAa,eAAe,IAAI,EAAE,aAAA,IAAiB,CAAA;AAAA,EAAC;AAK3E,QAAM,uBAAuB;AAAA,IAC3B,cAAc;AAAA,EAAA;AAEhB,QAAM,iBAAiB,cAAc;AAErC,QAAM,0BAA0B;AAAA,IAC9B,GAAI,kBAAkB,oBAAoB,IAAI,uBAAuB,CAAA;AAAA;AAAA,IAErE,GAAI,mBAAmB,UAAa,iBAAiB,IACjD,EAAE,iBAAiB,mBACnB,CAAA;AAAA,EAAC;AAIP,QAAM,kBAA8C;AAAA,IAClD,GAAI,cAAc,cACd,EAAE,aAAa,cAAc,YAAA,IAC7B,CAAA;AAAA,IACJ,GAAI,cAAc,4BAA4B,UAC9C,cAAc,0BAA0B,IACpC,EAAE,yBAAyB,cAAc,wBAAA,IACzC,CAAA;AAAA,IACJ,GAAI,cAAc,8BAClB,cAAc,2BAA2B,SAAS,IAC9C;AAAA,MACE,4BACE,cAAc,2BAA2B,IAAI,CAAC,UAAU;AAAA,QACtD,UAAU,KAAK,YAAY;AAAA,QAC3B,YAAY,KAAK,cAAc;AAAA,MAAA,EAC/B;AAAA,IAAA,IAEN,CAAA;AAAA,IACJ,GAAI,cAAc,sBAClB,cAAc,mBAAmB,SAAS,IACtC;AAAA,MACE,oBAAoB,cAAc,mBAAmB,IAAI,CAAC,UAAU;AAAA,QAClE,UAAU,KAAK,YAAY;AAAA,QAC3B,YAAY,KAAK,cAAc;AAAA,MAAA,EAC/B;AAAA,IAAA,IAEJ,CAAA;AAAA,EAAC;AAIP,MAAI,OAAO,KAAK,mBAAmB,EAAE,SAAS,GAAG;AAC/C,WAAO,sBAAsB;AAAA,EAC/B;AAEA,MAAI,OAAO,KAAK,eAAe,EAAE,SAAS,GAAG;AAC3C,WAAO,uBAAuB;AAAA,EAChC;AAEA,MAAI,OAAO,KAAK,uBAAuB,EAAE,SAAS,GAAG;AACnD,WAAO,0BAA0B;AAAA,EACnC;AAEA,SAAO;AACT;"}
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;IACH,OAAO,kBAAkB,OAAO,kBAAkB,KAAK;IACvD;EACJ;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"}
@@ -1,30 +1,37 @@
1
1
  import { GoogleGenAI } from "@google/genai";
2
- import { generateId as generateId$1, getApiKeyFromEnv } from "@tanstack/ai-utils";
2
+ import { generateId, getApiKeyFromEnv } from "@tanstack/ai-utils";
3
+ //#region src/utils/client.ts
4
+ /**
5
+ * Creates a Google Generative AI client instance
6
+ */
3
7
  function createGeminiClient(config) {
4
- return new GoogleGenAI({
5
- ...config,
6
- apiKey: config.apiKey
7
- });
8
+ return new GoogleGenAI({
9
+ ...config,
10
+ apiKey: config.apiKey
11
+ });
8
12
  }
13
+ /**
14
+ * Gets Google API key from environment variables
15
+ * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found
16
+ */
9
17
  function getGeminiApiKeyFromEnv() {
10
- try {
11
- return getApiKeyFromEnv("GOOGLE_API_KEY");
12
- } catch {
13
- try {
14
- return getApiKeyFromEnv("GEMINI_API_KEY");
15
- } catch {
16
- throw new Error(
17
- "GOOGLE_API_KEY or GEMINI_API_KEY is not set. Please set one of these environment variables or pass the API key directly."
18
- );
19
- }
20
- }
18
+ try {
19
+ return getApiKeyFromEnv("GOOGLE_API_KEY");
20
+ } catch {
21
+ try {
22
+ return getApiKeyFromEnv("GEMINI_API_KEY");
23
+ } catch {
24
+ throw new Error("GOOGLE_API_KEY or GEMINI_API_KEY is not set. Please set one of these environment variables or pass the API key directly.");
25
+ }
26
+ }
21
27
  }
22
- function generateId(prefix) {
23
- return generateId$1(prefix);
28
+ /**
29
+ * Generates a unique ID with a prefix
30
+ */
31
+ function generateId$1(prefix) {
32
+ return generateId(prefix);
24
33
  }
25
- export {
26
- createGeminiClient,
27
- generateId,
28
- getGeminiApiKeyFromEnv
29
- };
30
- //# sourceMappingURL=client.js.map
34
+ //#endregion
35
+ export { createGeminiClient, generateId$1 as generateId, getGeminiApiKeyFromEnv };
36
+
37
+ //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\nimport { generateId as _generateId, getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport type { GoogleGenAIOptions } from '@google/genai'\n\nexport interface GeminiClientConfig extends GoogleGenAIOptions {\n apiKey: string\n}\n\n/**\n * Creates a Google Generative AI client instance\n */\nexport function createGeminiClient(config: GeminiClientConfig): GoogleGenAI {\n return new GoogleGenAI({\n ...config,\n apiKey: config.apiKey,\n })\n}\n\n/**\n * Gets Google API key from environment variables\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found\n */\nexport function getGeminiApiKeyFromEnv(): string {\n try {\n return getApiKeyFromEnv('GOOGLE_API_KEY')\n } catch {\n try {\n return getApiKeyFromEnv('GEMINI_API_KEY')\n } catch {\n throw new Error(\n 'GOOGLE_API_KEY or GEMINI_API_KEY is not set. Please set one of these environment variables or pass the API key directly.',\n )\n }\n }\n}\n\n/**\n * Generates a unique ID with a prefix\n */\nexport function generateId(prefix: string): string {\n return _generateId(prefix)\n}\n"],"names":["_generateId"],"mappings":";;AAWO,SAAS,mBAAmB,QAAyC;AAC1E,SAAO,IAAI,YAAY;AAAA,IACrB,GAAG;AAAA,IACH,QAAQ,OAAO;AAAA,EAAA,CAChB;AACH;AAMO,SAAS,yBAAiC;AAC/C,MAAI;AACF,WAAO,iBAAiB,gBAAgB;AAAA,EAC1C,QAAQ;AACN,QAAI;AACF,aAAO,iBAAiB,gBAAgB;AAAA,IAC1C,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AAKO,SAAS,WAAW,QAAwB;AACjD,SAAOA,aAAY,MAAM;AAC3B;"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\nimport { generateId as _generateId, getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport type { GoogleGenAIOptions } from '@google/genai'\n\nexport interface GeminiClientConfig extends GoogleGenAIOptions {\n apiKey: string\n}\n\n/**\n * Creates a Google Generative AI client instance\n */\nexport function createGeminiClient(config: GeminiClientConfig): GoogleGenAI {\n return new GoogleGenAI({\n ...config,\n apiKey: config.apiKey,\n })\n}\n\n/**\n * Gets Google API key from environment variables\n * @throws Error if GOOGLE_API_KEY or GEMINI_API_KEY is not found\n */\nexport function getGeminiApiKeyFromEnv(): string {\n try {\n return getApiKeyFromEnv('GOOGLE_API_KEY')\n } catch {\n try {\n return getApiKeyFromEnv('GEMINI_API_KEY')\n } catch {\n throw new Error(\n 'GOOGLE_API_KEY or GEMINI_API_KEY is not set. Please set one of these environment variables or pass the API key directly.',\n )\n }\n }\n}\n\n/**\n * Generates a unique ID with a prefix\n */\nexport function generateId(prefix: string): string {\n return _generateId(prefix)\n}\n"],"mappings":";;;;;;AAWA,SAAgB,mBAAmB,QAAyC;CAC1E,OAAO,IAAI,YAAY;EACrB,GAAG;EACH,QAAQ,OAAO;CACjB,CAAC;AACH;;;;;AAMA,SAAgB,yBAAiC;CAC/C,IAAI;EACF,OAAO,iBAAiB,gBAAgB;CAC1C,QAAQ;EACN,IAAI;GACF,OAAO,iBAAiB,gBAAgB;EAC1C,QAAQ;GACN,MAAM,IAAI,MACR,0HACF;EACF;CACF;AACF;;;;AAKA,SAAgB,aAAW,QAAwB;CACjD,OAAO,WAAY,MAAM;AAC3B"}
@@ -0,0 +1,2 @@
1
+ import { createGeminiClient, generateId, getGeminiApiKeyFromEnv } from "./client.js";
2
+ export { createGeminiClient, generateId, getGeminiApiKeyFromEnv };
@@ -1,26 +1,79 @@
1
1
  import { GEMINI_INTERACTIONS_VIDEO_MODELS } from "../model-meta.js";
2
+ //#region src/video/video-provider-options.ts
3
+ /**
4
+ * Gemini Video Generation Provider Options
5
+ *
6
+ * Covers two request paths behind the one video adapter:
7
+ * - Veo models — long-running operations via `:predictLongRunning`
8
+ * (https://ai.google.dev/gemini-api/docs/video)
9
+ * - Gemini Omni Flash — background jobs via the Interactions API
10
+ * (https://ai.google.dev/gemini-api/docs/omni)
11
+ *
12
+ * @experimental Video generation is an experimental feature and may change.
13
+ */
14
+ /**
15
+ * Runtime guard for the Interactions-served video models.
16
+ * @experimental Omni video generation is an experimental feature and may change.
17
+ */
2
18
  function isInteractionsVideoModel(model) {
3
- return GEMINI_INTERACTIONS_VIDEO_MODELS.includes(
4
- model
5
- );
19
+ return GEMINI_INTERACTIONS_VIDEO_MODELS.includes(model);
6
20
  }
7
- const GEMINI_VIDEO_DURATIONS = {
8
- "veo-3.1-generate-preview": { kind: "discrete", values: [4, 6, 8] },
9
- "veo-3.1-fast-generate-preview": { kind: "discrete", values: [4, 6, 8] },
10
- "veo-3.1-lite-generate-preview": { kind: "discrete", values: [4, 6, 8] },
11
- "gemini-omni-flash-preview": {
12
- kind: "range",
13
- min: 3,
14
- max: 10,
15
- unit: "seconds"
16
- }
21
+ /**
22
+ * Runtime duration table backing `availableDurations()` / `snapDuration()`.
23
+ *
24
+ * Veo values are curated from the official docs
25
+ * (https://ai.google.dev/gemini-api/docs/video) — the Gemini OpenAPI spec
26
+ * types the `:predictLongRunning` request's `parameters` as unconstrained,
27
+ * so it carries no per-model duration information to derive these from.
28
+ * Omni Flash's 3–10s range was verified against the live API
29
+ * (2026-07-02): `response_format.duration` takes a `"<seconds>s"` string,
30
+ * fractional values are accepted, out-of-range values are rejected with
31
+ * "minimum allowed 3s" / "maximum allowed 10s", and omitting it defaults
32
+ * to a 10-second clip.
33
+ *
34
+ * @experimental Video generation is an experimental feature and may change.
35
+ */
36
+ var GEMINI_VIDEO_DURATIONS = {
37
+ "veo-3.1-generate-preview": {
38
+ kind: "discrete",
39
+ values: [
40
+ 4,
41
+ 6,
42
+ 8
43
+ ]
44
+ },
45
+ "veo-3.1-fast-generate-preview": {
46
+ kind: "discrete",
47
+ values: [
48
+ 4,
49
+ 6,
50
+ 8
51
+ ]
52
+ },
53
+ "veo-3.1-lite-generate-preview": {
54
+ kind: "discrete",
55
+ values: [
56
+ 4,
57
+ 6,
58
+ 8
59
+ ]
60
+ },
61
+ "gemini-omni-flash-preview": {
62
+ kind: "range",
63
+ min: 3,
64
+ max: 10,
65
+ unit: "seconds"
66
+ }
17
67
  };
68
+ /**
69
+ * Look up the duration options for a Gemini video model.
70
+ *
71
+ * @experimental Video generation is an experimental feature and may change.
72
+ */
18
73
  function getGeminiVideoDurationOptions(model) {
19
- return GEMINI_VIDEO_DURATIONS[model];
74
+ return GEMINI_VIDEO_DURATIONS[model];
20
75
  }
21
- export {
22
- GEMINI_VIDEO_DURATIONS,
23
- getGeminiVideoDurationOptions,
24
- isInteractionsVideoModel
25
- };
26
- //# sourceMappingURL=video-provider-options.js.map
76
+ //#endregion
77
+ export { GEMINI_VIDEO_DURATIONS, getGeminiVideoDurationOptions, isInteractionsVideoModel };
78
+
79
+ //# sourceMappingURL=video-provider-options.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"video-provider-options.js","sources":["../../../src/video/video-provider-options.ts"],"sourcesContent":["/**\n * Gemini Video Generation Provider Options\n *\n * Covers two request paths behind the one video adapter:\n * - Veo models — long-running operations via `:predictLongRunning`\n * (https://ai.google.dev/gemini-api/docs/video)\n * - Gemini Omni Flash — background jobs via the Interactions API\n * (https://ai.google.dev/gemini-api/docs/omni)\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nimport { GEMINI_INTERACTIONS_VIDEO_MODELS } from '../model-meta'\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type { GenerateVideosConfig, Interactions } from '@google/genai'\nimport type { GEMINI_VIDEO_MODELS } from '../model-meta'\n\n/**\n * Model type for Gemini video generation (Veo + Omni Flash).\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModel = (typeof GEMINI_VIDEO_MODELS)[number]\n\n/**\n * Video models served by the Interactions API (Gemini Omni Flash) rather\n * than Veo's `:predictLongRunning` operations flow.\n * @experimental Omni video generation is an experimental feature and may change.\n */\nexport type GeminiInteractionsVideoModel =\n (typeof GEMINI_INTERACTIONS_VIDEO_MODELS)[number]\n\n/**\n * Runtime guard for the Interactions-served video models.\n * @experimental Omni video generation is an experimental feature and may change.\n */\nexport function isInteractionsVideoModel(\n model: GeminiVideoModel,\n): model is GeminiInteractionsVideoModel {\n return (GEMINI_INTERACTIONS_VIDEO_MODELS as ReadonlyArray<string>).includes(\n model,\n )\n}\n\n/**\n * Supported aspect ratios for Gemini video generation. This is the `size`\n * value for the Gemini video adapter — both Veo and Omni Flash express\n * output shape as an aspect ratio (plus an optional `resolution` in Veo's\n * `modelOptions`), not pixel dimensions.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoSize = '16:9' | '9:16'\n\n/**\n * Provider-specific options for Gemini Veo video generation.\n *\n * Derived from the SDK's `GenerateVideosConfig`, minus the fields the\n * adapter manages itself:\n * - `durationSeconds` — set via the typed top-level `duration` option\n * (use `adapter.snapDuration(seconds)` to coerce raw seconds)\n * - `aspectRatio` — set via the top-level `size` option\n * - `lastFrame` / `referenceImages` — set via image parts in the `prompt`\n * with `metadata.role: 'end_frame'` / `'reference'`\n * - `httpOptions` / `abortSignal` — client-level transport concerns\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoProviderOptions = Omit<\n GenerateVideosConfig,\n | 'durationSeconds'\n | 'aspectRatio'\n | 'lastFrame'\n | 'referenceImages'\n | 'httpOptions'\n | 'abortSignal'\n>\n\n/**\n * Provider-specific options for Gemini Omni Flash video generation on the\n * Interactions API.\n *\n * Derived from the SDK's `Interactions.CreateModelInteractionParamsNonStreaming`,\n * minus the fields the adapter manages itself:\n * - `model` / `input` — set from the adapter's model and the `prompt`\n * - `stream` / `background` — the adapter always creates a background job\n * and polls it through the `generateVideo` jobs API\n * - `response_modalities` / `response_format` — the adapter requests video\n * output and maps the top-level `size` option onto\n * `response_format.aspect_ratio`\n * - `tools` / `response_mime_type` — not applicable to video generation\n *\n * Notable passthroughs:\n * - `previous_interaction_id` — conversational video editing: chain a new\n * prompt onto a prior Omni interaction to refine its video\n * - `generation_config.video_config.task` — pin the task mode\n * (`'text_to_video' | 'image_to_video' | 'reference_to_video' | 'edit'`)\n * instead of letting the model infer it\n *\n * @experimental Omni video generation is an experimental feature and may change.\n */\nexport type GeminiOmniVideoProviderOptions = Omit<\n Interactions.CreateModelInteractionParamsNonStreaming,\n | 'model'\n | 'input'\n | 'stream'\n | 'background'\n | 'response_modalities'\n | 'response_format'\n | 'response_mime_type'\n | 'tools'\n>\n\n/**\n * Model-specific provider options mapping.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelProviderOptionsByName = {\n [TModel in GeminiVideoModel]: TModel extends GeminiInteractionsVideoModel\n ? GeminiOmniVideoProviderOptions\n : GeminiVideoProviderOptions\n}\n\n/**\n * Model-specific size (aspect ratio) mapping.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelSizeByName = {\n [TModel in GeminiVideoModel]: GeminiVideoSize\n}\n\n/**\n * Per-model prompt input modalities. Every Veo model accepts image\n * conditioning inputs (first frame, last frame, reference images) alongside\n * the text prompt. Omni Flash additionally accepts video inputs (short\n * reference clips / videos to edit).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelInputModalitiesByName = {\n [TModel in GeminiVideoModel]: TModel extends GeminiInteractionsVideoModel\n ? readonly ['image', 'video']\n : readonly ['image']\n}\n\n/**\n * Per-model duration unions (seconds, as numbers — Veo's\n * `parameters.durationSeconds` field is numeric; Omni Flash accepts a\n * continuous 3–10 second range, fractional seconds included, so it stays\n * `number` — the adapter rejects out-of-range values at job creation,\n * against the range entry below).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelDurationByName = {\n 'veo-3.1-generate-preview': 4 | 6 | 8\n 'veo-3.1-fast-generate-preview': 4 | 6 | 8\n 'veo-3.1-lite-generate-preview': 4 | 6 | 8\n 'gemini-omni-flash-preview': number\n}\n\n/**\n * Runtime duration table backing `availableDurations()` / `snapDuration()`.\n *\n * Veo values are curated from the official docs\n * (https://ai.google.dev/gemini-api/docs/video) — the Gemini OpenAPI spec\n * types the `:predictLongRunning` request's `parameters` as unconstrained,\n * so it carries no per-model duration information to derive these from.\n * Omni Flash's 3–10s range was verified against the live API\n * (2026-07-02): `response_format.duration` takes a `\"<seconds>s\"` string,\n * fractional values are accepted, out-of-range values are rejected with\n * \"minimum allowed 3s\" / \"maximum allowed 10s\", and omitting it defaults\n * to a 10-second clip.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport const GEMINI_VIDEO_DURATIONS: {\n readonly [TModel in GeminiVideoModel]: DurationOptions<\n GeminiVideoModelDurationByName[TModel]\n >\n} = {\n 'veo-3.1-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n 'veo-3.1-fast-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n 'veo-3.1-lite-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n 'gemini-omni-flash-preview': {\n kind: 'range',\n min: 3,\n max: 10,\n unit: 'seconds',\n },\n}\n\n/**\n * Look up the duration options for a Gemini video model.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function getGeminiVideoDurationOptions<TModel extends GeminiVideoModel>(\n model: TModel,\n): DurationOptions<GeminiVideoModelDurationByName[TModel]> {\n return GEMINI_VIDEO_DURATIONS[model]\n}\n"],"names":[],"mappings":";AAkCO,SAAS,yBACd,OACuC;AACvC,SAAQ,iCAA2D;AAAA,IACjE;AAAA,EAAA;AAEJ;AAwIO,MAAM,yBAIT;AAAA,EACF,4BAA4B,EAAE,MAAM,YAAY,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAA;AAAA,EAChE,iCAAiC,EAAE,MAAM,YAAY,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAA;AAAA,EACrE,iCAAiC,EAAE,MAAM,YAAY,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAA;AAAA,EACrE,6BAA6B;AAAA,IAC3B,MAAM;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,EAAA;AAEV;AAOO,SAAS,8BACd,OACyD;AACzD,SAAO,uBAAuB,KAAK;AACrC;"}
1
+ {"version":3,"file":"video-provider-options.js","names":[],"sources":["../../../src/video/video-provider-options.ts"],"sourcesContent":["/**\n * Gemini Video Generation Provider Options\n *\n * Covers two request paths behind the one video adapter:\n * - Veo models — long-running operations via `:predictLongRunning`\n * (https://ai.google.dev/gemini-api/docs/video)\n * - Gemini Omni Flash — background jobs via the Interactions API\n * (https://ai.google.dev/gemini-api/docs/omni)\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nimport { GEMINI_INTERACTIONS_VIDEO_MODELS } from '../model-meta'\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type { GenerateVideosConfig, Interactions } from '@google/genai'\nimport type { GEMINI_VIDEO_MODELS } from '../model-meta'\n\n/**\n * Model type for Gemini video generation (Veo + Omni Flash).\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModel = (typeof GEMINI_VIDEO_MODELS)[number]\n\n/**\n * Video models served by the Interactions API (Gemini Omni Flash) rather\n * than Veo's `:predictLongRunning` operations flow.\n * @experimental Omni video generation is an experimental feature and may change.\n */\nexport type GeminiInteractionsVideoModel =\n (typeof GEMINI_INTERACTIONS_VIDEO_MODELS)[number]\n\n/**\n * Runtime guard for the Interactions-served video models.\n * @experimental Omni video generation is an experimental feature and may change.\n */\nexport function isInteractionsVideoModel(\n model: GeminiVideoModel,\n): model is GeminiInteractionsVideoModel {\n return (GEMINI_INTERACTIONS_VIDEO_MODELS as ReadonlyArray<string>).includes(\n model,\n )\n}\n\n/**\n * Supported aspect ratios for Gemini video generation. This is the `size`\n * value for the Gemini video adapter — both Veo and Omni Flash express\n * output shape as an aspect ratio (plus an optional `resolution` in Veo's\n * `modelOptions`), not pixel dimensions.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoSize = '16:9' | '9:16'\n\n/**\n * Provider-specific options for Gemini Veo video generation.\n *\n * Derived from the SDK's `GenerateVideosConfig`, minus the fields the\n * adapter manages itself:\n * - `durationSeconds` — set via the typed top-level `duration` option\n * (use `adapter.snapDuration(seconds)` to coerce raw seconds)\n * - `aspectRatio` — set via the top-level `size` option\n * - `lastFrame` / `referenceImages` — set via image parts in the `prompt`\n * with `metadata.role: 'end_frame'` / `'reference'`\n * - `httpOptions` / `abortSignal` — client-level transport concerns\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoProviderOptions = Omit<\n GenerateVideosConfig,\n | 'durationSeconds'\n | 'aspectRatio'\n | 'lastFrame'\n | 'referenceImages'\n | 'httpOptions'\n | 'abortSignal'\n>\n\n/**\n * Provider-specific options for Gemini Omni Flash video generation on the\n * Interactions API.\n *\n * Derived from the SDK's `Interactions.CreateModelInteractionParamsNonStreaming`,\n * minus the fields the adapter manages itself:\n * - `model` / `input` — set from the adapter's model and the `prompt`\n * - `stream` / `background` — the adapter always creates a background job\n * and polls it through the `generateVideo` jobs API\n * - `response_modalities` / `response_format` — the adapter requests video\n * output and maps the top-level `size` option onto\n * `response_format.aspect_ratio`\n * - `tools` / `response_mime_type` — not applicable to video generation\n *\n * Notable passthroughs:\n * - `previous_interaction_id` — conversational video editing: chain a new\n * prompt onto a prior Omni interaction to refine its video\n * - `generation_config.video_config.task` — pin the task mode\n * (`'text_to_video' | 'image_to_video' | 'reference_to_video' | 'edit'`)\n * instead of letting the model infer it\n *\n * @experimental Omni video generation is an experimental feature and may change.\n */\nexport type GeminiOmniVideoProviderOptions = Omit<\n Interactions.CreateModelInteractionParamsNonStreaming,\n | 'model'\n | 'input'\n | 'stream'\n | 'background'\n | 'response_modalities'\n | 'response_format'\n | 'response_mime_type'\n | 'tools'\n>\n\n/**\n * Model-specific provider options mapping.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelProviderOptionsByName = {\n [TModel in GeminiVideoModel]: TModel extends GeminiInteractionsVideoModel\n ? GeminiOmniVideoProviderOptions\n : GeminiVideoProviderOptions\n}\n\n/**\n * Model-specific size (aspect ratio) mapping.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelSizeByName = {\n [TModel in GeminiVideoModel]: GeminiVideoSize\n}\n\n/**\n * Per-model prompt input modalities. Every Veo model accepts image\n * conditioning inputs (first frame, last frame, reference images) alongside\n * the text prompt. Omni Flash additionally accepts video inputs (short\n * reference clips / videos to edit).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelInputModalitiesByName = {\n [TModel in GeminiVideoModel]: TModel extends GeminiInteractionsVideoModel\n ? readonly ['image', 'video']\n : readonly ['image']\n}\n\n/**\n * Per-model duration unions (seconds, as numbers — Veo's\n * `parameters.durationSeconds` field is numeric; Omni Flash accepts a\n * continuous 3–10 second range, fractional seconds included, so it stays\n * `number` — the adapter rejects out-of-range values at job creation,\n * against the range entry below).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type GeminiVideoModelDurationByName = {\n 'veo-3.1-generate-preview': 4 | 6 | 8\n 'veo-3.1-fast-generate-preview': 4 | 6 | 8\n 'veo-3.1-lite-generate-preview': 4 | 6 | 8\n 'gemini-omni-flash-preview': number\n}\n\n/**\n * Runtime duration table backing `availableDurations()` / `snapDuration()`.\n *\n * Veo values are curated from the official docs\n * (https://ai.google.dev/gemini-api/docs/video) — the Gemini OpenAPI spec\n * types the `:predictLongRunning` request's `parameters` as unconstrained,\n * so it carries no per-model duration information to derive these from.\n * Omni Flash's 3–10s range was verified against the live API\n * (2026-07-02): `response_format.duration` takes a `\"<seconds>s\"` string,\n * fractional values are accepted, out-of-range values are rejected with\n * \"minimum allowed 3s\" / \"maximum allowed 10s\", and omitting it defaults\n * to a 10-second clip.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport const GEMINI_VIDEO_DURATIONS: {\n readonly [TModel in GeminiVideoModel]: DurationOptions<\n GeminiVideoModelDurationByName[TModel]\n >\n} = {\n 'veo-3.1-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n 'veo-3.1-fast-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n 'veo-3.1-lite-generate-preview': { kind: 'discrete', values: [4, 6, 8] },\n 'gemini-omni-flash-preview': {\n kind: 'range',\n min: 3,\n max: 10,\n unit: 'seconds',\n },\n}\n\n/**\n * Look up the duration options for a Gemini video model.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function getGeminiVideoDurationOptions<TModel extends GeminiVideoModel>(\n model: TModel,\n): DurationOptions<GeminiVideoModelDurationByName[TModel]> {\n return GEMINI_VIDEO_DURATIONS[model]\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkCA,SAAgB,yBACd,OACuC;CACvC,OAAQ,iCAA2D,SACjE,KACF;AACF;;;;;;;;;;;;;;;;AAwIA,IAAa,yBAIT;CACF,4BAA4B;EAAE,MAAM;EAAY,QAAQ;GAAC;GAAG;GAAG;EAAC;CAAE;CAClE,iCAAiC;EAAE,MAAM;EAAY,QAAQ;GAAC;GAAG;GAAG;EAAC;CAAE;CACvE,iCAAiC;EAAE,MAAM;EAAY,QAAQ;GAAC;GAAG;GAAG;EAAC;CAAE;CACvE,6BAA6B;EAC3B,MAAM;EACN,KAAK;EACL,KAAK;EACL,MAAM;CACR;AACF;;;;;;AAOA,SAAgB,8BACd,OACyD;CACzD,OAAO,uBAAuB;AAChC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-gemini",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
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",
@@ -56,23 +56,23 @@
56
56
  "dependencies": {
57
57
  "@google/genai": "^2.10.0",
58
58
  "partial-json": "^0.1.7",
59
- "@tanstack/ai-utils": "0.3.1"
59
+ "@tanstack/ai-utils": "0.4.0"
60
60
  },
61
61
  "peerDependencies": {
62
- "@tanstack/ai": "^0.41.0"
62
+ "@tanstack/ai": "^0.43.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@vitest/coverage-v8": "4.0.14",
66
- "vite": "^7.3.3",
66
+ "vite": "^8.1.4",
67
67
  "zod": "^4.2.0",
68
- "@tanstack/ai": "0.41.0"
68
+ "@tanstack/ai": "0.43.0"
69
69
  },
70
70
  "scripts": {
71
71
  "build": "vite build",
72
72
  "clean": "premove ./build ./dist",
73
- "lint:fix": "eslint ./src --fix",
73
+ "lint:fix": "oxlint src --type-aware --fix",
74
74
  "test:build": "publint --strict",
75
- "test:eslint": "eslint ./src",
75
+ "test:oxlint": "oxlint src --type-aware",
76
76
  "test:lib": "vitest",
77
77
  "test:lib:dev": "pnpm test:lib --watch",
78
78
  "test:types": "tsc"