@ai-sdk/google 4.0.0-beta.21 → 4.0.0-beta.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/google",
3
- "version": "4.0.0-beta.21",
3
+ "version": "4.0.0-beta.23",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -36,8 +36,8 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "@ai-sdk/provider": "4.0.0-beta.5",
40
- "@ai-sdk/provider-utils": "5.0.0-beta.9"
39
+ "@ai-sdk/provider": "4.0.0-beta.6",
40
+ "@ai-sdk/provider-utils": "5.0.0-beta.10"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -2,7 +2,11 @@ import {
2
2
  LanguageModelV4Prompt,
3
3
  UnsupportedFunctionalityError,
4
4
  } from '@ai-sdk/provider';
5
- import { convertToBase64 } from '@ai-sdk/provider-utils';
5
+ import {
6
+ convertToBase64,
7
+ isProviderReference,
8
+ resolveProviderReference,
9
+ } from '@ai-sdk/provider-utils';
6
10
  import {
7
11
  GoogleGenerativeAIContent,
8
12
  GoogleGenerativeAIContentPart,
@@ -203,25 +207,40 @@ export function convertToGoogleGenerativeAIMessages(
203
207
  }
204
208
 
205
209
  case 'file': {
206
- // default to image/jpeg for unknown image/* types
207
210
  const mediaType =
208
211
  part.mediaType === 'image/*' ? 'image/jpeg' : part.mediaType;
209
212
 
210
- parts.push(
211
- part.data instanceof URL
212
- ? {
213
- fileData: {
214
- mimeType: mediaType,
215
- fileUri: part.data.toString(),
216
- },
217
- }
218
- : {
219
- inlineData: {
220
- mimeType: mediaType,
221
- data: convertToBase64(part.data),
222
- },
223
- },
224
- );
213
+ if (part.data instanceof URL) {
214
+ parts.push({
215
+ fileData: {
216
+ mimeType: mediaType,
217
+ fileUri: part.data.toString(),
218
+ },
219
+ });
220
+ } else if (isProviderReference(part.data)) {
221
+ if (providerOptionsName === 'vertex') {
222
+ throw new UnsupportedFunctionalityError({
223
+ functionality: 'file parts with provider references',
224
+ });
225
+ }
226
+
227
+ parts.push({
228
+ fileData: {
229
+ mimeType: mediaType,
230
+ fileUri: resolveProviderReference({
231
+ reference: part.data,
232
+ provider: 'google',
233
+ }),
234
+ },
235
+ });
236
+ } else {
237
+ parts.push({
238
+ inlineData: {
239
+ mimeType: mediaType,
240
+ data: convertToBase64(part.data),
241
+ },
242
+ });
243
+ }
225
244
 
226
245
  break;
227
246
  }
@@ -295,6 +314,28 @@ export function convertToGoogleGenerativeAIMessages(
295
314
  });
296
315
  }
297
316
 
