@ai-sdk/alibaba 1.0.49 → 1.0.50

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.
@@ -34,7 +34,10 @@ export type AlibabaVideoModelOptions = {
34
34
  shotType?: 'single' | 'multi' | null;
35
35
  /** Whether to add watermark to generated video. Defaults to false. */
36
36
  watermark?: boolean | null;
37
- /** Enable audio generation (for I2V/R2V models). */
37
+ /**
38
+ * Enable audio generation (I2V/R2V and wan3 models;
39
+ * wan2.7 models always generate audio). Defaults to true on wan3.
40
+ */
38
41
  audio?: boolean | null;
39
42
  /**
40
43
  * Reference URLs for reference-to-video mode.
@@ -43,22 +46,34 @@ export type AlibabaVideoModelOptions = {
43
46
  */
44
47
  referenceUrls?: string[] | null;
45
48
  /**
46
- * Explicit media array for reference-to-video mode (wan2.7 models).
47
- * Overrides the automatic mapping from `inputReferences` and `frameImages`.
49
+ * Explicit media array (wan2.7 and wan3 models). Overrides the automatic
50
+ * mapping from `inputReferences` and `frameImages`.
48
51
  * Use `Image 1`, `Video 1`, etc. in prompts to reference media items
49
- * (images and videos are counted separately, in array order).
52
+ * (images, videos, and audio are counted separately, in array order).
53
+ *
54
+ * `reference_audio`, `file`, and `link` are wan3-only and have no top-level
55
+ * call option, so they can only be passed here. wan3 also rejects mixing
56
+ * `reference_*`/`file`/`link` with `first_frame`/`last_frame`.
50
57
  */
51
58
  media?: Array<{
52
- type: 'reference_image' | 'reference_video' | 'first_frame';
59
+ type:
60
+ | 'reference_image'
61
+ | 'reference_video'
62
+ | 'reference_audio'
63
+ | 'first_frame'
64
+ | 'last_frame'
65
+ | 'file'
66
+ | 'link';
53
67
  /** Public URL, or a `data:{mime};base64,{data}` URI for images. */
54
68
  url: string;
55
69
  /** URL to an audio file used as voice reference for this media item. */
56
70
  referenceVoice?: string | null;
57
71
  }> | null;
58
72
  /**
59
- * Aspect ratio (wan2.7 text-to-video and reference-to-video models).
73
+ * Aspect ratio (wan2.7 text-to-video and reference-to-video models, and
74
+ * every wan3 generation). `adaptive` is wan3-only, and is its default.
60
75
  */
61
- ratio?: '16:9' | '9:16' | '1:1' | '4:3' | '3:4' | null;
76
+ ratio?: 'adaptive' | '16:9' | '9:16' | '1:1' | '4:3' | '3:4' | null;
62
77
  /** Polling interval in milliseconds. Defaults to 5000 (5 seconds). */
63
78
  pollIntervalMs?: number | null;
64
79
  /** Maximum wait time in milliseconds for video generation. Defaults to 600000 (10 minutes). */
@@ -83,14 +98,20 @@ const alibabaVideoModelOptionsSchema = lazySchema(() =>
83
98
  type: z.enum([
84
99
  'reference_image',
85
100
  'reference_video',
101
+ 'reference_audio',
86
102
  'first_frame',
103
+ 'last_frame',
104
+ 'file',
105
+ 'link',
87
106
  ]),
88
107
  url: z.string(),
89
108
  referenceVoice: z.string().nullish(),
90
109
  }),
91
110
  )
92
111
  .nullish(),
93
- ratio: z.enum(['16:9', '9:16', '1:1', '4:3', '3:4']).nullish(),
112
+ ratio: z
113
+ .enum(['adaptive', '16:9', '9:16', '1:1', '4:3', '3:4'])
114
+ .nullish(),
94
115
  pollIntervalMs: z.number().positive().nullish(),
95
116
  pollTimeoutMs: z.number().positive().nullish(),
96
117
  })
