@ai-sdk/gateway 4.0.43 → 4.0.44

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.
@@ -444,6 +444,29 @@ if (generationId) {
444
444
  }
445
445
  ```
446
446
 
447
+ You can also capture the generation ID for every completed model call with
448
+ `onLanguageModelCallEnd`. The callback runs before client-side tools execute,
449
+ so the metadata remains available even if the operation is later aborted while
450
+ a tool is running:
451
+
452
+ ```ts
453
+ import { gateway, generateText } from 'ai';
454
+
455
+ await generateText({
456
+ model: gateway('anthropic/claude-sonnet-4'),
457
+ prompt: 'Explain quantum entanglement briefly',
458
+ onLanguageModelCallEnd({ providerMetadata }) {
459
+ const generationId = providerMetadata?.gateway?.generationId as
460
+ | string
461
+ | undefined;
462
+
463
+ if (generationId) {
464
+ console.log(`Completed Gateway generation: ${generationId}`);
465
+ }
466
+ },
467
+ });
468
+ ```
469
+
447
470
  The `getGenerationInfo()` method accepts:
448
471
 
449
472
  - **id** _string_ - The generation ID to look up (format: `gen_<ulid>`, required)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ai-sdk/gateway",
3
3
  "private": false,
4
- "version": "4.0.43",
4
+ "version": "4.0.44",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "sideEffects": false,
@@ -3,7 +3,10 @@ import {
3
3
  type Experimental_VideoModelV4 as VideoModelV4,
4
4
  type Experimental_VideoModelV4CallOptions as VideoModelV4CallOptions,
5
5
  type Experimental_VideoModelV4File as VideoModelV4File,
6
+ type Experimental_VideoModelV4OperationStartResult as VideoModelV4OperationStartResult,
7
+ type Experimental_VideoModelV4OperationStatusResult as VideoModelV4OperationStatusResult,
6
8
  type Experimental_VideoModelV4VideoData as VideoModelV4VideoData,
9
+ type JSONValue,
7
10
  type SharedV4ProviderMetadata,
8
11
  type SharedV4Warning,
9
12
  } from '@ai-sdk/provider';
@@ -11,6 +14,7 @@ import {
11
14
  combineHeaders,
12
15
  convertUint8ArrayToBase64,
13
16
  createJsonErrorResponseHandler,
17
+ createJsonResponseHandler,
14
18
  parseJsonEventStream,
15
19
  postJsonToApi,
16
20
  resolve,
@@ -38,22 +42,7 @@ export class GatewayVideoModel implements VideoModelV4 {
38
42
  return this.config.provider;
39
43
  }
40
44
 
41
- async doGenerate({
42
- prompt,
43
- n,
44
- aspectRatio,
45
- resolution,
46
- duration,
47
- fps,
48
- seed,
49
- generateAudio,
50
- image,
51
- frameImages,
52
- inputReferences,
53
- providerOptions,
54
- headers,
55
- abortSignal,
56
- }: VideoModelV4CallOptions): Promise<{
45
+ async doGenerate(options: VideoModelV4CallOptions): Promise<{
57
46
  videos: Array<VideoModelV4VideoData>;
58
47
  warnings: Array<SharedV4Warning>;
59
48
  providerMetadata?: SharedV4ProviderMetadata;
@@ -63,6 +52,7 @@ export class GatewayVideoModel implements VideoModelV4 {
63
52
  headers: Record<string, string> | undefined;
64
53
  };
65
54
  }> {
55
+ const { headers, abortSignal } = options;
66
56
  const resolvedHeaders = this.config.headers
67
57
  ? await resolve(this.config.headers)
68
58
  : undefined;
@@ -76,29 +66,7 @@ export class GatewayVideoModel implements VideoModelV4 {
76
66
  await resolve(this.config.o11yHeaders),
77
67
  { accept: 'text/event-stream' },
78
68
  ),
79
- body: {
80
- prompt,
81
- n,
82
- ...(aspectRatio && { aspectRatio }),
83
- ...(resolution && { resolution }),
84
- ...(duration && { duration }),
85
- ...(fps && { fps }),
86
- ...(seed && { seed }),
87
- ...(generateAudio !== undefined && { generateAudio }),
88
- ...(providerOptions && { providerOptions }),
89
- ...(image && { image: maybeEncodeVideoFile(image) }),
90
- ...(frameImages && {
91
- frameImages: frameImages.map(frame => ({
92
- ...frame,
93
- image: maybeEncodeVideoFile(frame.image),
94
- })),
95
- }),
96
- ...(inputReferences && {
97
- inputReferences: inputReferences.map(reference =>
98
- maybeEncodeVideoFile(reference),
99
- ),
100
- }),
101
- },
69
+ body: this.buildRequestBody(options),
102
70
  successfulResponseHandler: async ({
103
71
  response,
104
72
  url,
@@ -186,8 +154,8 @@ export class GatewayVideoModel implements VideoModelV4 {
186
154
  return {
187
155
  videos: responseBody.videos,
188
156
  warnings: responseBody.warnings ?? [],
189
- providerMetadata:
190
- responseBody.providerMetadata as SharedV4ProviderMetadata,
157
+ providerMetadata: (responseBody.providerMetadata ??
158
+ undefined) as SharedV4ProviderMetadata,
191
159
  response: {
192
160
  timestamp: new Date(),
193
161
  modelId: this.modelId,
@@ -202,10 +170,209 @@ export class GatewayVideoModel implements VideoModelV4 {
202
170
  }
203
171
  }
204
172
 
173
+ // `handleWebhookOption` is intentionally NOT implemented. The Gateway's
174
+ // async video jobs are polling-first: it does not expose a provider->SDK
175
+ // webhook completion channel. If this method were present, `generateVideo`
176
+ // would forward a webhook URL and await a notification that never arrives.
177
+ // `doStart` still forwards the spec's `webhookUrl` — as the Gateway's
178
+ // `callbackUrl` wire field (its completion-webhook contract; unknown body
179
+ // keys are stripped server-side). Enabling webhooks later requires adding
180
+ // `handleWebhookOption` here plus handling the Gateway's HMAC-signed
181
+ // delivery format; the wire field is already the right one.
182
+
183
+ async doStart(
184
+ options: VideoModelV4CallOptions & {
185
+ webhookUrl?: string;
186
+ },
187
+ ): Promise<VideoModelV4OperationStartResult> {
188
+ const { headers, abortSignal, webhookUrl } = options;
189
+ const resolvedHeaders = this.config.headers
190
+ ? await resolve(this.config.headers)
191
+ : undefined;
192
+ try {
193
+ const { responseHeaders, value: responseBody } = await postJsonToApi({
194
+ url: this.getStartUrl(),
195
+ headers: combineHeaders(
196
+ resolvedHeaders,
197
+ headers ?? {},
198
+ this.getModelConfigHeaders(),
199
+ await resolve(this.config.o11yHeaders),
200
+ ),
201
+ body: {
202
+ ...this.buildRequestBody(options),
203
+ // The spec option is `webhookUrl`; the Gateway's wire contract for a
204
+ // completion webhook is `callbackUrl`.
205
+ ...(webhookUrl && { callbackUrl: webhookUrl }),
206
+ },
207
+ successfulResponseHandler: createJsonResponseHandler(
208
+ gatewayVideoStartResponseSchema,
209
+ ),
210
+ failedResponseHandler: createJsonErrorResponseHandler({
211
+ errorSchema: z.any(),
212
+ errorToMessage: data => data,
213
+ }),
214
+ ...(abortSignal && { abortSignal }),
215
+ fetch: this.config.fetch,
216
+ });
217
+
218
+ return {
219
+ operation: responseBody.operation as JSONValue,
220
+ warnings: responseBody.warnings ?? [],
221
+ providerMetadata: (responseBody.providerMetadata ??
222
+ undefined) as SharedV4ProviderMetadata,
223
+ response: {
224
+ timestamp: new Date(),
225
+ modelId: this.modelId,
226
+ headers: responseHeaders,
227
+ },
228
+ };
229
+ } catch (error) {
230
+ throw await asGatewayError(
231
+ error,
232
+ await parseAuthMethod(resolvedHeaders ?? {}),
233
+ );
234
+ }
235
+ }
236
+
237
+ async doStatus({
238
+ operation,
239
+ abortSignal,
240
+ headers,
241
+ }: {
242
+ operation: JSONValue;
243
+ abortSignal?: AbortSignal;
244
+ headers?: Record<string, string | undefined>;
245
+ }): Promise<VideoModelV4OperationStatusResult> {
246
+ const resolvedHeaders = this.config.headers
247
+ ? await resolve(this.config.headers)
248
+ : undefined;
249
+ try {
250
+ const { responseHeaders, value: responseBody } = await postJsonToApi({
251
+ url: this.getStatusUrl(),
252
+ headers: combineHeaders(
253
+ resolvedHeaders,
254
+ headers ?? {},
255
+ this.getModelConfigHeaders(),
256
+ await resolve(this.config.o11yHeaders),
257
+ ),
258
+ body: { operation },
259
+ successfulResponseHandler: createJsonResponseHandler(
260
+ gatewayVideoStatusResponseSchema,
261
+ ),
262
+ failedResponseHandler: createJsonErrorResponseHandler({
263
+ errorSchema: z.any(),
264
+ errorToMessage: data => data,
265
+ }),
266
+ ...(abortSignal && { abortSignal }),
267
+ fetch: this.config.fetch,
268
+ });
269
+
270
+ const response = {
271
+ timestamp: new Date(),
272
+ modelId: this.modelId,
273
+ headers: responseHeaders,
274
+ };
275
+
276
+ if (responseBody.status === 'completed') {
277
+ return {
278
+ status: 'completed',
279
+ videos: responseBody.videos,
280
+ warnings: responseBody.warnings ?? [],
281
+ providerMetadata: (responseBody.providerMetadata ??
282
+ undefined) as SharedV4ProviderMetadata,
283
+ response,
284
+ };
285
+ }
286
+
287
+ if (responseBody.status === 'error') {
288
+ return {
289
+ status: 'error',
290
+ error: responseBody.error,
291
+ providerMetadata: (responseBody.providerMetadata ??
292
+ undefined) as SharedV4ProviderMetadata,
293
+ response,
294
+ };
295
+ }
296
+
297
+ // The Gateway reports cooperative cancellation as its own terminal
298
+ // status; the v4 operation union has no cancelled state, so surface it
299
+ // as a terminal error rather than polling forever.
300
+ if (responseBody.status === 'cancelled') {
301
+ return {
302
+ status: 'error',
303
+ error: 'Video generation was cancelled.',
304
+ providerMetadata: (responseBody.providerMetadata ??
305
+ undefined) as SharedV4ProviderMetadata,
306
+ response,
307
+ };
308
+ }
309
+
310
+ return {
311
+ status: 'pending',
312
+ warnings: responseBody.warnings ?? [],
313
+ providerMetadata: (responseBody.providerMetadata ??
314
+ undefined) as SharedV4ProviderMetadata,
315
+ response,
316
+ };
317
+ } catch (error) {
318
+ throw await asGatewayError(
319
+ error,
320
+ await parseAuthMethod(resolvedHeaders ?? {}),
321
+ );
322
+ }
323
+ }
324
+
325
+ private buildRequestBody({
326
+ prompt,
327
+ n,
328
+ aspectRatio,
329
+ resolution,
330
+ duration,
331
+ fps,
332
+ seed,
333
+ generateAudio,
334
+ image,
335
+ frameImages,
336
+ inputReferences,
337
+ providerOptions,
338
+ }: VideoModelV4CallOptions) {
339
+ return {
340
+ prompt,
341
+ n,
342
+ ...(aspectRatio && { aspectRatio }),
343
+ ...(resolution && { resolution }),
344
+ ...(duration && { duration }),
345
+ ...(fps && { fps }),
346
+ ...(seed && { seed }),
347
+ ...(generateAudio !== undefined && { generateAudio }),
348
+ ...(providerOptions && { providerOptions }),
349
+ ...(image && { image: maybeEncodeVideoFile(image) }),
350
+ ...(frameImages && {
351
+ frameImages: frameImages.map(frame => ({
352
+ ...frame,
353
+ image: maybeEncodeVideoFile(frame.image),
354
+ })),
355
+ }),
356
+ ...(inputReferences && {
357
+ inputReferences: inputReferences.map(reference =>
358
+ maybeEncodeVideoFile(reference),
359
+ ),
360
+ }),
361
+ };
362
+ }
363
+
205
364
  private getUrl() {
206
365
  return `${this.config.baseURL}/video-model`;
207
366
  }
208
367
 
368
+ private getStartUrl() {
369
+ return `${this.config.baseURL}/video-model/start`;
370
+ }
371
+
372
+ private getStatusUrl() {
373
+ return `${this.config.baseURL}/video-model/status`;
374
+ }
375
+
209
376
  private getModelConfigHeaders() {
210
377
  return {
211
378
  'ai-video-model-specification-version': '4',
@@ -282,3 +449,40 @@ const gatewayVideoEventSchema = z.discriminatedUnion('type', [
282
449
  param: z.unknown().nullable(),
283
450
  }),
284
451
  ]);
452
+
453
+ const gatewayVideoStartResponseSchema = z.object({
454
+ operation: z.unknown(),
455
+ warnings: z.array(gatewayVideoWarningSchema).nullish(),
456
+ providerMetadata: z.record(z.string(), providerMetadataEntrySchema).nullish(),
457
+ });
458
+
459
+ const gatewayVideoStatusResponseSchema = z.discriminatedUnion('status', [
460
+ z.object({
461
+ status: z.literal('pending'),
462
+ warnings: z.array(gatewayVideoWarningSchema).nullish(),
463
+ providerMetadata: z
464
+ .record(z.string(), providerMetadataEntrySchema)
465
+ .nullish(),
466
+ }),
467
+ z.object({
468
+ status: z.literal('completed'),
469
+ videos: z.array(gatewayVideoDataSchema),
470
+ warnings: z.array(gatewayVideoWarningSchema).nullish(),
471
+ providerMetadata: z
472
+ .record(z.string(), providerMetadataEntrySchema)
473
+ .nullish(),
474
+ }),
475
+ z.object({
476
+ status: z.literal('error'),
477
+ error: z.string(),
478
+ providerMetadata: z
479
+ .record(z.string(), providerMetadataEntrySchema)
480
+ .nullish(),
481
+ }),
482
+ z.object({
483
+ status: z.literal('cancelled'),
484
+ providerMetadata: z
485
+ .record(z.string(), providerMetadataEntrySchema)
486
+ .nullish(),
487
+ }),
488
+ ]);