317
+ if (isProviderReference(part.data)) {
318
+ if (providerOptionsName === 'vertex') {
319
+ throw new UnsupportedFunctionalityError({
320
+ functionality: 'file parts with provider references',
321
+ });
322
+ }
323
+
324
+ return {
325
+ fileData: {
326
+ mimeType: part.mediaType,
327
+ fileUri: resolveProviderReference({
328
+ reference: part.data,
329
+ provider: 'google',
330
+ }),
331
+ },
332
+ ...(providerOpts?.thought === true
333
+ ? { thought: true }
334
+ : {}),
335
+ thoughtSignature,
336
+ };
337
+ }
338
+
298
339
  return {
299
340
  inlineData: {
300
341
  mimeType: part.mediaType,
@@ -0,0 +1,230 @@
1
+ import {
2
+ AISDKError,
3
+ type FilesV4,
4
+ type FilesV4UploadFileCallOptions,
5
+ type FilesV4UploadFileResult,
6
+ type SharedV4Warning,
7
+ } from '@ai-sdk/provider';
8
+ import {
9
+ combineHeaders,
10
+ convertUint8ArrayToBase64,
11
+ createJsonResponseHandler,
12
+ delay,
13
+ type FetchFunction,
14
+ lazySchema,
15
+ parseProviderOptions,
16
+ postJsonToApi,
17
+ zodSchema,
18
+ getFromApi,
19
+ } from '@ai-sdk/provider-utils';
20
+ import { z } from 'zod/v4';
21
+ import { googleFailedResponseHandler } from './google-error';
22
+
23
+ export type GoogleFilesUploadOptions = {
24
+ displayName?: string | null;
25
+ pollIntervalMs?: number | null;
26
+ pollTimeoutMs?: number | null;
27
+
28
+ [key: string]: unknown;
29
+ };
30
+
31
+ interface GoogleGenerativeAIFilesConfig {
32
+ provider: string;
33
+ baseURL: string;
34
+ headers: () => Record<string, string | undefined>;
35
+ fetch?: FetchFunction;
36
+ }
37
+
38
+ export class GoogleGenerativeAIFiles implements FilesV4 {
39
+ readonly specificationVersion = 'v4';
40
+
41
+ get provider(): string {
42
+ return this.config.provider;
43
+ }
44
+
45
+ constructor(private readonly config: GoogleGenerativeAIFilesConfig) {}
46
+
47
+ async uploadFile(
48
+ options: FilesV4UploadFileCallOptions,
49
+ ): Promise<FilesV4UploadFileResult> {
50
+ const googleOptions = (await parseProviderOptions({
51
+ provider: 'google',
52
+ providerOptions: options.providerOptions,
53
+ schema: googleFilesUploadOptionsSchema,
54
+ })) as GoogleFilesUploadOptions | undefined;
55
+
56
+ const resolvedHeaders = this.config.headers();
57
+ const fetchFn = this.config.fetch ?? globalThis.fetch;
58
+
59
+ const warnings: Array<SharedV4Warning> = [];
60
+ if (options.filename != null) {
61
+ warnings.push({ type: 'unsupported', feature: 'filename' });
62
+ }
63
+
64
+ const data = options.data;
65
+ const fileBytes =
66
+ data instanceof Uint8Array
67
+ ? data
68
+ : Uint8Array.from(atob(data), c => c.charCodeAt(0));
69
+
70
+ const mediaType = options.mediaType;
71
+ const displayName = googleOptions?.displayName;
72
+
73
+ const baseOrigin = this.config.baseURL.replace(/\/v1beta$/, '');
74
+
75
+ const initResponse = await fetchFn(`${baseOrigin}/upload/v1beta/files`, {
76
+ method: 'POST',
77
+ headers: {
78
+ ...resolvedHeaders,
79
+ 'X-Goog-Upload-Protocol': 'resumable',
80
+ 'X-Goog-Upload-Command': 'start',
81
+ 'X-Goog-Upload-Header-Content-Length': String(fileBytes.length),
82
+ 'X-Goog-Upload-Header-Content-Type': mediaType,
83
+ 'Content-Type': 'application/json',
84
+ },
85
+ body: JSON.stringify({
86
+ file: {
87
+ ...(displayName != null ? { display_name: displayName } : {}),
88
+ },
89
+ }),
90
+ });
91
+
92
+ if (!initResponse.ok) {
93
+ const errorBody = await initResponse.text();
94
+ throw new AISDKError({
95
+ name: 'GOOGLE_FILES_UPLOAD_ERROR',
96
+ message: `Failed to initiate resumable upload: ${initResponse.status} ${errorBody}`,
97
+ });
98
+ }
99
+
100
+ const uploadUrl = initResponse.headers.get('x-goog-upload-url');
101
+ if (!uploadUrl) {
102
+ throw new AISDKError({
103
+ name: 'GOOGLE_FILES_UPLOAD_ERROR',
104
+ message: 'No upload URL returned from initiation request',
105
+ });
106
+ }
107
+
108
+ const uploadResponse = await fetchFn(uploadUrl, {
109
+ method: 'POST',
110
+ headers: {
111
+ 'Content-Length': String(fileBytes.length),
112
+ 'X-Goog-Upload-Offset': '0',
113
+ 'X-Goog-Upload-Command': 'upload, finalize',
114
+ },
115
+ body: fileBytes,
116
+ });
117
+
118
+ if (!uploadResponse.ok) {
119
+ const errorBody = await uploadResponse.text();
120
+ throw new AISDKError({
121
+ name: 'GOOGLE_FILES_UPLOAD_ERROR',
122
+ message: `Failed to upload file data: ${uploadResponse.status} ${errorBody}`,
123
+ });
124
+ }
125
+
126
+ const uploadResult = (await uploadResponse.json()) as {
127
+ file: GoogleFileResource;
128
+ };
129
+
130
+ let file = uploadResult.file;
131
+
132
+ const pollIntervalMs = googleOptions?.pollIntervalMs ?? 2000;
133
+ const pollTimeoutMs = googleOptions?.pollTimeoutMs ?? 300000;
134
+ const startTime = Date.now();
135
+
136
+ while (file.state === 'PROCESSING') {
137
+ if (Date.now() - startTime > pollTimeoutMs) {
138
+ throw new AISDKError({
139
+ name: 'GOOGLE_FILES_UPLOAD_TIMEOUT',
140
+ message: `File processing timed out after ${pollTimeoutMs}ms`,
141
+ });
142
+ }
143
+
144
+ await delay(pollIntervalMs);
145
+
146
+ const { value: fileStatus } = await getFromApi({
147
+ url: `${this.config.baseURL}/${file.name}`,
148
+ headers: combineHeaders(resolvedHeaders),
149
+ successfulResponseHandler: createJsonResponseHandler(
150
+ googleFileResponseSchema,
151
+ ),
152
+ failedResponseHandler: googleFailedResponseHandler,
153
+ fetch: this.config.fetch,
154
+ });
155
+
156
+ file = fileStatus;
157
+ }
158
+
159
+ if (file.state === 'FAILED') {
160
+ throw new AISDKError({
161
+ name: 'GOOGLE_FILES_UPLOAD_FAILED',
162
+ message: `File processing failed for ${file.name}`,
163
+ });
164
+ }
165
+
166
+ return {
167
+ warnings,
168
+ providerReference: { google: file.uri },
169
+ mediaType: file.mimeType ?? options.mediaType,
170
+ providerMetadata: {
171
+ google: {
172
+ name: file.name,
173
+ displayName: file.displayName,
174
+ mimeType: file.mimeType,
175
+ sizeBytes: file.sizeBytes,
176
+ state: file.state,
177
+ uri: file.uri,
178
+ ...(file.createTime != null ? { createTime: file.createTime } : {}),
179
+ ...(file.updateTime != null ? { updateTime: file.updateTime } : {}),
180
+ ...(file.expirationTime != null
181
+ ? { expirationTime: file.expirationTime }
182
+ : {}),
183
+ ...(file.sha256Hash != null ? { sha256Hash: file.sha256Hash } : {}),
184
+ },
185
+ },
186
+ };
187
+ }
188
+ }
189
+
190
+ type GoogleFileResource = {
191
+ name: string;
192
+ displayName?: string | null;
193
+ mimeType: string;
194
+ sizeBytes?: string | null;
195
+ createTime?: string | null;
196
+ updateTime?: string | null;
197
+ expirationTime?: string | null;
198
+ sha256Hash?: string | null;
199
+ uri: string;
200
+ state: string;
201
+ };
202
+
203
+ const googleFileResponseSchema = lazySchema(() =>
204
+ zodSchema(
205
+ z.object({
206
+ name: z.string(),
207
+ displayName: z.string().nullish(),
208
+ mimeType: z.string(),
209
+ sizeBytes: z.string().nullish(),
210
+ createTime: z.string().nullish(),
211
+ updateTime: z.string().nullish(),
212
+ expirationTime: z.string().nullish(),
213
+ sha256Hash: z.string().nullish(),
214
+ uri: z.string(),
215
+ state: z.string(),
216
+ }),
217
+ ),
218
+ );
219
+
220
+ const googleFilesUploadOptionsSchema = lazySchema(() =>
221
+ zodSchema(
222
+ z
223
+ .object({
224
+ displayName: z.string().nullish(),
225
+ pollIntervalMs: z.number().positive().nullish(),
226
+ pollTimeoutMs: z.number().positive().nullish(),
227
+ })
228
+ .passthrough(),
229
+ ),
230
+ );
@@ -22,7 +22,11 @@ export type GoogleGenerativeAIContent = {
22
22
 
23
23
  export type GoogleGenerativeAIContentPart =
24
24
  | { text: string; thought?: boolean; thoughtSignature?: string }
25
- | { inlineData: { mimeType: string; data: string } }
25
+ | {
26
+ inlineData: { mimeType: string; data: string };
27
+ thought?: boolean;
28
+ thoughtSignature?: string;
29
+ }
26
30
  | { functionCall: { name: string; args: unknown }; thoughtSignature?: string }
27
31
  | {
28
32
  functionResponse: {
@@ -31,7 +35,11 @@ export type GoogleGenerativeAIContentPart =
31
35
  parts?: Array<GoogleGenerativeAIFunctionResponsePart>;
32
36
  };
33
37
  }
34
- | { fileData: { mimeType: string; fileUri: string } }
38
+ | {
39
+ fileData: { mimeType: string; fileUri: string };
40
+ thought?: boolean;
41
+ thoughtSignature?: string;
42
+ }
35
43
  | {
36
44
  toolCall: {
37
45
  toolType: string;
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  EmbeddingModelV4,
3
3
  Experimental_VideoModelV4,
4
+ FilesV4,
4
5
  ImageModelV4,
5
6
  LanguageModelV4,
6
7
  ProviderV4,
@@ -24,6 +25,7 @@ import {
24
25
  GoogleGenerativeAIImageModelId,
25
26
  } from './google-generative-ai-image-settings';
26
27
  import { GoogleGenerativeAIImageModel } from './google-generative-ai-image-model';
28
+ import { GoogleGenerativeAIFiles } from './google-generative-ai-files';
27
29
  import { GoogleGenerativeAIVideoModel } from './google-generative-ai-video-model';
28
30
  import { GoogleGenerativeAIVideoModelId } from './google-generative-ai-video-settings';
29
31
 
@@ -81,6 +83,8 @@ export interface GoogleGenerativeAIProvider extends ProviderV4 {
81
83
  modelId: GoogleGenerativeAIVideoModelId,
82
84
  ): Experimental_VideoModelV4;
83
85
 
86
+ files(): FilesV4;
87
+
84
88
  tools: typeof googleTools;
85
89
  }
86
90
 
@@ -185,6 +189,14 @@ export function createGoogleGenerativeAI(
185
189
  fetch: options.fetch,
186
190
  });
187
191
 
192
+ const createFiles = () =>
193
+ new GoogleGenerativeAIFiles({
194
+ provider: providerName,
195
+ baseURL,
196
+ headers: getHeaders,
197
+ fetch: options.fetch,
198
+ });
199
+
188
200
  const createVideoModel = (modelId: GoogleGenerativeAIVideoModelId) =>
189
201
  new GoogleGenerativeAIVideoModel(modelId, {
190
202
  provider: providerName,
@@ -216,6 +228,7 @@ export function createGoogleGenerativeAI(
216
228
  provider.imageModel = createImageModel;
217
229
  provider.video = createVideoModel;
218
230
  provider.videoModel = createVideoModel;
231
+ provider.files = createFiles;
219
232
  provider.tools = googleTools;
220
233
 
221
234
  return provider as GoogleGenerativeAIProvider;
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ export type {
21
21
  GoogleVideoModelOptions as GoogleGenerativeAIVideoProviderOptions,
22
22
  } from './google-generative-ai-video-model';
23
23
  export type { GoogleGenerativeAIVideoModelId } from './google-generative-ai-video-settings';
24
+ export type { GoogleFilesUploadOptions } from './google-generative-ai-files';
24
25
  export { createGoogleGenerativeAI, google } from './google-provider';
25
26
  export type {
26
27
  GoogleGenerativeAIProvider,