@tanstack/ai-gemini 0.32.1 → 0.33.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/dist/esm/adapters/files.d.ts +27 -0
- package/dist/esm/adapters/files.js +62 -0
- package/dist/esm/adapters/files.js.map +1 -0
- package/dist/esm/adapters/image.d.ts +1 -0
- package/dist/esm/adapters/image.js +3 -2
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/text.d.ts +1 -0
- package/dist/esm/adapters/text.js +10 -4
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/video.d.ts +1 -0
- package/dist/esm/adapters/video.js +8 -5
- package/dist/esm/adapters/video.js.map +1 -1
- package/dist/esm/experimental/text-interactions/adapter.d.ts +1 -0
- package/dist/esm/experimental/text-interactions/adapter.js +11 -9
- package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -1
- package/package.json +3 -3
- package/src/adapters/files.ts +84 -0
- package/src/adapters/image.ts +13 -3
- package/src/adapters/text.ts +25 -5
- package/src/adapters/video.ts +27 -5
- package/src/experimental/text-interactions/adapter.ts +18 -9
- package/src/index.ts +8 -0
package/src/adapters/text.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { FinishReason } from '@google/genai'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
EventType,
|
|
4
|
+
fileReferenceFor,
|
|
5
|
+
isFileSource,
|
|
6
|
+
normalizeSystemPrompts,
|
|
7
|
+
} from '@tanstack/ai'
|
|
3
8
|
import { toRunErrorRawEvent } from '@tanstack/ai/adapter-internals'
|
|
4
9
|
import { BaseTextAdapter } from '@tanstack/ai/adapters'
|
|
5
10
|
import { convertToolsToProviderFormat } from '../tools/tool-converter'
|
|
@@ -107,8 +112,11 @@ function contentPartToInteraction(part: ContentPart): InteractionContent {
|
|
|
107
112
|
source.type === 'data'
|
|
108
113
|
? source.mimeType
|
|
109
114
|
: (source.mimeType ?? DEFAULT_MEDIA_MIME_TYPES[part.type])
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
// A Gemini Files API handle maps to the `uri` field, same as a public URL;
|
|
116
|
+
// `fileReferenceFor` throws when another provider issued it.
|
|
117
|
+
const base = isFileSource(source)
|
|
118
|
+
? { uri: fileReferenceFor(source, 'gemini'), mime_type: mimeType }
|
|
119
|
+
: source.type === 'data'
|
|
112
120
|
? { data: source.value, mime_type: mimeType }
|
|
113
121
|
: { uri: source.value, mime_type: mimeType }
|
|
114
122
|
|
|
@@ -216,6 +224,8 @@ export class GeminiTextAdapter<
|
|
|
216
224
|
> {
|
|
217
225
|
override readonly kind = 'text' as const
|
|
218
226
|
readonly name = 'gemini' as const
|
|
227
|
+
// Consumes Gemini Files API references (geminiFiles()) as fileData.fileUri.
|
|
228
|
+
override readonly supportsFileSources = true
|
|
219
229
|
|
|
220
230
|
private readonly client: GoogleGenAI
|
|
221
231
|
|
|
@@ -920,6 +930,13 @@ export class GeminiTextAdapter<
|
|
|
920
930
|
case 'audio':
|
|
921
931
|
case 'video':
|
|
922
932
|
case 'document': {
|
|
933
|
+
// File references (Gemini Files API) and public URLs both pass
|
|
934
|
+
// through as `fileData`; Gemini fetches the URI server-side. A
|
|
935
|
+
// file source's handle is the file URI (throws when another provider
|
|
936
|
+
// issued it).
|
|
937
|
+
const fileUri = isFileSource(part.source)
|
|
938
|
+
? fileReferenceFor(part.source, this.name)
|
|
939
|
+
: part.source.value
|
|
923
940
|
const geminiPart: Part =
|
|
924
941
|
part.source.type === 'data'
|
|
925
942
|
? {
|
|
@@ -930,7 +947,7 @@ export class GeminiTextAdapter<
|
|
|
930
947
|
}
|
|
931
948
|
: {
|
|
932
949
|
fileData: {
|
|
933
|
-
fileUri
|
|
950
|
+
fileUri,
|
|
934
951
|
// For URL sources, use provided mimeType or fall back to
|
|
935
952
|
// reasonable defaults.
|
|
936
953
|
mimeType:
|
|
@@ -1045,6 +1062,9 @@ export class GeminiTextAdapter<
|
|
|
1045
1062
|
},
|
|
1046
1063
|
})
|
|
1047
1064
|
} else {
|
|
1065
|
+
const fileUri = isFileSource(part.source)
|
|
1066
|
+
? fileReferenceFor(part.source, this.name)
|
|
1067
|
+
: part.source.value
|
|
1048
1068
|
const defaultMimeType = {
|
|
1049
1069
|
image: 'image/jpeg',
|
|
1050
1070
|
audio: 'audio/mp3',
|
|
@@ -1053,7 +1073,7 @@ export class GeminiTextAdapter<
|
|
|
1053
1073
|
}[part.type]
|
|
1054
1074
|
mediaParts.push({
|
|
1055
1075
|
fileData: {
|
|
1056
|
-
fileUri
|
|
1076
|
+
fileUri,
|
|
1057
1077
|
mimeType: part.source.mimeType ?? defaultMimeType,
|
|
1058
1078
|
},
|
|
1059
1079
|
})
|
package/src/adapters/video.ts
CHANGED
|
@@ -2,7 +2,12 @@ import {
|
|
|
2
2
|
GenerateVideosOperation,
|
|
3
3
|
VideoGenerationReferenceType,
|
|
4
4
|
} from '@google/genai'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
fileReferenceFor,
|
|
7
|
+
isFileSource,
|
|
8
|
+
resolveMediaPrompt,
|
|
9
|
+
unsupportedFileSourceError,
|
|
10
|
+
} from '@tanstack/ai'
|
|
6
11
|
import { BaseVideoAdapter, snapToDurationOption } from '@tanstack/ai/adapters'
|
|
7
12
|
import { arrayBufferToBase64 } from '@tanstack/ai-utils'
|
|
8
13
|
import { createGeminiClient, getGeminiApiKeyFromEnv } from '../utils'
|
|
@@ -93,6 +98,14 @@ async function imagePartToVeoImage(
|
|
|
93
98
|
mimeType: part.source.mimeType || 'image/png',
|
|
94
99
|
}
|
|
95
100
|
}
|
|
101
|
+
if (isFileSource(part.source)) {
|
|
102
|
+
// Veo's predict API accepts only inline bytes or a gs:// reference — there's
|
|
103
|
+
// no way to reference a Files API handle here.
|
|
104
|
+
throw unsupportedFileSourceError(
|
|
105
|
+
'gemini',
|
|
106
|
+
'for Veo video generation, which needs inline image bytes or a gs:// reference — pass a data: URI or gs:// URL',
|
|
107
|
+
)
|
|
108
|
+
}
|
|
96
109
|
const url = part.source.value
|
|
97
110
|
if (url.startsWith('gs://')) {
|
|
98
111
|
return {
|
|
@@ -143,15 +156,21 @@ async function imagePartToVeoImage(
|
|
|
143
156
|
function mediaPartToInteractionsContent(
|
|
144
157
|
part: ImagePart<MediaInputMetadata> | VideoPart<MediaInputMetadata>,
|
|
145
158
|
): InteractionContent {
|
|
159
|
+
// A Gemini Files API reference maps to the `uri` field, same as a public
|
|
160
|
+
// URL (mirrors the Interactions text adapter). `fileReferenceFor` throws
|
|
161
|
+
// when another provider issued the handle.
|
|
162
|
+
const sourceValue = isFileSource(part.source)
|
|
163
|
+
? fileReferenceFor(part.source, 'gemini')
|
|
164
|
+
: part.source.value
|
|
146
165
|
const mimeType = part.source.mimeType
|
|
147
166
|
if (part.type === 'image') {
|
|
148
167
|
return part.source.type === 'data'
|
|
149
|
-
? { type: 'image', data:
|
|
150
|
-
: { type: 'image', uri:
|
|
168
|
+
? { type: 'image', data: sourceValue, mime_type: mimeType }
|
|
169
|
+
: { type: 'image', uri: sourceValue, mime_type: mimeType }
|
|
151
170
|
}
|
|
152
171
|
return part.source.type === 'data'
|
|
153
|
-
? { type: 'video', data:
|
|
154
|
-
: { type: 'video', uri:
|
|
172
|
+
? { type: 'video', data: sourceValue, mime_type: mimeType }
|
|
173
|
+
: { type: 'video', uri: sourceValue, mime_type: mimeType }
|
|
155
174
|
}
|
|
156
175
|
|
|
157
176
|
/**
|
|
@@ -255,6 +274,9 @@ export class GeminiVideoAdapter<
|
|
|
255
274
|
GeminiVideoModelDurationByName
|
|
256
275
|
> {
|
|
257
276
|
readonly name = 'gemini' as const
|
|
277
|
+
// The Interactions path consumes Gemini Files API references as content
|
|
278
|
+
// `uri`s; the Veo path still rejects them (raw bytes / gs:// only).
|
|
279
|
+
override readonly supportsFileSources = true
|
|
258
280
|
|
|
259
281
|
protected client: GoogleGenAI
|
|
260
282
|
private readonly allowUrlFetch: boolean
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventType } from '@tanstack/ai'
|
|
1
|
+
import { EventType, fileReferenceFor, isFileSource } from '@tanstack/ai'
|
|
2
2
|
import { BaseTextAdapter } from '@tanstack/ai/adapters'
|
|
3
3
|
import { parse as parsePartialJSON } from 'partial-json'
|
|
4
4
|
import {
|
|
@@ -200,6 +200,8 @@ export class GeminiTextInteractionsAdapter<
|
|
|
200
200
|
> {
|
|
201
201
|
override readonly kind = 'text' as const
|
|
202
202
|
override readonly name = 'gemini-text-interactions' as const
|
|
203
|
+
// Consumes Gemini Files API references (geminiFiles()) as content `uri`s.
|
|
204
|
+
override readonly supportsFileSources = true
|
|
203
205
|
|
|
204
206
|
private readonly client: GoogleGenAI
|
|
205
207
|
// Tracks the most recent server-assigned interaction id per threadId
|
|
@@ -911,6 +913,13 @@ function contentPartToBlock(part: ContentPart): ContentBlock {
|
|
|
911
913
|
if (part.type === 'text') {
|
|
912
914
|
return { type: 'text', text: part.content }
|
|
913
915
|
}
|
|
916
|
+
// A Gemini Files API handle maps to the `uri` field (isData stays false),
|
|
917
|
+
// same as a public URL. `fileReferenceFor` checks the issuer against
|
|
918
|
+
// 'gemini', the name geminiFiles() stamps on its handles, not this
|
|
919
|
+
// adapter's own name.
|
|
920
|
+
const sourceValue = isFileSource(part.source)
|
|
921
|
+
? fileReferenceFor(part.source, 'gemini')
|
|
922
|
+
: part.source.value
|
|
914
923
|
const isData = part.source.type === 'data'
|
|
915
924
|
switch (part.type) {
|
|
916
925
|
case 'image': {
|
|
@@ -920,8 +929,8 @@ function contentPartToBlock(part: ContentPart): ContentBlock {
|
|
|
920
929
|
'image',
|
|
921
930
|
)
|
|
922
931
|
return isData
|
|
923
|
-
? { type: 'image', data:
|
|
924
|
-
: { type: 'image', uri:
|
|
932
|
+
? { type: 'image', data: sourceValue, mime_type }
|
|
933
|
+
: { type: 'image', uri: sourceValue, mime_type }
|
|
925
934
|
}
|
|
926
935
|
case 'audio': {
|
|
927
936
|
const mime_type = validateMime(
|
|
@@ -930,8 +939,8 @@ function contentPartToBlock(part: ContentPart): ContentBlock {
|
|
|
930
939
|
'audio',
|
|
931
940
|
)
|
|
932
941
|
return isData
|
|
933
|
-
? { type: 'audio', data:
|
|
934
|
-
: { type: 'audio', uri:
|
|
942
|
+
? { type: 'audio', data: sourceValue, mime_type }
|
|
943
|
+
: { type: 'audio', uri: sourceValue, mime_type }
|
|
935
944
|
}
|
|
936
945
|
case 'video': {
|
|
937
946
|
const mime_type = validateMime(
|
|
@@ -940,8 +949,8 @@ function contentPartToBlock(part: ContentPart): ContentBlock {
|
|
|
940
949
|
'video',
|
|
941
950
|
)
|
|
942
951
|
return isData
|
|
943
|
-
? { type: 'video', data:
|
|
944
|
-
: { type: 'video', uri:
|
|
952
|
+
? { type: 'video', data: sourceValue, mime_type }
|
|
953
|
+
: { type: 'video', uri: sourceValue, mime_type }
|
|
945
954
|
}
|
|
946
955
|
case 'document': {
|
|
947
956
|
const mime_type = validateMime(
|
|
@@ -950,8 +959,8 @@ function contentPartToBlock(part: ContentPart): ContentBlock {
|
|
|
950
959
|
'document',
|
|
951
960
|
)
|
|
952
961
|
return isData
|
|
953
|
-
? { type: 'document', data:
|
|
954
|
-
: { type: 'document', uri:
|
|
962
|
+
? { type: 'document', data: sourceValue, mime_type }
|
|
963
|
+
: { type: 'document', uri: sourceValue, mime_type }
|
|
955
964
|
}
|
|
956
965
|
}
|
|
957
966
|
}
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,14 @@ export {
|
|
|
19
19
|
type GeminiSummarizeModel,
|
|
20
20
|
} from './adapters/summarize'
|
|
21
21
|
|
|
22
|
+
// Files adapter - upload media to the Gemini Files API and reference by file URI
|
|
23
|
+
export {
|
|
24
|
+
GeminiFilesAdapter,
|
|
25
|
+
createGeminiFiles,
|
|
26
|
+
geminiFiles,
|
|
27
|
+
type GeminiFilesConfig,
|
|
28
|
+
} from './adapters/files'
|
|
29
|
+
|
|
22
30
|
// Image adapter
|
|
23
31
|
export {
|
|
24
32
|
GeminiImageAdapter,
|