@ai-sdk/google 3.0.19 → 3.0.21
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/CHANGELOG.md +15 -0
- package/dist/index.d.mts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +269 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +277 -1
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.d.mts +1 -1
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs.map +1 -1
- package/docs/15-google-generative-ai.mdx +2 -2
- package/package.json +3 -3
- package/src/google-generative-ai-options.ts +1 -1
- package/src/google-generative-ai-video-model.ts +374 -0
- package/src/google-generative-ai-video-settings.ts +6 -0
- package/src/google-provider.ts +19 -1
- package/src/index.ts +2 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AISDKError,
|
|
3
|
+
type Experimental_VideoModelV3,
|
|
4
|
+
type SharedV3Warning,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
6
|
+
import {
|
|
7
|
+
combineHeaders,
|
|
8
|
+
convertUint8ArrayToBase64,
|
|
9
|
+
createJsonResponseHandler,
|
|
10
|
+
delay,
|
|
11
|
+
type FetchFunction,
|
|
12
|
+
getFromApi,
|
|
13
|
+
lazySchema,
|
|
14
|
+
parseProviderOptions,
|
|
15
|
+
postJsonToApi,
|
|
16
|
+
type Resolvable,
|
|
17
|
+
resolve,
|
|
18
|
+
zodSchema,
|
|
19
|
+
} from '@ai-sdk/provider-utils';
|
|
20
|
+
import { z } from 'zod/v4';
|
|
21
|
+
import { googleFailedResponseHandler } from './google-error';
|
|
22
|
+
import type { GoogleGenerativeAIVideoModelId } from './google-generative-ai-video-settings';
|
|
23
|
+
|
|
24
|
+
export type GoogleGenerativeAIVideoProviderOptions = {
|
|
25
|
+
// Polling configuration
|
|
26
|
+
pollIntervalMs?: number | null;
|
|
27
|
+
pollTimeoutMs?: number | null;
|
|
28
|
+
|
|
29
|
+
// Video generation options
|
|
30
|
+
personGeneration?: 'dont_allow' | 'allow_adult' | 'allow_all' | null;
|
|
31
|
+
negativePrompt?: string | null;
|
|
32
|
+
|
|
33
|
+
// Reference images (for style/asset reference)
|
|
34
|
+
referenceImages?: Array<{
|
|
35
|
+
bytesBase64Encoded?: string;
|
|
36
|
+
gcsUri?: string;
|
|
37
|
+
}> | null;
|
|
38
|
+
|
|
39
|
+
[key: string]: unknown; // For passthrough
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
interface GoogleGenerativeAIVideoModelConfig {
|
|
43
|
+
provider: string;
|
|
44
|
+
baseURL: string;
|
|
45
|
+
headers?: Resolvable<Record<string, string | undefined>>;
|
|
46
|
+
fetch?: FetchFunction;
|
|
47
|
+
generateId?: () => string;
|
|
48
|
+
_internal?: {
|
|
49
|
+
currentDate?: () => Date;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class GoogleGenerativeAIVideoModel implements Experimental_VideoModelV3 {
|
|
54
|
+
readonly specificationVersion = 'v3';
|
|
55
|
+
|
|
56
|
+
get provider(): string {
|
|
57
|
+
return this.config.provider;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get maxVideosPerCall(): number {
|
|
61
|
+
// Google supports multiple videos via sampleCount
|
|
62
|
+
return 4;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
constructor(
|
|
66
|
+
readonly modelId: GoogleGenerativeAIVideoModelId,
|
|
67
|
+
private readonly config: GoogleGenerativeAIVideoModelConfig,
|
|
68
|
+
) {}
|
|
69
|
+
|
|
70
|
+
async doGenerate(
|
|
71
|
+
options: Parameters<Experimental_VideoModelV3['doGenerate']>[0],
|
|
72
|
+
): Promise<Awaited<ReturnType<Experimental_VideoModelV3['doGenerate']>>> {
|
|
73
|
+
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
74
|
+
const warnings: SharedV3Warning[] = [];
|
|
75
|
+
|
|
76
|
+
const googleOptions = (await parseProviderOptions({
|
|
77
|
+
provider: 'google',
|
|
78
|
+
providerOptions: options.providerOptions,
|
|
79
|
+
schema: googleVideoProviderOptionsSchema,
|
|
80
|
+
})) as GoogleGenerativeAIVideoProviderOptions | undefined;
|
|
81
|
+
|
|
82
|
+
const instances: Array<Record<string, unknown>> = [{}];
|
|
83
|
+
const instance = instances[0];
|
|
84
|
+
|
|
85
|
+
if (options.prompt != null) {
|
|
86
|
+
instance.prompt = options.prompt;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle image-to-video: convert image to base64
|
|
90
|
+
if (options.image != null) {
|
|
91
|
+
if (options.image.type === 'url') {
|
|
92
|
+
warnings.push({
|
|
93
|
+
type: 'unsupported',
|
|
94
|
+
feature: 'URL-based image input',
|
|
95
|
+
details:
|
|
96
|
+
'Google Generative AI video models require base64-encoded images. URL will be ignored.',
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
const base64Data =
|
|
100
|
+
typeof options.image.data === 'string'
|
|
101
|
+
? options.image.data
|
|
102
|
+
: convertUint8ArrayToBase64(options.image.data);
|
|
103
|
+
|
|
104
|
+
instance.image = {
|
|
105
|
+
inlineData: {
|
|
106
|
+
mimeType: options.image.mediaType || 'image/png',
|
|
107
|
+
data: base64Data,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (googleOptions?.referenceImages != null) {
|
|
114
|
+
instance.referenceImages = googleOptions.referenceImages.map(refImg => {
|
|
115
|
+
if (refImg.bytesBase64Encoded) {
|
|
116
|
+
return {
|
|
117
|
+
inlineData: {
|
|
118
|
+
mimeType: 'image/png',
|
|
119
|
+
data: refImg.bytesBase64Encoded,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
} else if (refImg.gcsUri) {
|
|
123
|
+
return {
|
|
124
|
+
gcsUri: refImg.gcsUri,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return refImg;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const parameters: Record<string, unknown> = {
|
|
132
|
+
sampleCount: options.n,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (options.aspectRatio) {
|
|
136
|
+
parameters.aspectRatio = options.aspectRatio;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (options.resolution) {
|
|
140
|
+
const resolutionMap: Record<string, string> = {
|
|
141
|
+
'1280x720': '720p',
|
|
142
|
+
'1920x1080': '1080p',
|
|
143
|
+
'3840x2160': '4k',
|
|
144
|
+
};
|
|
145
|
+
parameters.resolution =
|
|
146
|
+
resolutionMap[options.resolution] || options.resolution;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (options.duration) {
|
|
150
|
+
parameters.durationSeconds = options.duration;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (options.seed) {
|
|
154
|
+
parameters.seed = options.seed;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (googleOptions != null) {
|
|
158
|
+
const opts = googleOptions as GoogleGenerativeAIVideoProviderOptions;
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
opts.personGeneration !== undefined &&
|
|
162
|
+
opts.personGeneration !== null
|
|
163
|
+
) {
|
|
164
|
+
parameters.personGeneration = opts.personGeneration;
|
|
165
|
+
}
|
|
166
|
+
if (opts.negativePrompt !== undefined && opts.negativePrompt !== null) {
|
|
167
|
+
parameters.negativePrompt = opts.negativePrompt;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const [key, value] of Object.entries(opts)) {
|
|
171
|
+
if (
|
|
172
|
+
![
|
|
173
|
+
'pollIntervalMs',
|
|
174
|
+
'pollTimeoutMs',
|
|
175
|
+
'personGeneration',
|
|
176
|
+
'negativePrompt',
|
|
177
|
+
'referenceImages',
|
|
178
|
+
].includes(key)
|
|
179
|
+
) {
|
|
180
|
+
parameters[key] = value;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { value: operation } = await postJsonToApi({
|
|
186
|
+
url: `${this.config.baseURL}/models/${this.modelId}:predictLongRunning`,
|
|
187
|
+
headers: combineHeaders(
|
|
188
|
+
await resolve(this.config.headers),
|
|
189
|
+
options.headers,
|
|
190
|
+
),
|
|
191
|
+
body: {
|
|
192
|
+
instances,
|
|
193
|
+
parameters,
|
|
194
|
+
},
|
|
195
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
196
|
+
googleOperationSchema,
|
|
197
|
+
),
|
|
198
|
+
failedResponseHandler: googleFailedResponseHandler,
|
|
199
|
+
abortSignal: options.abortSignal,
|
|
200
|
+
fetch: this.config.fetch,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const operationName = operation.name;
|
|
204
|
+
if (!operationName) {
|
|
205
|
+
throw new AISDKError({
|
|
206
|
+
name: 'GOOGLE_VIDEO_GENERATION_ERROR',
|
|
207
|
+
message: 'No operation name returned from API',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const pollIntervalMs = googleOptions?.pollIntervalMs ?? 10000; // 10 seconds (per Google docs)
|
|
212
|
+
const pollTimeoutMs = googleOptions?.pollTimeoutMs ?? 600000; // 10 minutes
|
|
213
|
+
|
|
214
|
+
const startTime = Date.now();
|
|
215
|
+
let finalOperation = operation;
|
|
216
|
+
let responseHeaders: Record<string, string> | undefined;
|
|
217
|
+
|
|
218
|
+
while (!finalOperation.done) {
|
|
219
|
+
if (Date.now() - startTime > pollTimeoutMs) {
|
|
220
|
+
throw new AISDKError({
|
|
221
|
+
name: 'GOOGLE_VIDEO_GENERATION_TIMEOUT',
|
|
222
|
+
message: `Video generation timed out after ${pollTimeoutMs}ms`,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
await delay(pollIntervalMs);
|
|
227
|
+
|
|
228
|
+
if (options.abortSignal?.aborted) {
|
|
229
|
+
throw new AISDKError({
|
|
230
|
+
name: 'GOOGLE_VIDEO_GENERATION_ABORTED',
|
|
231
|
+
message: 'Video generation request was aborted',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const { value: statusOperation, responseHeaders: pollHeaders } =
|
|
236
|
+
await getFromApi({
|
|
237
|
+
url: `${this.config.baseURL}/${operationName}`,
|
|
238
|
+
headers: combineHeaders(
|
|
239
|
+
await resolve(this.config.headers),
|
|
240
|
+
options.headers,
|
|
241
|
+
),
|
|
242
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
243
|
+
googleOperationSchema,
|
|
244
|
+
),
|
|
245
|
+
failedResponseHandler: googleFailedResponseHandler,
|
|
246
|
+
abortSignal: options.abortSignal,
|
|
247
|
+
fetch: this.config.fetch,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
finalOperation = statusOperation;
|
|
251
|
+
responseHeaders = pollHeaders;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (finalOperation.error) {
|
|
255
|
+
throw new AISDKError({
|
|
256
|
+
name: 'GOOGLE_VIDEO_GENERATION_FAILED',
|
|
257
|
+
message: `Video generation failed: ${finalOperation.error.message}`,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const response = finalOperation.response;
|
|
262
|
+
if (
|
|
263
|
+
!response?.generateVideoResponse?.generatedSamples ||
|
|
264
|
+
response.generateVideoResponse.generatedSamples.length === 0
|
|
265
|
+
) {
|
|
266
|
+
throw new AISDKError({
|
|
267
|
+
name: 'GOOGLE_VIDEO_GENERATION_ERROR',
|
|
268
|
+
message: `No videos in response. Response: ${JSON.stringify(finalOperation)}`,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const videos: Array<{ type: 'url'; url: string; mediaType: string }> = [];
|
|
273
|
+
const videoMetadata: Array<{ uri: string }> = [];
|
|
274
|
+
|
|
275
|
+
// Get API key from headers to append to download URLs
|
|
276
|
+
const resolvedHeaders = await resolve(this.config.headers);
|
|
277
|
+
const apiKey = resolvedHeaders?.['x-goog-api-key'];
|
|
278
|
+
|
|
279
|
+
for (const generatedSample of response.generateVideoResponse
|
|
280
|
+
.generatedSamples) {
|
|
281
|
+
if (generatedSample.video?.uri) {
|
|
282
|
+
// Append API key to URL for authentication during download
|
|
283
|
+
const urlWithAuth = apiKey
|
|
284
|
+
? `${generatedSample.video.uri}${generatedSample.video.uri.includes('?') ? '&' : '?'}key=${apiKey}`
|
|
285
|
+
: generatedSample.video.uri;
|
|
286
|
+
|
|
287
|
+
videos.push({
|
|
288
|
+
type: 'url',
|
|
289
|
+
url: urlWithAuth,
|
|
290
|
+
mediaType: 'video/mp4',
|
|
291
|
+
});
|
|
292
|
+
videoMetadata.push({
|
|
293
|
+
uri: generatedSample.video.uri,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (videos.length === 0) {
|
|
299
|
+
throw new AISDKError({
|
|
300
|
+
name: 'GOOGLE_VIDEO_GENERATION_ERROR',
|
|
301
|
+
message: 'No valid videos in response',
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
videos,
|
|
307
|
+
warnings,
|
|
308
|
+
response: {
|
|
309
|
+
timestamp: currentDate,
|
|
310
|
+
modelId: this.modelId,
|
|
311
|
+
headers: responseHeaders,
|
|
312
|
+
},
|
|
313
|
+
providerMetadata: {
|
|
314
|
+
google: {
|
|
315
|
+
videos: videoMetadata,
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const googleOperationSchema = z.object({
|
|
323
|
+
name: z.string().nullish(),
|
|
324
|
+
done: z.boolean().nullish(),
|
|
325
|
+
error: z
|
|
326
|
+
.object({
|
|
327
|
+
code: z.number().nullish(),
|
|
328
|
+
message: z.string(),
|
|
329
|
+
status: z.string().nullish(),
|
|
330
|
+
})
|
|
331
|
+
.nullish(),
|
|
332
|
+
response: z
|
|
333
|
+
.object({
|
|
334
|
+
generateVideoResponse: z
|
|
335
|
+
.object({
|
|
336
|
+
generatedSamples: z
|
|
337
|
+
.array(
|
|
338
|
+
z.object({
|
|
339
|
+
video: z
|
|
340
|
+
.object({
|
|
341
|
+
uri: z.string().nullish(),
|
|
342
|
+
})
|
|
343
|
+
.nullish(),
|
|
344
|
+
}),
|
|
345
|
+
)
|
|
346
|
+
.nullish(),
|
|
347
|
+
})
|
|
348
|
+
.nullish(),
|
|
349
|
+
})
|
|
350
|
+
.nullish(),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const googleVideoProviderOptionsSchema = lazySchema(() =>
|
|
354
|
+
zodSchema(
|
|
355
|
+
z
|
|
356
|
+
.object({
|
|
357
|
+
pollIntervalMs: z.number().positive().nullish(),
|
|
358
|
+
pollTimeoutMs: z.number().positive().nullish(),
|
|
359
|
+
personGeneration: z
|
|
360
|
+
.enum(['dont_allow', 'allow_adult', 'allow_all'])
|
|
361
|
+
.nullish(),
|
|
362
|
+
negativePrompt: z.string().nullish(),
|
|
363
|
+
referenceImages: z
|
|
364
|
+
.array(
|
|
365
|
+
z.object({
|
|
366
|
+
bytesBase64Encoded: z.string().nullish(),
|
|
367
|
+
gcsUri: z.string().nullish(),
|
|
368
|
+
}),
|
|
369
|
+
)
|
|
370
|
+
.nullish(),
|
|
371
|
+
})
|
|
372
|
+
.passthrough(),
|
|
373
|
+
),
|
|
374
|
+
);
|
package/src/google-provider.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EmbeddingModelV3,
|
|
3
|
+
Experimental_VideoModelV3,
|
|
4
|
+
ImageModelV3,
|
|
3
5
|
LanguageModelV3,
|
|
4
6
|
ProviderV3,
|
|
5
|
-
ImageModelV3,
|
|
6
7
|
} from '@ai-sdk/provider';
|
|
7
8
|
import {
|
|
8
9
|
FetchFunction,
|
|
@@ -23,6 +24,8 @@ import {
|
|
|
23
24
|
GoogleGenerativeAIImageModelId,
|
|
24
25
|
} from './google-generative-ai-image-settings';
|
|
25
26
|
import { GoogleGenerativeAIImageModel } from './google-generative-ai-image-model';
|
|
27
|
+
import { GoogleGenerativeAIVideoModel } from './google-generative-ai-video-model';
|
|
28
|
+
import { GoogleGenerativeAIVideoModelId } from './google-generative-ai-video-settings';
|
|
26
29
|
|
|
27
30
|
export interface GoogleGenerativeAIProvider extends ProviderV3 {
|
|
28
31
|
(modelId: GoogleGenerativeAIModelId): LanguageModelV3;
|
|
@@ -39,6 +42,11 @@ export interface GoogleGenerativeAIProvider extends ProviderV3 {
|
|
|
39
42
|
settings?: GoogleGenerativeAIImageSettings,
|
|
40
43
|
): ImageModelV3;
|
|
41
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Creates a model for video generation.
|
|
47
|
+
*/
|
|
48
|
+
video(modelId: GoogleGenerativeAIVideoModelId): Experimental_VideoModelV3;
|
|
49
|
+
|
|
42
50
|
/**
|
|
43
51
|
* @deprecated Use `chat()` instead.
|
|
44
52
|
*/
|
|
@@ -170,6 +178,15 @@ export function createGoogleGenerativeAI(
|
|
|
170
178
|
fetch: options.fetch,
|
|
171
179
|
});
|
|
172
180
|
|
|
181
|
+
const createVideoModel = (modelId: GoogleGenerativeAIVideoModelId) =>
|
|
182
|
+
new GoogleGenerativeAIVideoModel(modelId, {
|
|
183
|
+
provider: providerName,
|
|
184
|
+
baseURL,
|
|
185
|
+
headers: getHeaders,
|
|
186
|
+
fetch: options.fetch,
|
|
187
|
+
generateId: options.generateId ?? generateId,
|
|
188
|
+
});
|
|
189
|
+
|
|
173
190
|
const provider = function (modelId: GoogleGenerativeAIModelId) {
|
|
174
191
|
if (new.target) {
|
|
175
192
|
throw new Error(
|
|
@@ -190,6 +207,7 @@ export function createGoogleGenerativeAI(
|
|
|
190
207
|
provider.textEmbeddingModel = createEmbeddingModel;
|
|
191
208
|
provider.image = createImageModel;
|
|
192
209
|
provider.imageModel = createImageModel;
|
|
210
|
+
provider.video = createVideoModel;
|
|
193
211
|
provider.tools = googleTools;
|
|
194
212
|
|
|
195
213
|
return provider as GoogleGenerativeAIProvider;
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export type { GoogleGenerativeAIProviderOptions } from './google-generative-ai-o
|
|
|
3
3
|
export type { GoogleGenerativeAIProviderMetadata } from './google-generative-ai-prompt';
|
|
4
4
|
export type { GoogleGenerativeAIImageProviderOptions } from './google-generative-ai-image-model';
|
|
5
5
|
export type { GoogleGenerativeAIEmbeddingProviderOptions } from './google-generative-ai-embedding-options';
|
|
6
|
+
export type { GoogleGenerativeAIVideoProviderOptions } from './google-generative-ai-video-model';
|
|
7
|
+
export type { GoogleGenerativeAIVideoModelId } from './google-generative-ai-video-settings';
|
|
6
8
|
export { createGoogleGenerativeAI, google } from './google-provider';
|
|
7
9
|
export type {
|
|
8
10
|
GoogleGenerativeAIProvider,
|