@@ -149,8 +170,12 @@ const alibabaVideoTaskStatusSchema = z.object({
149
170
  .object({
150
171
  duration: z.number().nullish(),
151
172
  output_video_duration: z.number().nullish(),
173
+ // wan3 splits the total: input video counts toward its 30s ceiling.
174
+ input_video_duration: z.number().nullish(),
175
+ fps: z.number().nullish(),
152
176
  SR: z.number().nullish(),
153
177
  size: z.string().nullish(),
178
+ ratio: z.string().nullish(),
154
179
  })
155
180
  .nullish(),
156
181
  request_id: z.string().nullish(),
@@ -160,17 +185,29 @@ type AlibabaVideoTaskStatusResponse = z.infer<
160
185
  typeof alibabaVideoTaskStatusSchema
161
186
  >;
162
187
 
188
+ // Only meaningful for ids that name their mode (wan2.6/wan2.7). wan3 ships a
189
+ // single all-in-one id, so its mode comes from the media the request carries.
163
190
  function detectMode(modelId: string): 't2v' | 'i2v' | 'r2v' {
164
191
  if (modelId.includes('-i2v')) return 'i2v';
165
192
  if (modelId.includes('-r2v')) return 'r2v';
166
193
  return 't2v';
167
194
  }
168
195
 
169
- // wan2.7 models use a different protocol than earlier wan models:
170
- // resolution tiers + ratio instead of size, input.media instead of
171
- // input.reference_urls (R2V), and no shot_type or audio parameters.
172
- function isWan27Model(modelId: string): boolean {
173
- return modelId.startsWith('wan2.7');
196
+ /**
197
+ * Request protocol, selected by model id:
198
+ * - `legacy` (wan2.6 and earlier): `parameters.size`, `input.img_url`, and
199
+ * `input.reference_urls`.
200
+ * - `wan27`: resolution tiers and `ratio` instead of `size`, `input.media`
201
+ * instead of `input.reference_urls`, no `shot_type`, audio always on.
202
+ * - `wan3`: like `wan27`, plus a real `last_frame` slot, an `audio` toggle,
203
+ * a 480P tier, and one id covering every mode.
204
+ */
205
+ type AlibabaVideoProtocol = 'legacy' | 'wan27' | 'wan3';
206
+
207
+ function detectProtocol(modelId: string): AlibabaVideoProtocol {
208
+ if (modelId.startsWith('wan3')) return 'wan3';
209
+ if (modelId.startsWith('wan2.7')) return 'wan27';
210
+ return 'legacy';
174
211
  }
175
212
 
176
213
  // Maps SDK "WIDTHxHEIGHT" resolutions to Alibaba resolution tiers.
@@ -229,6 +266,13 @@ function getFirstFrameImage(
229
266
  ?.image;
230
267
  }
231
268
 
269
+ function getLastFrameImage(
270
+ options: Parameters<Experimental_VideoModelV3['doGenerate']>[0],
271
+ ): Experimental_VideoModelV3File | undefined {
272
+ return options.frameImages?.find(frame => frame.frameType === 'last_frame')
273
+ ?.image;
274
+ }
275
+
232
276
  function resolveStartImage(
233
277
  options: Parameters<Experimental_VideoModelV3['doGenerate']>[0],
234
278
  ): Experimental_VideoModelV3File | undefined {
@@ -239,11 +283,16 @@ function isVideoUrl(url: string): boolean {
239
283
  return /\.(mp4|mov)([?#]|$)/i.test(url);
240
284
  }
241
285
 
242
- // Builds the wan2.7 input.media array from inputReferences and frameImages.
286
+ // Builds the input.media array (wan2.7 and wan3) from inputReferences plus the
287
+ // frame images the caller resolved for this protocol.
243
288
  function resolveMedia(
244
289
  options: Parameters<Experimental_VideoModelV3['doGenerate']>[0],
245
290
  alibabaOptions: AlibabaVideoModelOptions | undefined,
246
291
  warnings: SharedV3Warning[],
292
+ frames: {
293
+ first?: Experimental_VideoModelV3File;
294
+ last?: Experimental_VideoModelV3File;
295
+ },
247
296
  ): Array<Record<string, unknown>> | undefined {
248
297
  if (alibabaOptions?.media != null && alibabaOptions.media.length > 0) {
249
298
  return alibabaOptions.media.map(item => ({
@@ -279,11 +328,17 @@ function resolveMedia(
279
328
  }
280
329
  }
281
330
 
282
- const firstFrame = getFirstFrameImage(options);
283
- if (firstFrame != null) {
331
+ if (frames.first != null) {
284
332
  media.push({
285
333
  type: 'first_frame',
286
- url: convertImageModelFileToDataUri(firstFrame),
334
+ url: convertImageModelFileToDataUri(frames.first),
335
+ });
336
+ }
337
+
338
+ if (frames.last != null) {
339
+ media.push({
340
+ type: 'last_frame',
341
+ url: convertImageModelFileToDataUri(frames.last),
287
342
  });
288
343
  }
289
344
 
@@ -364,20 +419,37 @@ export class AlibabaVideoModel implements Experimental_VideoModelV3 {
364
419
  }
365
420
 
366
421
  const startImage = resolveStartImage(options);
367
- const wan27 = isWan27Model(this.modelId);
368
- // wan2.7 T2V and R2V take an explicit aspect ratio (I2V follows the input image)
369
- const supportsRatio = wan27 && mode !== 'i2v';
370
-
371
- // Handle image input for I2V mode
372
- if (mode === 'i2v' && startImage != null) {
422
+ const protocol = detectProtocol(this.modelId);
423
+ const wan27 = protocol === 'wan27';
424
+ const wan3 = protocol === 'wan3';
425
+ // Resolution tiers and input.media replaced size/img_url from wan2.7 on.
426
+ const tieredProtocol = wan27 || wan3;
427
+ // wan2.7 T2V and R2V take an explicit aspect ratio (I2V follows the input
428
+ // image); wan3 serves every mode from one id, so it always takes one.
429
+ const supportsRatio = wan3 || (wan27 && mode !== 'i2v');
430
+
431
+ // Handle image input for I2V mode (wan3 carries frames in input.media)
432
+ if (!wan3 && mode === 'i2v' && startImage != null) {
373
433
  input.img_url = fileToImageString(startImage);
374
434
  }
375
435
 
376
- // Handle references for R2V mode
377
- if (mode === 'r2v') {
436
+ if (wan3) {
437
+ // The media the request carries decides whether this is text-, image-,
438
+ // or reference-to-video, so there is no mode to read off the id. A bare
439
+ // prompt stays text-to-video.
440
+ const media = resolveMedia(options, alibabaOptions, warnings, {
441
+ first: startImage,
442
+ last: getLastFrameImage(options),
443
+ });
444
+ if (media != null) {
445
+ input.media = media;
446
+ }
447
+ } else if (mode === 'r2v') {
378
448
  if (wan27) {
379
449
  // wan2.7: input.media
380
- const media = resolveMedia(options, alibabaOptions, warnings);
450
+ const media = resolveMedia(options, alibabaOptions, warnings, {
451
+ first: getFirstFrameImage(options),
452
+ });
381
453
  if (media != null) {
382
454
  input.media = media;
383
455
  }
@@ -394,11 +466,10 @@ export class AlibabaVideoModel implements Experimental_VideoModelV3 {
394
466
  }
395
467
  }
396
468
 
397
- const lastFrame = options.frameImages?.find(
398
- frame => frame.frameType === 'last_frame',
399
- )?.image;
469
+ const lastFrame = getLastFrameImage(options);
400
470
 
401
- if (lastFrame != null) {
471
+ // wan3 has a real closing-frame slot, filled in input.media above.
472
+ if (lastFrame != null && !wan3) {
402
473
  warnings.push({
403
474
  type: 'unsupported',
404
475
  feature: 'frameImages',
@@ -411,7 +482,8 @@ export class AlibabaVideoModel implements Experimental_VideoModelV3 {
411
482
  if (
412
483
  options.inputReferences != null &&
413
484
  options.inputReferences.length > 0 &&
414
- mode !== 'r2v'
485
+ mode !== 'r2v' &&
486
+ !wan3
415
487
  ) {
416
488
  warnings.push({
417
489
  type: 'unsupported',
@@ -435,17 +507,22 @@ export class AlibabaVideoModel implements Experimental_VideoModelV3 {
435
507
 
436
508
  // Resolution / Size mapping
437
509
  if (options.resolution != null) {
438
- if (mode === 'i2v' || wan27) {
439
- // I2V and wan2.7 models use "720P" / "1080P" format
510
+ if (mode === 'i2v' || tieredProtocol) {
511
+ // I2V, wan2.7, and wan3 use the "720P" / "1080P" tier format
440
512
  const resolutionTier =
441
513
  resolutionTierMap[options.resolution] || options.resolution;
442
- if (wan27 && resolutionTier !== '720P' && resolutionTier !== '1080P') {
514
+ // wan3 adds a 480P tier to wan2.7's two.
515
+ const supportedTiers = wan3
516
+ ? ['480P', '720P', '1080P']
517
+ : ['720P', '1080P'];
518
+ if (tieredProtocol && !supportedTiers.includes(resolutionTier)) {
443
519
  warnings.push({
444
520
  type: 'unsupported',
445
521
  feature: 'resolution',
446
522
  details:
447
- 'wan2.7 models only support 720P and 1080P ' +
448
- `resolutions. The resolution "${options.resolution}" was ignored.`,
523
+ `${wan3 ? 'wan3' : 'wan2.7'} models only support the ` +
524
+ `${supportedTiers.join(', ')} resolution tiers. ` +
525
+ `The resolution "${options.resolution}" was ignored.`,
449
526
  });
450
527
  } else {
451
528
  parameters.resolution = resolutionTier;
@@ -475,14 +552,14 @@ export class AlibabaVideoModel implements Experimental_VideoModelV3 {
475
552
  parameters.prompt_extend = alibabaOptions.promptExtend;
476
553
  }
477
554
  if (alibabaOptions?.shotType != null) {
478
- if (wan27) {
555
+ if (tieredProtocol) {
479
556
  // wan2.7 removed shot_type; shot structure is described in the prompt
480
557
  warnings.push({
481
558
  type: 'unsupported',
482
559
  feature: 'shotType',
483
560
  details:
484
- 'wan2.7 models do not support the shotType option. ' +
485
- 'Describe the shot structure in the prompt instead.',
561
+ `${wan3 ? 'wan3' : 'wan2.7'} models do not support the shotType ` +
562
+ 'option. Describe the shot structure in the prompt instead.',
486
563
  });
487
564
  } else {
488
565
  parameters.shot_type = alibabaOptions.shotType;
@@ -650,6 +727,20 @@ export class AlibabaVideoModel implements Experimental_VideoModelV3 {
650
727
  finalResponse.usage.output_video_duration,
651
728
  resolution: finalResponse.usage.SR,
652
729
  size: finalResponse.usage.size,
730
+ // wan3-only. Spread rather than set to undefined so the
731
+ // metadata shape for older wan models is unchanged.
732
+ ...(finalResponse.usage.input_video_duration != null
733
+ ? {
734
+ inputVideoDuration:
735
+ finalResponse.usage.input_video_duration,
736
+ }
737
+ : {}),
738
+ ...(finalResponse.usage.fps != null
739
+ ? { fps: finalResponse.usage.fps }
740
+ : {}),
741
+ ...(finalResponse.usage.ratio != null
742
+ ? { ratio: finalResponse.usage.ratio }
743
+ : {}),
653
744
  },
654
745
  }
655
746
  : {}),
@@ -13,4 +13,6 @@ export type AlibabaVideoModelId =
13
13
  | 'wan2.6-r2v-flash'
14
14
  | 'wan2.7-r2v'
15
15
  | 'wan2.7-r2v-2026-06-12'
16
+ // All-in-One (one id serves text-, image-, and reference-to-video)
17
+ | 'wan3.0-video'
16
18
  | (string & {});