@ai-sdk/alibaba 0.0.0-6b196531-20260710185421
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 +775 -0
- package/LICENSE +13 -0
- package/README.md +151 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.js +1380 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
- package/src/alibaba-chat-language-model-options.ts +56 -0
- package/src/alibaba-chat-language-model.ts +568 -0
- package/src/alibaba-chat-prompt.ts +67 -0
- package/src/alibaba-config.ts +9 -0
- package/src/alibaba-embedding-model-options.ts +33 -0
- package/src/alibaba-embedding-model.ts +178 -0
- package/src/alibaba-error.ts +17 -0
- package/src/alibaba-provider.ts +187 -0
- package/src/alibaba-video-model-options.ts +77 -0
- package/src/alibaba-video-model.ts +587 -0
- package/src/alibaba-video-settings.ts +16 -0
- package/src/convert-alibaba-usage.ts +37 -0
- package/src/convert-to-alibaba-chat-messages.ts +220 -0
- package/src/get-cache-control.ts +49 -0
- package/src/index.ts +27 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AISDKError,
|
|
3
|
+
type Experimental_VideoModelV4,
|
|
4
|
+
type Experimental_VideoModelV4File,
|
|
5
|
+
type SharedV4Warning,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
7
|
+
import {
|
|
8
|
+
combineHeaders,
|
|
9
|
+
convertImageModelFileToDataUri,
|
|
10
|
+
convertUint8ArrayToBase64,
|
|
11
|
+
createJsonErrorResponseHandler,
|
|
12
|
+
createJsonResponseHandler,
|
|
13
|
+
delay,
|
|
14
|
+
getFromApi,
|
|
15
|
+
parseProviderOptions,
|
|
16
|
+
postJsonToApi,
|
|
17
|
+
resolve,
|
|
18
|
+
type FetchFunction,
|
|
19
|
+
type Resolvable,
|
|
20
|
+
} from '@ai-sdk/provider-utils';
|
|
21
|
+
import { z } from 'zod/v4';
|
|
22
|
+
import {
|
|
23
|
+
alibabaVideoModelOptionsSchema,
|
|
24
|
+
type AlibabaVideoModelOptions,
|
|
25
|
+
} from './alibaba-video-model-options';
|
|
26
|
+
import type { AlibabaVideoModelId } from './alibaba-video-settings';
|
|
27
|
+
|
|
28
|
+
interface AlibabaVideoModelConfig {
|
|
29
|
+
provider: string;
|
|
30
|
+
baseURL: string;
|
|
31
|
+
headers: Resolvable<Record<string, string | undefined>>;
|
|
32
|
+
fetch?: FetchFunction;
|
|
33
|
+
_internal?: {
|
|
34
|
+
currentDate?: () => Date;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// DashScope native API error format (different from OpenAI-compatible endpoint)
|
|
39
|
+
const alibabaVideoErrorSchema = z.object({
|
|
40
|
+
code: z.string().nullish(),
|
|
41
|
+
message: z.string(),
|
|
42
|
+
request_id: z.string().nullish(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const alibabaVideoFailedResponseHandler = createJsonErrorResponseHandler({
|
|
46
|
+
errorSchema: alibabaVideoErrorSchema,
|
|
47
|
+
errorToMessage: data => data.message,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const alibabaVideoCreateTaskSchema = z.object({
|
|
51
|
+
output: z
|
|
52
|
+
.object({
|
|
53
|
+
task_status: z.string(),
|
|
54
|
+
task_id: z.string(),
|
|
55
|
+
})
|
|
56
|
+
.nullish(),
|
|
57
|
+
request_id: z.string().nullish(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const alibabaVideoTaskStatusSchema = z.object({
|
|
61
|
+
output: z
|
|
62
|
+
.object({
|
|
63
|
+
task_id: z.string(),
|
|
64
|
+
task_status: z.string(),
|
|
65
|
+
video_url: z.string().nullish(),
|
|
66
|
+
submit_time: z.string().nullish(),
|
|
67
|
+
scheduled_time: z.string().nullish(),
|
|
68
|
+
end_time: z.string().nullish(),
|
|
69
|
+
orig_prompt: z.string().nullish(),
|
|
70
|
+
actual_prompt: z.string().nullish(),
|
|
71
|
+
code: z.string().nullish(),
|
|
72
|
+
message: z.string().nullish(),
|
|
73
|
+
})
|
|
74
|
+
.nullish(),
|
|
75
|
+
usage: z
|
|
76
|
+
.object({
|
|
77
|
+
duration: z.number().nullish(),
|
|
78
|
+
output_video_duration: z.number().nullish(),
|
|
79
|
+
SR: z.number().nullish(),
|
|
80
|
+
size: z.string().nullish(),
|
|
81
|
+
})
|
|
82
|
+
.nullish(),
|
|
83
|
+
request_id: z.string().nullish(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
type AlibabaVideoTaskStatusResponse = z.infer<
|
|
87
|
+
typeof alibabaVideoTaskStatusSchema
|
|
88
|
+
>;
|
|
89
|
+
|
|
90
|
+
function detectMode(modelId: string): 't2v' | 'i2v' | 'r2v' {
|
|
91
|
+
if (modelId.includes('-i2v')) return 'i2v';
|
|
92
|
+
if (modelId.includes('-r2v')) return 'r2v';
|
|
93
|
+
return 't2v';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// wan2.7 models use a different protocol than earlier wan models:
|
|
97
|
+
// resolution tiers + ratio instead of size, input.media instead of
|
|
98
|
+
// input.reference_urls (R2V), and no shot_type or audio parameters.
|
|
99
|
+
function isWan27Model(modelId: string): boolean {
|
|
100
|
+
return modelId.startsWith('wan2.7');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Maps SDK "WIDTHxHEIGHT" resolutions to Alibaba resolution tiers.
|
|
104
|
+
const resolutionTierMap: Record<string, string> = {
|
|
105
|
+
'1280x720': '720P',
|
|
106
|
+
'720x1280': '720P',
|
|
107
|
+
'960x960': '720P',
|
|
108
|
+
'1088x832': '720P',
|
|
109
|
+
'832x1088': '720P',
|
|
110
|
+
'1920x1080': '1080P',
|
|
111
|
+
'1080x1920': '1080P',
|
|
112
|
+
'1440x1440': '1080P',
|
|
113
|
+
'1632x1248': '1080P',
|
|
114
|
+
'1248x1632': '1080P',
|
|
115
|
+
'832x480': '480P',
|
|
116
|
+
'480x832': '480P',
|
|
117
|
+
'624x624': '480P',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const supportedRatios = new Set(['16:9', '9:16', '1:1', '4:3', '3:4']);
|
|
121
|
+
|
|
122
|
+
function deriveRatioFromResolution(
|
|
123
|
+
resolution: `${number}x${number}`,
|
|
124
|
+
): string | undefined {
|
|
125
|
+
const [width, height] = resolution.split('x').map(Number);
|
|
126
|
+
if (
|
|
127
|
+
!Number.isInteger(width) ||
|
|
128
|
+
!Number.isInteger(height) ||
|
|
129
|
+
width <= 0 ||
|
|
130
|
+
height <= 0
|
|
131
|
+
) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
let a = width;
|
|
135
|
+
let b = height;
|
|
136
|
+
while (b !== 0) {
|
|
137
|
+
[a, b] = [b, a % b];
|
|
138
|
+
}
|
|
139
|
+
const ratio = `${width / a}:${height / a}`;
|
|
140
|
+
return supportedRatios.has(ratio) ? ratio : undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function fileToImageString(file: Experimental_VideoModelV4File): string {
|
|
144
|
+
if (file.type === 'url') {
|
|
145
|
+
return file.url;
|
|
146
|
+
}
|
|
147
|
+
return typeof file.data === 'string'
|
|
148
|
+
? file.data
|
|
149
|
+
: convertUint8ArrayToBase64(file.data);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getFirstFrameImage(
|
|
153
|
+
options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
|
|
154
|
+
): Experimental_VideoModelV4File | undefined {
|
|
155
|
+
return options.frameImages?.find(frame => frame.frameType === 'first_frame')
|
|
156
|
+
?.image;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function resolveStartImage(
|
|
160
|
+
options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
|
|
161
|
+
): Experimental_VideoModelV4File | undefined {
|
|
162
|
+
return getFirstFrameImage(options) ?? options.image;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isVideoUrl(url: string): boolean {
|
|
166
|
+
return /\.(mp4|mov)([?#]|$)/i.test(url);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Builds the wan2.7 input.media array from inputReferences and frameImages.
|
|
170
|
+
function resolveMedia(
|
|
171
|
+
options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
|
|
172
|
+
alibabaOptions: AlibabaVideoModelOptions | undefined,
|
|
173
|
+
warnings: SharedV4Warning[],
|
|
174
|
+
): Array<Record<string, unknown>> | undefined {
|
|
175
|
+
if (alibabaOptions?.media != null && alibabaOptions.media.length > 0) {
|
|
176
|
+
return alibabaOptions.media.map(item => ({
|
|
177
|
+
type: item.type,
|
|
178
|
+
url: item.url,
|
|
179
|
+
...(item.referenceVoice != null
|
|
180
|
+
? { reference_voice: item.referenceVoice }
|
|
181
|
+
: {}),
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const media: Array<Record<string, unknown>> = [];
|
|
186
|
+
|
|
187
|
+
for (const reference of options.inputReferences ?? []) {
|
|
188
|
+
if (reference.type === 'url') {
|
|
189
|
+
media.push({
|
|
190
|
+
type: isVideoUrl(reference.url) ? 'reference_video' : 'reference_image',
|
|
191
|
+
url: reference.url,
|
|
192
|
+
});
|
|
193
|
+
} else if (reference.mediaType.startsWith('image/')) {
|
|
194
|
+
media.push({
|
|
195
|
+
type: 'reference_image',
|
|
196
|
+
url: convertImageModelFileToDataUri(reference),
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
warnings.push({
|
|
200
|
+
type: 'unsupported',
|
|
201
|
+
feature: 'inputReferences',
|
|
202
|
+
details:
|
|
203
|
+
'Alibaba reference-to-video requires URL references for videos. ' +
|
|
204
|
+
'Non-URL video reference was skipped.',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const firstFrame = getFirstFrameImage(options);
|
|
210
|
+
if (firstFrame != null) {
|
|
211
|
+
media.push({
|
|
212
|
+
type: 'first_frame',
|
|
213
|
+
url: convertImageModelFileToDataUri(firstFrame),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return media.length > 0 ? media : undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function resolveReferenceUrls(
|
|
221
|
+
options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
|
|
222
|
+
alibabaOptions: AlibabaVideoModelOptions | undefined,
|
|
223
|
+
warnings: SharedV4Warning[],
|
|
224
|
+
): string[] | undefined {
|
|
225
|
+
if (options.frameImages != null && options.frameImages.length > 0) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (options.inputReferences != null && options.inputReferences.length > 0) {
|
|
230
|
+
const urls: string[] = [];
|
|
231
|
+
|
|
232
|
+
for (const reference of options.inputReferences) {
|
|
233
|
+
if (reference.type === 'url') {
|
|
234
|
+
urls.push(reference.url);
|
|
235
|
+
} else {
|
|
236
|
+
warnings.push({
|
|
237
|
+
type: 'unsupported',
|
|
238
|
+
feature: 'inputReferences',
|
|
239
|
+
details:
|
|
240
|
+
'Alibaba reference-to-video requires URL references. ' +
|
|
241
|
+
'Non-URL reference was skipped.',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return urls.length > 0 ? urls : undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return alibabaOptions?.referenceUrls ?? undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export class AlibabaVideoModel implements Experimental_VideoModelV4 {
|
|
253
|
+
readonly specificationVersion = 'v4';
|
|
254
|
+
readonly maxVideosPerCall = 1;
|
|
255
|
+
|
|
256
|
+
get provider(): string {
|
|
257
|
+
return this.config.provider;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
constructor(
|
|
261
|
+
readonly modelId: AlibabaVideoModelId,
|
|
262
|
+
private readonly config: AlibabaVideoModelConfig,
|
|
263
|
+
) {}
|
|
264
|
+
|
|
265
|
+
async doGenerate(
|
|
266
|
+
options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
|
|
267
|
+
): Promise<Awaited<ReturnType<Experimental_VideoModelV4['doGenerate']>>> {
|
|
268
|
+
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
269
|
+
const warnings: SharedV4Warning[] = [];
|
|
270
|
+
const mode = detectMode(this.modelId);
|
|
271
|
+
|
|
272
|
+
const alibabaOptions = (await parseProviderOptions({
|
|
273
|
+
provider: 'alibaba',
|
|
274
|
+
providerOptions: options.providerOptions,
|
|
275
|
+
schema: alibabaVideoModelOptionsSchema,
|
|
276
|
+
})) as AlibabaVideoModelOptions | undefined;
|
|
277
|
+
|
|
278
|
+
// Build input object
|
|
279
|
+
const input: Record<string, unknown> = {};
|
|
280
|
+
|
|
281
|
+
if (options.prompt != null) {
|
|
282
|
+
input.prompt = options.prompt;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (alibabaOptions?.negativePrompt != null) {
|
|
286
|
+
input.negative_prompt = alibabaOptions.negativePrompt;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (alibabaOptions?.audioUrl != null) {
|
|
290
|
+
input.audio_url = alibabaOptions.audioUrl;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const startImage = resolveStartImage(options);
|
|
294
|
+
const wan27 = isWan27Model(this.modelId);
|
|
295
|
+
// wan2.7 T2V and R2V take an explicit aspect ratio (I2V follows the input image)
|
|
296
|
+
const supportsRatio = wan27 && mode !== 'i2v';
|
|
297
|
+
|
|
298
|
+
// Handle image input for I2V mode
|
|
299
|
+
if (mode === 'i2v' && startImage != null) {
|
|
300
|
+
input.img_url = fileToImageString(startImage);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Handle references for R2V mode
|
|
304
|
+
if (mode === 'r2v') {
|
|
305
|
+
if (wan27) {
|
|
306
|
+
// wan2.7: input.media
|
|
307
|
+
const media = resolveMedia(options, alibabaOptions, warnings);
|
|
308
|
+
if (media != null) {
|
|
309
|
+
input.media = media;
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
// wan2.6: legacy protocol with input.reference_urls
|
|
313
|
+
const referenceUrls = resolveReferenceUrls(
|
|
314
|
+
options,
|
|
315
|
+
alibabaOptions,
|
|
316
|
+
warnings,
|
|
317
|
+
);
|
|
318
|
+
if (referenceUrls != null && referenceUrls.length > 0) {
|
|
319
|
+
input.reference_urls = referenceUrls;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const lastFrame = options.frameImages?.find(
|
|
325
|
+
frame => frame.frameType === 'last_frame',
|
|
326
|
+
)?.image;
|
|
327
|
+
|
|
328
|
+
if (lastFrame != null) {
|
|
329
|
+
warnings.push({
|
|
330
|
+
type: 'unsupported',
|
|
331
|
+
feature: 'frameImages',
|
|
332
|
+
details:
|
|
333
|
+
'This model does not support last_frame. ' +
|
|
334
|
+
'The last frame image was ignored.',
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (
|
|
339
|
+
options.inputReferences != null &&
|
|
340
|
+
options.inputReferences.length > 0 &&
|
|
341
|
+
mode !== 'r2v'
|
|
342
|
+
) {
|
|
343
|
+
warnings.push({
|
|
344
|
+
type: 'unsupported',
|
|
345
|
+
feature: 'inputReferences',
|
|
346
|
+
details:
|
|
347
|
+
'Alibaba only supports inputReferences (reference-to-video) on ' +
|
|
348
|
+
'reference-to-video models. The reference images were ignored.',
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Build parameters object
|
|
353
|
+
const parameters: Record<string, unknown> = {};
|
|
354
|
+
|
|
355
|
+
if (options.duration != null) {
|
|
356
|
+
parameters.duration = options.duration;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (options.seed != null) {
|
|
360
|
+
parameters.seed = options.seed;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Resolution / Size mapping
|
|
364
|
+
if (options.resolution != null) {
|
|
365
|
+
if (mode === 'i2v' || wan27) {
|
|
366
|
+
// I2V and wan2.7 models use "720P" / "1080P" format
|
|
367
|
+
const resolutionTier =
|
|
368
|
+
resolutionTierMap[options.resolution] || options.resolution;
|
|
369
|
+
if (wan27 && resolutionTier !== '720P' && resolutionTier !== '1080P') {
|
|
370
|
+
warnings.push({
|
|
371
|
+
type: 'unsupported',
|
|
372
|
+
feature: 'resolution',
|
|
373
|
+
details:
|
|
374
|
+
'wan2.7 models only support 720P and 1080P ' +
|
|
375
|
+
`resolutions. The resolution "${options.resolution}" was ignored.`,
|
|
376
|
+
});
|
|
377
|
+
} else {
|
|
378
|
+
parameters.resolution = resolutionTier;
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
// wan2.6 T2V and R2V use "WIDTH*HEIGHT" format for the size parameter
|
|
382
|
+
// Convert "WIDTHxHEIGHT" (SDK standard) to "WIDTH*HEIGHT" (Alibaba API)
|
|
383
|
+
parameters.size = options.resolution.replace('x', '*');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// wan2.7 T2V and R2V support an explicit aspect ratio parameter
|
|
388
|
+
if (supportsRatio) {
|
|
389
|
+
const ratio =
|
|
390
|
+
alibabaOptions?.ratio ??
|
|
391
|
+
options.aspectRatio ??
|
|
392
|
+
(options.resolution != null
|
|
393
|
+
? deriveRatioFromResolution(options.resolution)
|
|
394
|
+
: undefined);
|
|
395
|
+
if (ratio != null) {
|
|
396
|
+
parameters.ratio = ratio;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Provider-specific parameters
|
|
401
|
+
if (alibabaOptions?.promptExtend != null) {
|
|
402
|
+
parameters.prompt_extend = alibabaOptions.promptExtend;
|
|
403
|
+
}
|
|
404
|
+
if (alibabaOptions?.shotType != null) {
|
|
405
|
+
if (wan27) {
|
|
406
|
+
// wan2.7 removed shot_type; shot structure is described in the prompt
|
|
407
|
+
warnings.push({
|
|
408
|
+
type: 'unsupported',
|
|
409
|
+
feature: 'shotType',
|
|
410
|
+
details:
|
|
411
|
+
'wan2.7 models do not support the shotType option. ' +
|
|
412
|
+
'Describe the shot structure in the prompt instead.',
|
|
413
|
+
});
|
|
414
|
+
} else {
|
|
415
|
+
parameters.shot_type = alibabaOptions.shotType;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (alibabaOptions?.watermark != null) {
|
|
419
|
+
parameters.watermark = alibabaOptions.watermark;
|
|
420
|
+
}
|
|
421
|
+
const audio = options.generateAudio ?? alibabaOptions?.audio;
|
|
422
|
+
if (audio != null) {
|
|
423
|
+
if (wan27) {
|
|
424
|
+
// wan2.7 does not have an audio parameter (audio is always generated)
|
|
425
|
+
warnings.push({
|
|
426
|
+
type: 'unsupported',
|
|
427
|
+
feature: 'generateAudio',
|
|
428
|
+
details:
|
|
429
|
+
'wan2.7 models always generate audio. ' +
|
|
430
|
+
'The audio option was ignored.',
|
|
431
|
+
});
|
|
432
|
+
} else {
|
|
433
|
+
parameters.audio = audio;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Warn about unsupported standard options
|
|
438
|
+
if (options.aspectRatio && !supportsRatio) {
|
|
439
|
+
warnings.push({
|
|
440
|
+
type: 'unsupported',
|
|
441
|
+
feature: 'aspectRatio',
|
|
442
|
+
details:
|
|
443
|
+
'Alibaba video models use explicit size/resolution dimensions. Use the resolution option or providerOptions.alibaba for size control.',
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
if (options.fps) {
|
|
447
|
+
warnings.push({
|
|
448
|
+
type: 'unsupported',
|
|
449
|
+
feature: 'fps',
|
|
450
|
+
details: 'Alibaba video models do not support custom FPS.',
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
if (options.n != null && options.n > 1) {
|
|
454
|
+
warnings.push({
|
|
455
|
+
type: 'unsupported',
|
|
456
|
+
feature: 'n',
|
|
457
|
+
details:
|
|
458
|
+
'Alibaba video models only support generating 1 video per call.',
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Step 1: Create task
|
|
463
|
+
const { value: createResponse } = await postJsonToApi({
|
|
464
|
+
url: `${this.config.baseURL}/api/v1/services/aigc/video-generation/video-synthesis`,
|
|
465
|
+
headers: combineHeaders(
|
|
466
|
+
await resolve(this.config.headers),
|
|
467
|
+
options.headers,
|
|
468
|
+
{
|
|
469
|
+
'X-DashScope-Async': 'enable',
|
|
470
|
+
},
|
|
471
|
+
),
|
|
472
|
+
body: {
|
|
473
|
+
model: this.modelId,
|
|
474
|
+
input,
|
|
475
|
+
parameters,
|
|
476
|
+
},
|
|
477
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
478
|
+
alibabaVideoCreateTaskSchema,
|
|
479
|
+
),
|
|
480
|
+
failedResponseHandler: alibabaVideoFailedResponseHandler,
|
|
481
|
+
abortSignal: options.abortSignal,
|
|
482
|
+
fetch: this.config.fetch,
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const taskId = createResponse.output?.task_id;
|
|
486
|
+
if (!taskId) {
|
|
487
|
+
throw new AISDKError({
|
|
488
|
+
name: 'ALIBABA_VIDEO_GENERATION_ERROR',
|
|
489
|
+
message: `No task_id returned from Alibaba API. Response: ${JSON.stringify(createResponse)}`,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Step 2: Poll for task completion
|
|
494
|
+
const pollIntervalMs = alibabaOptions?.pollIntervalMs ?? 5000;
|
|
495
|
+
const pollTimeoutMs = alibabaOptions?.pollTimeoutMs ?? 600000;
|
|
496
|
+
const startTime = Date.now();
|
|
497
|
+
let finalResponse: AlibabaVideoTaskStatusResponse | undefined;
|
|
498
|
+
let responseHeaders: Record<string, string> | undefined;
|
|
499
|
+
|
|
500
|
+
while (true) {
|
|
501
|
+
await delay(pollIntervalMs, { abortSignal: options.abortSignal });
|
|
502
|
+
|
|
503
|
+
if (Date.now() - startTime > pollTimeoutMs) {
|
|
504
|
+
throw new AISDKError({
|
|
505
|
+
name: 'ALIBABA_VIDEO_GENERATION_TIMEOUT',
|
|
506
|
+
message: `Video generation timed out after ${pollTimeoutMs}ms`,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const { value: statusResponse, responseHeaders: pollHeaders } =
|
|
511
|
+
await getFromApi({
|
|
512
|
+
url: `${this.config.baseURL}/api/v1/tasks/${taskId}`,
|
|
513
|
+
headers: combineHeaders(
|
|
514
|
+
await resolve(this.config.headers),
|
|
515
|
+
options.headers,
|
|
516
|
+
),
|
|
517
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
518
|
+
alibabaVideoTaskStatusSchema,
|
|
519
|
+
),
|
|
520
|
+
failedResponseHandler: alibabaVideoFailedResponseHandler,
|
|
521
|
+
abortSignal: options.abortSignal,
|
|
522
|
+
fetch: this.config.fetch,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
responseHeaders = pollHeaders;
|
|
526
|
+
const taskStatus = statusResponse.output?.task_status;
|
|
527
|
+
|
|
528
|
+
if (taskStatus === 'SUCCEEDED') {
|
|
529
|
+
finalResponse = statusResponse;
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (taskStatus === 'FAILED' || taskStatus === 'CANCELED') {
|
|
534
|
+
throw new AISDKError({
|
|
535
|
+
name: 'ALIBABA_VIDEO_GENERATION_FAILED',
|
|
536
|
+
message: `Video generation ${taskStatus.toLowerCase()}. Task ID: ${taskId}. ${statusResponse.output?.message ?? ''}`,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Continue polling for PENDING, RUNNING, UNKNOWN statuses
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const videoUrl = finalResponse?.output?.video_url;
|
|
544
|
+
if (!videoUrl) {
|
|
545
|
+
throw new AISDKError({
|
|
546
|
+
name: 'ALIBABA_VIDEO_GENERATION_ERROR',
|
|
547
|
+
message: `No video URL in response. Task ID: ${taskId}`,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
videos: [
|
|
553
|
+
{
|
|
554
|
+
type: 'url',
|
|
555
|
+
url: videoUrl,
|
|
556
|
+
mediaType: 'video/mp4',
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
warnings,
|
|
560
|
+
response: {
|
|
561
|
+
timestamp: currentDate,
|
|
562
|
+
modelId: this.modelId,
|
|
563
|
+
headers: responseHeaders,
|
|
564
|
+
},
|
|
565
|
+
providerMetadata: {
|
|
566
|
+
alibaba: {
|
|
567
|
+
taskId,
|
|
568
|
+
videoUrl,
|
|
569
|
+
...(finalResponse?.output?.actual_prompt
|
|
570
|
+
? { actualPrompt: finalResponse.output.actual_prompt }
|
|
571
|
+
: {}),
|
|
572
|
+
...(finalResponse?.usage
|
|
573
|
+
? {
|
|
574
|
+
usage: {
|
|
575
|
+
duration: finalResponse.usage.duration,
|
|
576
|
+
outputVideoDuration:
|
|
577
|
+
finalResponse.usage.output_video_duration,
|
|
578
|
+
resolution: finalResponse.usage.SR,
|
|
579
|
+
size: finalResponse.usage.size,
|
|
580
|
+
},
|
|
581
|
+
}
|
|
582
|
+
: {}),
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// https://www.alibabacloud.com/help/en/model-studio/use-video-generation
|
|
2
|
+
export type AlibabaVideoModelId =
|
|
3
|
+
// Text-to-Video
|
|
4
|
+
| 'wan2.6-t2v'
|
|
5
|
+
| 'wan2.5-t2v-preview'
|
|
6
|
+
| 'wan2.7-t2v'
|
|
7
|
+
| 'wan2.7-t2v-2026-06-12'
|
|
8
|
+
// Image-to-Video (first frame)
|
|
9
|
+
| 'wan2.6-i2v'
|
|
10
|
+
| 'wan2.6-i2v-flash'
|
|
11
|
+
// Reference-to-Video
|
|
12
|
+
| 'wan2.6-r2v'
|
|
13
|
+
| 'wan2.6-r2v-flash'
|
|
14
|
+
| 'wan2.7-r2v'
|
|
15
|
+
| 'wan2.7-r2v-2026-06-12'
|
|
16
|
+
| (string & {});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { convertOpenAICompatibleChatUsage } from '@ai-sdk/openai-compatible/internal';
|
|
2
|
+
import type { LanguageModelV4Usage } from '@ai-sdk/provider';
|
|
3
|
+
|
|
4
|
+
export type AlibabaUsage = {
|
|
5
|
+
prompt_tokens?: number | null;
|
|
6
|
+
completion_tokens?: number | null;
|
|
7
|
+
total_tokens?: number | null;
|
|
8
|
+
prompt_tokens_details?: {
|
|
9
|
+
cached_tokens?: number | null;
|
|
10
|
+
cache_creation_input_tokens?: number | null;
|
|
11
|
+
} | null;
|
|
12
|
+
completion_tokens_details?: {
|
|
13
|
+
reasoning_tokens?: number | null;
|
|
14
|
+
} | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function convertAlibabaUsage(
|
|
18
|
+
usage: AlibabaUsage | undefined | null,
|
|
19
|
+
): LanguageModelV4Usage {
|
|
20
|
+
const baseUsage = convertOpenAICompatibleChatUsage(usage);
|
|
21
|
+
|
|
22
|
+
const cacheWriteTokens =
|
|
23
|
+
usage?.prompt_tokens_details?.cache_creation_input_tokens ?? 0;
|
|
24
|
+
const noCacheTokens =
|
|
25
|
+
(baseUsage.inputTokens.total ?? 0) -
|
|
26
|
+
(baseUsage.inputTokens.cacheRead ?? 0) -
|
|
27
|
+
cacheWriteTokens;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
...baseUsage,
|
|
31
|
+
inputTokens: {
|
|
32
|
+
...baseUsage.inputTokens,
|
|
33
|
+
cacheWrite: cacheWriteTokens,
|
|
34
|
+
noCache: noCacheTokens,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|