@ai-sdk/fal 3.0.52 → 3.0.54
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 +18 -0
- package/dist/index.d.ts +140 -131
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +959 -1233
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,1260 +1,986 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
NoSuchModelError
|
|
4
|
-
} from "@ai-sdk/provider";
|
|
5
|
-
import {
|
|
6
|
-
withoutTrailingSlash,
|
|
7
|
-
withUserAgentSuffix
|
|
8
|
-
} from "@ai-sdk/provider-utils";
|
|
9
|
-
|
|
10
|
-
// src/fal-image-model.ts
|
|
11
|
-
import {
|
|
12
|
-
combineHeaders,
|
|
13
|
-
convertImageModelFileToDataUri,
|
|
14
|
-
createBinaryResponseHandler,
|
|
15
|
-
createJsonErrorResponseHandler,
|
|
16
|
-
createJsonResponseHandler,
|
|
17
|
-
createStatusCodeErrorResponseHandler,
|
|
18
|
-
getFromApi,
|
|
19
|
-
parseProviderOptions,
|
|
20
|
-
postJsonToApi,
|
|
21
|
-
resolve,
|
|
22
|
-
serializeModelOptions,
|
|
23
|
-
WORKFLOW_SERIALIZE,
|
|
24
|
-
WORKFLOW_DESERIALIZE
|
|
25
|
-
} from "@ai-sdk/provider-utils";
|
|
26
|
-
import { z as z2 } from "zod/v4";
|
|
27
|
-
|
|
28
|
-
// src/fal-image-model-options.ts
|
|
29
|
-
import {
|
|
30
|
-
lazySchema,
|
|
31
|
-
zodSchema
|
|
32
|
-
} from "@ai-sdk/provider-utils";
|
|
1
|
+
import { AISDKError, APICallError, NoSuchModelError } from "@ai-sdk/provider";
|
|
2
|
+
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE, combineHeaders, convertImageModelFileToDataUri, convertUint8ArrayToBase64, createBinaryResponseHandler, createJsonErrorResponseHandler, createJsonResponseHandler, createStatusCodeErrorResponseHandler, delay, getFromApi, lazySchema, parseProviderOptions, postJsonToApi, resolve, serializeModelOptions, withUserAgentSuffix, withoutTrailingSlash, zodSchema } from "@ai-sdk/provider-utils";
|
|
33
3
|
import { z } from "zod/v4";
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
targetImages.map(
|
|
294
|
-
(image) => this.downloadImage(image.url, options.abortSignal)
|
|
295
|
-
)
|
|
296
|
-
);
|
|
297
|
-
return {
|
|
298
|
-
images: downloadedImages,
|
|
299
|
-
warnings,
|
|
300
|
-
response: {
|
|
301
|
-
modelId: this.modelId,
|
|
302
|
-
timestamp: currentDate,
|
|
303
|
-
headers: responseHeaders
|
|
304
|
-
},
|
|
305
|
-
providerMetadata: {
|
|
306
|
-
fal: {
|
|
307
|
-
images: targetImages.map((image, index) => {
|
|
308
|
-
const {
|
|
309
|
-
url: _url,
|
|
310
|
-
content_type: contentType,
|
|
311
|
-
file_name: fileName,
|
|
312
|
-
file_data: fileData,
|
|
313
|
-
file_size: fileSize,
|
|
314
|
-
...imageMetaData
|
|
315
|
-
} = image;
|
|
316
|
-
const nsfw = has_nsfw_concepts?.[index] ?? nsfw_content_detected?.[index];
|
|
317
|
-
return {
|
|
318
|
-
...imageMetaData,
|
|
319
|
-
...removeOnlyUndefined({
|
|
320
|
-
contentType,
|
|
321
|
-
fileName,
|
|
322
|
-
fileData,
|
|
323
|
-
fileSize,
|
|
324
|
-
nsfw
|
|
325
|
-
})
|
|
326
|
-
};
|
|
327
|
-
}),
|
|
328
|
-
...responseMetaData
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
async downloadImage(url, abortSignal) {
|
|
334
|
-
const { value: response } = await getFromApi({
|
|
335
|
-
url,
|
|
336
|
-
// url is a generated-image URL from the provider response; validate it.
|
|
337
|
-
validateUrl: true,
|
|
338
|
-
trustedOrigin: this.config.baseURL,
|
|
339
|
-
// No specific headers should be needed for this request as it's a
|
|
340
|
-
// generated image provided by fal.ai.
|
|
341
|
-
abortSignal,
|
|
342
|
-
failedResponseHandler: createStatusCodeErrorResponseHandler(),
|
|
343
|
-
successfulResponseHandler: createBinaryResponseHandler(),
|
|
344
|
-
fetch: this.config.fetch
|
|
345
|
-
});
|
|
346
|
-
return response;
|
|
347
|
-
}
|
|
4
|
+
//#region src/fal-image-model-options.ts
|
|
5
|
+
const falImageModelOptionsSchema = lazySchema(() => zodSchema(z.looseObject({
|
|
6
|
+
/** @deprecated use prompt.images instead */
|
|
7
|
+
imageUrl: z.string().nullish().meta({
|
|
8
|
+
deprecated: true,
|
|
9
|
+
description: "Use `prompt.images` instead"
|
|
10
|
+
}),
|
|
11
|
+
maskUrl: z.string().nullish().meta({
|
|
12
|
+
deprecated: true,
|
|
13
|
+
description: "Use `prompt.mask` instead"
|
|
14
|
+
}),
|
|
15
|
+
guidanceScale: z.number().min(1).max(20).nullish(),
|
|
16
|
+
numInferenceSteps: z.number().min(1).max(50).nullish(),
|
|
17
|
+
enableSafetyChecker: z.boolean().nullish(),
|
|
18
|
+
outputFormat: z.enum(["jpeg", "png"]).nullish(),
|
|
19
|
+
syncMode: z.boolean().nullish(),
|
|
20
|
+
strength: z.number().nullish(),
|
|
21
|
+
acceleration: z.enum([
|
|
22
|
+
"none",
|
|
23
|
+
"regular",
|
|
24
|
+
"high"
|
|
25
|
+
]).nullish(),
|
|
26
|
+
safetyTolerance: z.enum([
|
|
27
|
+
"1",
|
|
28
|
+
"2",
|
|
29
|
+
"3",
|
|
30
|
+
"4",
|
|
31
|
+
"5",
|
|
32
|
+
"6"
|
|
33
|
+
]).or(z.number().min(1).max(6)).nullish(),
|
|
34
|
+
/**
|
|
35
|
+
* When true, converts multiple input images to `image_urls` array instead of `image_url` string.
|
|
36
|
+
*/
|
|
37
|
+
useMultipleImages: z.boolean().nullish(),
|
|
38
|
+
image_url: z.string().nullish(),
|
|
39
|
+
mask_url: z.string().nullish(),
|
|
40
|
+
guidance_scale: z.number().min(1).max(20).nullish(),
|
|
41
|
+
num_inference_steps: z.number().min(1).max(50).nullish(),
|
|
42
|
+
enable_safety_checker: z.boolean().nullish(),
|
|
43
|
+
output_format: z.enum(["jpeg", "png"]).nullish(),
|
|
44
|
+
sync_mode: z.boolean().nullish(),
|
|
45
|
+
safety_tolerance: z.enum([
|
|
46
|
+
"1",
|
|
47
|
+
"2",
|
|
48
|
+
"3",
|
|
49
|
+
"4",
|
|
50
|
+
"5",
|
|
51
|
+
"6"
|
|
52
|
+
]).or(z.number().min(1).max(6)).nullish()
|
|
53
|
+
}).transform((data) => {
|
|
54
|
+
const result = {};
|
|
55
|
+
const deprecatedKeys = [];
|
|
56
|
+
const mapKey = (snakeKey, camelKey) => {
|
|
57
|
+
const snakeValue = data[snakeKey];
|
|
58
|
+
const camelValue = data[camelKey];
|
|
59
|
+
if (snakeValue !== void 0 && snakeValue !== null) {
|
|
60
|
+
deprecatedKeys.push(snakeKey);
|
|
61
|
+
result[camelKey] = snakeValue;
|
|
62
|
+
} else if (camelValue !== void 0 && camelValue !== null) result[camelKey] = camelValue;
|
|
63
|
+
};
|
|
64
|
+
mapKey("image_url", "imageUrl");
|
|
65
|
+
mapKey("mask_url", "maskUrl");
|
|
66
|
+
mapKey("guidance_scale", "guidanceScale");
|
|
67
|
+
mapKey("num_inference_steps", "numInferenceSteps");
|
|
68
|
+
mapKey("enable_safety_checker", "enableSafetyChecker");
|
|
69
|
+
mapKey("output_format", "outputFormat");
|
|
70
|
+
mapKey("sync_mode", "syncMode");
|
|
71
|
+
mapKey("safety_tolerance", "safetyTolerance");
|
|
72
|
+
if (data.strength !== void 0 && data.strength !== null) result.strength = data.strength;
|
|
73
|
+
if (data.acceleration !== void 0 && data.acceleration !== null) result.acceleration = data.acceleration;
|
|
74
|
+
if (data.useMultipleImages !== void 0 && data.useMultipleImages !== null) result.useMultipleImages = data.useMultipleImages;
|
|
75
|
+
for (const [key, value] of Object.entries(data)) if (![
|
|
76
|
+
"imageUrl",
|
|
77
|
+
"maskUrl",
|
|
78
|
+
"guidanceScale",
|
|
79
|
+
"numInferenceSteps",
|
|
80
|
+
"enableSafetyChecker",
|
|
81
|
+
"outputFormat",
|
|
82
|
+
"syncMode",
|
|
83
|
+
"strength",
|
|
84
|
+
"acceleration",
|
|
85
|
+
"safetyTolerance",
|
|
86
|
+
"useMultipleImages",
|
|
87
|
+
"image_url",
|
|
88
|
+
"mask_url",
|
|
89
|
+
"guidance_scale",
|
|
90
|
+
"num_inference_steps",
|
|
91
|
+
"enable_safety_checker",
|
|
92
|
+
"output_format",
|
|
93
|
+
"sync_mode",
|
|
94
|
+
"safety_tolerance"
|
|
95
|
+
].includes(key)) result[key] = value;
|
|
96
|
+
if (deprecatedKeys.length > 0) result.__deprecatedKeys = deprecatedKeys;
|
|
97
|
+
return result;
|
|
98
|
+
})));
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/fal-image-model.ts
|
|
101
|
+
var FalImageModel = class FalImageModel {
|
|
102
|
+
get supportsFileInputs() {
|
|
103
|
+
if ([
|
|
104
|
+
"fal-ai/flux-2/edit",
|
|
105
|
+
"fal-ai/flux-pro/kontext",
|
|
106
|
+
"fal-ai/flux-pro/kontext/max",
|
|
107
|
+
"fal-ai/flux-general/image-to-image",
|
|
108
|
+
"fal-ai/flux-general/inpainting",
|
|
109
|
+
"fal-ai/flux-lora/image-to-image",
|
|
110
|
+
"fal-ai/flux-lora/inpainting",
|
|
111
|
+
"fal-ai/flux/dev/image-to-image",
|
|
112
|
+
"fal-ai/flux/krea/image-to-image",
|
|
113
|
+
"fal-ai/recraft/v3/image-to-image"
|
|
114
|
+
].includes(this.modelId)) return true;
|
|
115
|
+
return [
|
|
116
|
+
"bria/text-to-image/3.2",
|
|
117
|
+
"fal-ai/bria/text-to-image/base",
|
|
118
|
+
"fal-ai/bria/text-to-image/fast",
|
|
119
|
+
"fal-ai/bria/text-to-image/hd",
|
|
120
|
+
"fal-ai/bytedance/dreamina/v3.1/text-to-image",
|
|
121
|
+
"fal-ai/flux-kontext-lora/text-to-image",
|
|
122
|
+
"fal-ai/recraft/v3/text-to-image",
|
|
123
|
+
"fal-ai/wan/v2.2-5b/text-to-image",
|
|
124
|
+
"fal-ai/wan/v2.2-a14b/text-to-image"
|
|
125
|
+
].includes(this.modelId) ? false : void 0;
|
|
126
|
+
}
|
|
127
|
+
get supportsMaskInputs() {
|
|
128
|
+
if (this.modelId === "fal-ai/flux-general/inpainting" || this.modelId === "fal-ai/flux-lora/inpainting") return true;
|
|
129
|
+
return this.supportsFileInputs == null ? void 0 : false;
|
|
130
|
+
}
|
|
131
|
+
get provider() {
|
|
132
|
+
return this.config.provider;
|
|
133
|
+
}
|
|
134
|
+
static [WORKFLOW_SERIALIZE](model) {
|
|
135
|
+
return serializeModelOptions({
|
|
136
|
+
modelId: model.modelId,
|
|
137
|
+
config: model.config
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
static [WORKFLOW_DESERIALIZE](options) {
|
|
141
|
+
return new FalImageModel(options.modelId, options.config);
|
|
142
|
+
}
|
|
143
|
+
constructor(modelId, config) {
|
|
144
|
+
this.modelId = modelId;
|
|
145
|
+
this.config = config;
|
|
146
|
+
this.specificationVersion = "v4";
|
|
147
|
+
this.maxImagesPerCall = 1;
|
|
148
|
+
}
|
|
149
|
+
async getArgs({ prompt, n, size, aspectRatio, seed, providerOptions, files, mask }) {
|
|
150
|
+
const warnings = [];
|
|
151
|
+
let imageSize;
|
|
152
|
+
if (size) {
|
|
153
|
+
const [width, height] = size.split("x").map(Number);
|
|
154
|
+
imageSize = {
|
|
155
|
+
width,
|
|
156
|
+
height
|
|
157
|
+
};
|
|
158
|
+
} else if (aspectRatio) imageSize = convertAspectRatioToSize(aspectRatio);
|
|
159
|
+
const falOptions = await parseProviderOptions({
|
|
160
|
+
provider: "fal",
|
|
161
|
+
providerOptions,
|
|
162
|
+
schema: falImageModelOptionsSchema
|
|
163
|
+
});
|
|
164
|
+
const requestBody = {
|
|
165
|
+
prompt,
|
|
166
|
+
seed,
|
|
167
|
+
image_size: imageSize,
|
|
168
|
+
num_images: n
|
|
169
|
+
};
|
|
170
|
+
if (files != null && files.length > 0) {
|
|
171
|
+
if (falOptions?.useMultipleImages === true) requestBody.image_urls = files.map((file) => convertImageModelFileToDataUri(file));
|
|
172
|
+
else {
|
|
173
|
+
requestBody.image_url = convertImageModelFileToDataUri(files[0]);
|
|
174
|
+
if (files.length > 1) warnings.push({
|
|
175
|
+
type: "other",
|
|
176
|
+
message: "Multiple input images provided but useMultipleImages is not enabled. Only the first image will be used. Set providerOptions.fal.useMultipleImages to true for models that support multiple images (e.g., fal-ai/flux-2/edit)."
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (mask != null) requestBody.mask_url = convertImageModelFileToDataUri(mask);
|
|
181
|
+
if (falOptions) {
|
|
182
|
+
const deprecatedKeys = "__deprecatedKeys" in falOptions ? falOptions.__deprecatedKeys : void 0;
|
|
183
|
+
if (deprecatedKeys && deprecatedKeys.length > 0) warnings.push({
|
|
184
|
+
type: "other",
|
|
185
|
+
message: `The following provider options use deprecated snake_case and will be removed in @ai-sdk/fal v2.0. Please use camelCase instead: ${deprecatedKeys.map((key) => {
|
|
186
|
+
return `'${key}' (use '${key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())}')`;
|
|
187
|
+
}).join(", ")}`
|
|
188
|
+
});
|
|
189
|
+
const fieldMapping = {
|
|
190
|
+
imageUrl: "image_url",
|
|
191
|
+
maskUrl: "mask_url",
|
|
192
|
+
guidanceScale: "guidance_scale",
|
|
193
|
+
numInferenceSteps: "num_inference_steps",
|
|
194
|
+
enableSafetyChecker: "enable_safety_checker",
|
|
195
|
+
outputFormat: "output_format",
|
|
196
|
+
syncMode: "sync_mode",
|
|
197
|
+
safetyTolerance: "safety_tolerance"
|
|
198
|
+
};
|
|
199
|
+
for (const [key, value] of Object.entries(falOptions)) {
|
|
200
|
+
if (key === "__deprecatedKeys") continue;
|
|
201
|
+
if (key === "useMultipleImages") continue;
|
|
202
|
+
const apiKey = fieldMapping[key] ?? key;
|
|
203
|
+
if (value !== void 0) requestBody[apiKey] = value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
requestBody,
|
|
208
|
+
warnings
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async doGenerate(options) {
|
|
212
|
+
const { requestBody, warnings } = await this.getArgs(options);
|
|
213
|
+
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
214
|
+
const { value, responseHeaders } = await postJsonToApi({
|
|
215
|
+
url: `${this.config.baseURL}/${this.modelId}`,
|
|
216
|
+
headers: combineHeaders(this.config.headers ? await resolve(this.config.headers) : void 0, options.headers),
|
|
217
|
+
body: requestBody,
|
|
218
|
+
failedResponseHandler: falFailedResponseHandler$1,
|
|
219
|
+
successfulResponseHandler: createJsonResponseHandler(falImageResponseSchema),
|
|
220
|
+
abortSignal: options.abortSignal,
|
|
221
|
+
fetch: this.config.fetch
|
|
222
|
+
});
|
|
223
|
+
const { images: targetImages, prompt: _prompt, has_nsfw_concepts, nsfw_content_detected, ...responseMetaData } = value;
|
|
224
|
+
return {
|
|
225
|
+
images: await Promise.all(targetImages.map((image) => this.downloadImage(image.url, options.abortSignal))),
|
|
226
|
+
warnings,
|
|
227
|
+
response: {
|
|
228
|
+
modelId: this.modelId,
|
|
229
|
+
timestamp: currentDate,
|
|
230
|
+
headers: responseHeaders
|
|
231
|
+
},
|
|
232
|
+
providerMetadata: { fal: {
|
|
233
|
+
images: targetImages.map((image, index) => {
|
|
234
|
+
const { url: _url, content_type: contentType, file_name: fileName, file_data: fileData, file_size: fileSize, ...imageMetaData } = image;
|
|
235
|
+
const nsfw = has_nsfw_concepts?.[index] ?? nsfw_content_detected?.[index];
|
|
236
|
+
return {
|
|
237
|
+
...imageMetaData,
|
|
238
|
+
...removeOnlyUndefined({
|
|
239
|
+
contentType,
|
|
240
|
+
fileName,
|
|
241
|
+
fileData,
|
|
242
|
+
fileSize,
|
|
243
|
+
nsfw
|
|
244
|
+
})
|
|
245
|
+
};
|
|
246
|
+
}),
|
|
247
|
+
...responseMetaData
|
|
248
|
+
} }
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async downloadImage(url, abortSignal) {
|
|
252
|
+
const { value: response } = await getFromApi({
|
|
253
|
+
url,
|
|
254
|
+
validateUrl: true,
|
|
255
|
+
trustedOrigin: this.config.baseURL,
|
|
256
|
+
abortSignal,
|
|
257
|
+
failedResponseHandler: createStatusCodeErrorResponseHandler(),
|
|
258
|
+
successfulResponseHandler: createBinaryResponseHandler(),
|
|
259
|
+
fetch: this.config.fetch
|
|
260
|
+
});
|
|
261
|
+
return response;
|
|
262
|
+
}
|
|
348
263
|
};
|
|
349
264
|
function removeOnlyUndefined(obj) {
|
|
350
|
-
|
|
351
|
-
Object.entries(obj).filter(([, v]) => v !== void 0)
|
|
352
|
-
);
|
|
265
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
353
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Converts an aspect ratio to an image size compatible with fal.ai APIs.
|
|
269
|
+
* @param aspectRatio - The aspect ratio to convert.
|
|
270
|
+
* @returns The image size.
|
|
271
|
+
*/
|
|
354
272
|
function convertAspectRatioToSize(aspectRatio) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
273
|
+
switch (aspectRatio) {
|
|
274
|
+
case "1:1": return "square_hd";
|
|
275
|
+
case "16:9": return "landscape_16_9";
|
|
276
|
+
case "9:16": return "portrait_16_9";
|
|
277
|
+
case "4:3": return "landscape_4_3";
|
|
278
|
+
case "3:4": return "portrait_4_3";
|
|
279
|
+
case "16:10": return {
|
|
280
|
+
width: 1280,
|
|
281
|
+
height: 800
|
|
282
|
+
};
|
|
283
|
+
case "10:16": return {
|
|
284
|
+
width: 800,
|
|
285
|
+
height: 1280
|
|
286
|
+
};
|
|
287
|
+
case "21:9": return {
|
|
288
|
+
width: 2560,
|
|
289
|
+
height: 1080
|
|
290
|
+
};
|
|
291
|
+
case "9:21": return {
|
|
292
|
+
width: 1080,
|
|
293
|
+
height: 2560
|
|
294
|
+
};
|
|
295
|
+
}
|
|
375
296
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
297
|
+
const falValidationErrorSchema = z.object({ detail: z.array(z.object({
|
|
298
|
+
loc: z.array(z.string()),
|
|
299
|
+
msg: z.string(),
|
|
300
|
+
type: z.string()
|
|
301
|
+
})) });
|
|
302
|
+
const falHttpErrorSchema = z.object({ message: z.string() });
|
|
303
|
+
const falErrorSchema = z.union([falValidationErrorSchema, falHttpErrorSchema]);
|
|
304
|
+
const falImageSchema = z.object({
|
|
305
|
+
url: z.string(),
|
|
306
|
+
width: z.number().nullish(),
|
|
307
|
+
height: z.number().nullish(),
|
|
308
|
+
content_type: z.string().nullish(),
|
|
309
|
+
file_name: z.string().nullish(),
|
|
310
|
+
file_data: z.string().optional(),
|
|
311
|
+
file_size: z.number().nullish()
|
|
384
312
|
});
|
|
385
|
-
|
|
386
|
-
|
|
313
|
+
const loraFileSchema = z.object({
|
|
314
|
+
url: z.string(),
|
|
315
|
+
content_type: z.string().optional(),
|
|
316
|
+
file_name: z.string().nullable().optional(),
|
|
317
|
+
file_data: z.string().optional(),
|
|
318
|
+
file_size: z.number().nullable().optional()
|
|
387
319
|
});
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
file_data: z2.string().optional(),
|
|
398
|
-
file_size: z2.number().nullish()
|
|
320
|
+
const commonResponseSchema = z.object({
|
|
321
|
+
timings: z.object({ inference: z.number().optional() }).optional(),
|
|
322
|
+
seed: z.number().optional(),
|
|
323
|
+
has_nsfw_concepts: z.array(z.boolean()).optional(),
|
|
324
|
+
prompt: z.string().optional(),
|
|
325
|
+
nsfw_content_detected: z.array(z.boolean()).optional(),
|
|
326
|
+
num_inference_steps: z.number().optional(),
|
|
327
|
+
debug_latents: loraFileSchema.optional(),
|
|
328
|
+
debug_per_pass_latents: loraFileSchema.optional()
|
|
399
329
|
});
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
file_size: z2.number().nullable().optional()
|
|
406
|
-
});
|
|
407
|
-
var commonResponseSchema = z2.object({
|
|
408
|
-
timings: z2.object({
|
|
409
|
-
inference: z2.number().optional()
|
|
410
|
-
}).optional(),
|
|
411
|
-
seed: z2.number().optional(),
|
|
412
|
-
has_nsfw_concepts: z2.array(z2.boolean()).optional(),
|
|
413
|
-
prompt: z2.string().optional(),
|
|
414
|
-
// https://fal.ai/models/fal-ai/lcm/api#schema-output
|
|
415
|
-
nsfw_content_detected: z2.array(z2.boolean()).optional(),
|
|
416
|
-
num_inference_steps: z2.number().optional(),
|
|
417
|
-
// https://fal.ai/models/fal-ai/lora/api#schema-output
|
|
418
|
-
debug_latents: loraFileSchema.optional(),
|
|
419
|
-
debug_per_pass_latents: loraFileSchema.optional()
|
|
420
|
-
});
|
|
421
|
-
var base = z2.looseObject(commonResponseSchema.shape);
|
|
422
|
-
var falImageResponseSchema = z2.union([
|
|
423
|
-
base.extend({ images: z2.array(falImageSchema) }),
|
|
424
|
-
base.extend({ image: falImageSchema })
|
|
425
|
-
]).transform((v) => "images" in v ? v : { ...v, images: [v.image] }).pipe(base.extend({ images: z2.array(falImageSchema) }));
|
|
330
|
+
const base = z.looseObject(commonResponseSchema.shape);
|
|
331
|
+
const falImageResponseSchema = z.union([base.extend({ images: z.array(falImageSchema) }), base.extend({ image: falImageSchema })]).transform((v) => "images" in v ? v : {
|
|
332
|
+
...v,
|
|
333
|
+
images: [v.image]
|
|
334
|
+
}).pipe(base.extend({ images: z.array(falImageSchema) }));
|
|
426
335
|
function isValidationError(error) {
|
|
427
|
-
|
|
336
|
+
return falValidationErrorSchema.safeParse(error).success;
|
|
428
337
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return error.message ?? "Unknown fal error";
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
// src/fal-transcription-model.ts
|
|
440
|
-
import {
|
|
441
|
-
AISDKError
|
|
442
|
-
} from "@ai-sdk/provider";
|
|
443
|
-
import {
|
|
444
|
-
combineHeaders as combineHeaders2,
|
|
445
|
-
convertUint8ArrayToBase64,
|
|
446
|
-
createJsonErrorResponseHandler as createJsonErrorResponseHandler3,
|
|
447
|
-
createJsonResponseHandler as createJsonResponseHandler2,
|
|
448
|
-
delay,
|
|
449
|
-
getFromApi as getFromApi2,
|
|
450
|
-
parseProviderOptions as parseProviderOptions2,
|
|
451
|
-
postJsonToApi as postJsonToApi2,
|
|
452
|
-
serializeModelOptions as serializeModelOptions2,
|
|
453
|
-
WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2,
|
|
454
|
-
WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2
|
|
455
|
-
} from "@ai-sdk/provider-utils";
|
|
456
|
-
import { z as z5 } from "zod/v4";
|
|
457
|
-
|
|
458
|
-
// src/fal-error.ts
|
|
459
|
-
import { z as z3 } from "zod/v4";
|
|
460
|
-
import { createJsonErrorResponseHandler as createJsonErrorResponseHandler2 } from "@ai-sdk/provider-utils";
|
|
461
|
-
var falErrorDataSchema = z3.object({
|
|
462
|
-
error: z3.object({
|
|
463
|
-
message: z3.string(),
|
|
464
|
-
code: z3.number()
|
|
465
|
-
})
|
|
338
|
+
const falFailedResponseHandler$1 = createJsonErrorResponseHandler({
|
|
339
|
+
errorSchema: falErrorSchema,
|
|
340
|
+
errorToMessage: (error) => {
|
|
341
|
+
if (isValidationError(error)) return error.detail.map((detail) => `${detail.loc.join(".")}: ${detail.msg}`).join("\n");
|
|
342
|
+
return error.message ?? "Unknown fal error";
|
|
343
|
+
}
|
|
466
344
|
});
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/fal-error.ts
|
|
347
|
+
const falErrorDataSchema = z.object({ error: z.object({
|
|
348
|
+
message: z.string(),
|
|
349
|
+
code: z.number()
|
|
350
|
+
}) });
|
|
351
|
+
const falFailedResponseHandler = createJsonErrorResponseHandler({
|
|
352
|
+
errorSchema: falErrorDataSchema,
|
|
353
|
+
errorToMessage: (data) => data.error.message
|
|
470
354
|
});
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
numSpeakers: z4.number().nullable().nullish()
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/fal-transcription-model-options.ts
|
|
357
|
+
const falTranscriptionModelOptionsSchema = z.object({
|
|
358
|
+
/**
|
|
359
|
+
* Language of the audio file. If set to null, the language will be automatically detected. Defaults to null.
|
|
360
|
+
*
|
|
361
|
+
* If translate is selected as the task, the audio will be translated to English, regardless of the language selected.
|
|
362
|
+
*/
|
|
363
|
+
language: z.union([z.enum(["en"]), z.string()]).nullish().default("en"),
|
|
364
|
+
/**
|
|
365
|
+
* Whether to diarize the audio file. Defaults to true.
|
|
366
|
+
*/
|
|
367
|
+
diarize: z.boolean().nullish().default(true),
|
|
368
|
+
/**
|
|
369
|
+
* Level of the chunks to return. Either segment or word. Default value: "segment"
|
|
370
|
+
*/
|
|
371
|
+
chunkLevel: z.enum(["segment", "word"]).nullish().default("segment"),
|
|
372
|
+
/**
|
|
373
|
+
* Version of the model to use. All of the models are the Whisper large variant. Default value: "3"
|
|
374
|
+
*/
|
|
375
|
+
version: z.enum(["3"]).nullish().default("3"),
|
|
376
|
+
/**
|
|
377
|
+
* Default value: 64
|
|
378
|
+
*/
|
|
379
|
+
batchSize: z.number().nullish().default(64),
|
|
380
|
+
/**
|
|
381
|
+
* Number of speakers in the audio file. Defaults to null. If not provided, the number of speakers will be automatically detected.
|
|
382
|
+
*/
|
|
383
|
+
numSpeakers: z.number().nullable().nullish()
|
|
501
384
|
});
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
var FalTranscriptionModel = class
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
await delay(pollIntervalMs);
|
|
635
|
-
}
|
|
636
|
-
return {
|
|
637
|
-
text: response.text,
|
|
638
|
-
segments: response.chunks?.map((chunk) => ({
|
|
639
|
-
text: chunk.text,
|
|
640
|
-
startSecond: chunk.timestamp?.at(0) ?? 0,
|
|
641
|
-
endSecond: chunk.timestamp?.at(1) ?? 0
|
|
642
|
-
})) ?? [],
|
|
643
|
-
language: response.inferred_languages?.at(0) ?? void 0,
|
|
644
|
-
durationInSeconds: response.chunks?.at(-1)?.timestamp?.at(1) ?? void 0,
|
|
645
|
-
warnings,
|
|
646
|
-
response: {
|
|
647
|
-
timestamp: currentDate,
|
|
648
|
-
modelId: this.modelId,
|
|
649
|
-
headers: responseHeaders,
|
|
650
|
-
body: rawResponse
|
|
651
|
-
}
|
|
652
|
-
};
|
|
653
|
-
}
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/fal-transcription-model.ts
|
|
387
|
+
var FalTranscriptionModel = class FalTranscriptionModel {
|
|
388
|
+
get provider() {
|
|
389
|
+
return this.config.provider;
|
|
390
|
+
}
|
|
391
|
+
static [WORKFLOW_SERIALIZE](model) {
|
|
392
|
+
return serializeModelOptions({
|
|
393
|
+
modelId: model.modelId,
|
|
394
|
+
config: model.config
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
static [WORKFLOW_DESERIALIZE](options) {
|
|
398
|
+
return new FalTranscriptionModel(options.modelId, options.config);
|
|
399
|
+
}
|
|
400
|
+
constructor(modelId, config) {
|
|
401
|
+
this.modelId = modelId;
|
|
402
|
+
this.config = config;
|
|
403
|
+
this.specificationVersion = "v4";
|
|
404
|
+
}
|
|
405
|
+
async getArgs({ providerOptions }) {
|
|
406
|
+
const warnings = [];
|
|
407
|
+
const falOptions = await parseProviderOptions({
|
|
408
|
+
provider: "fal",
|
|
409
|
+
providerOptions,
|
|
410
|
+
schema: falTranscriptionModelOptionsSchema
|
|
411
|
+
});
|
|
412
|
+
const body = {
|
|
413
|
+
task: "transcribe",
|
|
414
|
+
diarize: true,
|
|
415
|
+
chunk_level: "word"
|
|
416
|
+
};
|
|
417
|
+
if (falOptions) {
|
|
418
|
+
body.language = falOptions.language;
|
|
419
|
+
body.version = falOptions.version ?? void 0;
|
|
420
|
+
body.batch_size = falOptions.batchSize ?? void 0;
|
|
421
|
+
body.num_speakers = falOptions.numSpeakers ?? void 0;
|
|
422
|
+
if (typeof falOptions.diarize === "boolean") body.diarize = falOptions.diarize;
|
|
423
|
+
if (falOptions.chunkLevel) body.chunk_level = falOptions.chunkLevel;
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
body,
|
|
427
|
+
warnings
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
async doGenerate(options) {
|
|
431
|
+
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
432
|
+
const { body, warnings } = await this.getArgs(options);
|
|
433
|
+
const base64Audio = typeof options.audio === "string" ? options.audio : convertUint8ArrayToBase64(options.audio);
|
|
434
|
+
const audioUrl = `data:${options.mediaType};base64,${base64Audio}`;
|
|
435
|
+
const { value: queueResponse } = await postJsonToApi({
|
|
436
|
+
url: this.config.url({
|
|
437
|
+
path: `https://queue.fal.run/fal-ai/${this.modelId}`,
|
|
438
|
+
modelId: this.modelId
|
|
439
|
+
}),
|
|
440
|
+
headers: combineHeaders(this.config.headers?.(), options.headers),
|
|
441
|
+
body: {
|
|
442
|
+
...body,
|
|
443
|
+
audio_url: audioUrl
|
|
444
|
+
},
|
|
445
|
+
failedResponseHandler: falFailedResponseHandler,
|
|
446
|
+
successfulResponseHandler: createJsonResponseHandler(falJobResponseSchema$1),
|
|
447
|
+
abortSignal: options.abortSignal,
|
|
448
|
+
fetch: this.config.fetch
|
|
449
|
+
});
|
|
450
|
+
const startTime = Date.now();
|
|
451
|
+
const timeoutMs = 6e4;
|
|
452
|
+
const pollIntervalMs = 1e3;
|
|
453
|
+
let response;
|
|
454
|
+
let responseHeaders;
|
|
455
|
+
let rawResponse;
|
|
456
|
+
while (true) {
|
|
457
|
+
try {
|
|
458
|
+
const { value: statusResponse, responseHeaders: statusHeaders, rawValue: statusRawResponse } = await getFromApi({
|
|
459
|
+
url: this.config.url({
|
|
460
|
+
path: `https://queue.fal.run/fal-ai/${this.modelId}/requests/${queueResponse.request_id}`,
|
|
461
|
+
modelId: this.modelId
|
|
462
|
+
}),
|
|
463
|
+
validateUrl: false,
|
|
464
|
+
headers: combineHeaders(this.config.headers?.(), options.headers),
|
|
465
|
+
failedResponseHandler: async ({ requestBodyValues, response, url }) => {
|
|
466
|
+
const body = await response.clone().json();
|
|
467
|
+
if (body.detail === "Request is still in progress") return {
|
|
468
|
+
value: /* @__PURE__ */ new Error("Request is still in progress"),
|
|
469
|
+
rawValue: body,
|
|
470
|
+
responseHeaders: {}
|
|
471
|
+
};
|
|
472
|
+
return createJsonErrorResponseHandler({
|
|
473
|
+
errorSchema: falErrorDataSchema,
|
|
474
|
+
errorToMessage: (data) => data.error.message
|
|
475
|
+
})({
|
|
476
|
+
requestBodyValues,
|
|
477
|
+
response,
|
|
478
|
+
url
|
|
479
|
+
});
|
|
480
|
+
},
|
|
481
|
+
successfulResponseHandler: createJsonResponseHandler(falTranscriptionResponseSchema),
|
|
482
|
+
abortSignal: options.abortSignal,
|
|
483
|
+
fetch: this.config.fetch
|
|
484
|
+
});
|
|
485
|
+
response = statusResponse;
|
|
486
|
+
responseHeaders = statusHeaders;
|
|
487
|
+
rawResponse = statusRawResponse;
|
|
488
|
+
break;
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (error instanceof Error && error.message === "Request is still in progress") {} else throw error;
|
|
491
|
+
}
|
|
492
|
+
if (Date.now() - startTime > timeoutMs) throw new AISDKError({
|
|
493
|
+
message: "Transcription request timed out after 60 seconds",
|
|
494
|
+
name: "TranscriptionRequestTimedOut",
|
|
495
|
+
cause: response
|
|
496
|
+
});
|
|
497
|
+
await delay(pollIntervalMs);
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
text: response.text,
|
|
501
|
+
segments: response.chunks?.map((chunk) => ({
|
|
502
|
+
text: chunk.text,
|
|
503
|
+
startSecond: chunk.timestamp?.at(0) ?? 0,
|
|
504
|
+
endSecond: chunk.timestamp?.at(1) ?? 0
|
|
505
|
+
})) ?? [],
|
|
506
|
+
language: response.inferred_languages?.at(0) ?? void 0,
|
|
507
|
+
durationInSeconds: response.chunks?.at(-1)?.timestamp?.at(1) ?? void 0,
|
|
508
|
+
warnings,
|
|
509
|
+
response: {
|
|
510
|
+
timestamp: currentDate,
|
|
511
|
+
modelId: this.modelId,
|
|
512
|
+
headers: responseHeaders,
|
|
513
|
+
body: rawResponse
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
}
|
|
654
517
|
};
|
|
655
|
-
|
|
656
|
-
|
|
518
|
+
const falJobResponseSchema$1 = z.object({ request_id: z.string().nullish() });
|
|
519
|
+
const falTranscriptionResponseSchema = z.object({
|
|
520
|
+
text: z.string(),
|
|
521
|
+
chunks: z.array(z.object({
|
|
522
|
+
text: z.string(),
|
|
523
|
+
timestamp: z.array(z.number()).nullish()
|
|
524
|
+
})).nullish(),
|
|
525
|
+
inferred_languages: z.array(z.string()).nullish()
|
|
657
526
|
});
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
527
|
+
//#endregion
|
|
528
|
+
//#region src/fal-speech-model-options.ts
|
|
529
|
+
const falSpeechModelOptionsSchema = z.looseObject({
|
|
530
|
+
voice_setting: z.object({
|
|
531
|
+
speed: z.number().nullish(),
|
|
532
|
+
vol: z.number().nullish(),
|
|
533
|
+
voice_id: z.string().nullish(),
|
|
534
|
+
pitch: z.number().nullish(),
|
|
535
|
+
english_normalization: z.boolean().nullish(),
|
|
536
|
+
emotion: z.enum([
|
|
537
|
+
"happy",
|
|
538
|
+
"sad",
|
|
539
|
+
"angry",
|
|
540
|
+
"fearful",
|
|
541
|
+
"disgusted",
|
|
542
|
+
"surprised",
|
|
543
|
+
"neutral"
|
|
544
|
+
]).nullish()
|
|
545
|
+
}).partial().nullish(),
|
|
546
|
+
audio_setting: z.record(z.string(), z.unknown()).nullish(),
|
|
547
|
+
language_boost: z.enum([
|
|
548
|
+
"Chinese",
|
|
549
|
+
"Chinese,Yue",
|
|
550
|
+
"English",
|
|
551
|
+
"Arabic",
|
|
552
|
+
"Russian",
|
|
553
|
+
"Spanish",
|
|
554
|
+
"French",
|
|
555
|
+
"Portuguese",
|
|
556
|
+
"German",
|
|
557
|
+
"Turkish",
|
|
558
|
+
"Dutch",
|
|
559
|
+
"Ukrainian",
|
|
560
|
+
"Vietnamese",
|
|
561
|
+
"Indonesian",
|
|
562
|
+
"Japanese",
|
|
563
|
+
"Italian",
|
|
564
|
+
"Korean",
|
|
565
|
+
"Thai",
|
|
566
|
+
"Polish",
|
|
567
|
+
"Romanian",
|
|
568
|
+
"Greek",
|
|
569
|
+
"Czech",
|
|
570
|
+
"Finnish",
|
|
571
|
+
"Hindi",
|
|
572
|
+
"auto"
|
|
573
|
+
]).nullish(),
|
|
574
|
+
pronunciation_dict: z.record(z.string(), z.string()).nullish()
|
|
667
575
|
});
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
static [WORKFLOW_DESERIALIZE3](options) {
|
|
757
|
-
return new _FalSpeechModel(options.modelId, options.config);
|
|
758
|
-
}
|
|
759
|
-
async getArgs({
|
|
760
|
-
text,
|
|
761
|
-
voice,
|
|
762
|
-
outputFormat,
|
|
763
|
-
speed,
|
|
764
|
-
language,
|
|
765
|
-
providerOptions
|
|
766
|
-
}) {
|
|
767
|
-
const warnings = [];
|
|
768
|
-
const falOptions = await parseProviderOptions3({
|
|
769
|
-
provider: "fal",
|
|
770
|
-
providerOptions,
|
|
771
|
-
schema: falSpeechModelOptionsSchema
|
|
772
|
-
});
|
|
773
|
-
const requestBody = {
|
|
774
|
-
text,
|
|
775
|
-
output_format: outputFormat === "hex" ? "hex" : "url",
|
|
776
|
-
voice,
|
|
777
|
-
speed,
|
|
778
|
-
...falOptions
|
|
779
|
-
};
|
|
780
|
-
if (language) {
|
|
781
|
-
warnings.push({
|
|
782
|
-
type: "unsupported",
|
|
783
|
-
feature: "language",
|
|
784
|
-
details: "fal speech models don't support 'language' directly; consider providerOptions.fal.language_boost"
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
if (outputFormat && outputFormat !== "url" && outputFormat !== "hex") {
|
|
788
|
-
warnings.push({
|
|
789
|
-
type: "unsupported",
|
|
790
|
-
feature: "outputFormat",
|
|
791
|
-
details: `Unsupported outputFormat: ${outputFormat}. Using 'url' instead.`
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
return { requestBody, warnings };
|
|
795
|
-
}
|
|
796
|
-
async doGenerate(options) {
|
|
797
|
-
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
798
|
-
const { requestBody, warnings } = await this.getArgs(options);
|
|
799
|
-
const requestUrl = this.config.url({
|
|
800
|
-
path: `https://fal.run/${this.modelId}`,
|
|
801
|
-
modelId: this.modelId
|
|
802
|
-
});
|
|
803
|
-
const {
|
|
804
|
-
value: json,
|
|
805
|
-
responseHeaders,
|
|
806
|
-
rawValue
|
|
807
|
-
} = await postJsonToApi3({
|
|
808
|
-
url: requestUrl,
|
|
809
|
-
headers: combineHeaders3(this.config.headers?.(), options.headers),
|
|
810
|
-
body: requestBody,
|
|
811
|
-
failedResponseHandler: falFailedResponseHandler2,
|
|
812
|
-
successfulResponseHandler: createJsonResponseHandler3(
|
|
813
|
-
falSpeechResponseSchema
|
|
814
|
-
),
|
|
815
|
-
abortSignal: options.abortSignal,
|
|
816
|
-
fetch: this.config.fetch
|
|
817
|
-
});
|
|
818
|
-
const audioUrl = json.audio.url;
|
|
819
|
-
const { value: audio } = await getFromApi3({
|
|
820
|
-
url: audioUrl,
|
|
821
|
-
// audioUrl comes from the provider response body; validate it.
|
|
822
|
-
validateUrl: true,
|
|
823
|
-
trustedOrigin: requestUrl,
|
|
824
|
-
failedResponseHandler: createStatusCodeErrorResponseHandler2(),
|
|
825
|
-
successfulResponseHandler: createBinaryResponseHandler2(),
|
|
826
|
-
abortSignal: options.abortSignal,
|
|
827
|
-
fetch: this.config.fetch
|
|
828
|
-
});
|
|
829
|
-
return {
|
|
830
|
-
audio,
|
|
831
|
-
warnings,
|
|
832
|
-
request: {
|
|
833
|
-
body: JSON.stringify(requestBody)
|
|
834
|
-
},
|
|
835
|
-
response: {
|
|
836
|
-
timestamp: currentDate,
|
|
837
|
-
modelId: this.modelId,
|
|
838
|
-
headers: responseHeaders,
|
|
839
|
-
body: rawValue
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
}
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/fal-speech-model.ts
|
|
578
|
+
var FalSpeechModel = class FalSpeechModel {
|
|
579
|
+
get provider() {
|
|
580
|
+
return this.config.provider;
|
|
581
|
+
}
|
|
582
|
+
static [WORKFLOW_SERIALIZE](model) {
|
|
583
|
+
return serializeModelOptions({
|
|
584
|
+
modelId: model.modelId,
|
|
585
|
+
config: model.config
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
static [WORKFLOW_DESERIALIZE](options) {
|
|
589
|
+
return new FalSpeechModel(options.modelId, options.config);
|
|
590
|
+
}
|
|
591
|
+
constructor(modelId, config) {
|
|
592
|
+
this.modelId = modelId;
|
|
593
|
+
this.config = config;
|
|
594
|
+
this.specificationVersion = "v4";
|
|
595
|
+
}
|
|
596
|
+
async getArgs({ text, voice, outputFormat, speed, language, providerOptions }) {
|
|
597
|
+
const warnings = [];
|
|
598
|
+
const falOptions = await parseProviderOptions({
|
|
599
|
+
provider: "fal",
|
|
600
|
+
providerOptions,
|
|
601
|
+
schema: falSpeechModelOptionsSchema
|
|
602
|
+
});
|
|
603
|
+
const requestBody = {
|
|
604
|
+
text,
|
|
605
|
+
output_format: outputFormat === "hex" ? "hex" : "url",
|
|
606
|
+
voice,
|
|
607
|
+
speed,
|
|
608
|
+
...falOptions
|
|
609
|
+
};
|
|
610
|
+
if (language) warnings.push({
|
|
611
|
+
type: "unsupported",
|
|
612
|
+
feature: "language",
|
|
613
|
+
details: "fal speech models don't support 'language' directly; consider providerOptions.fal.language_boost"
|
|
614
|
+
});
|
|
615
|
+
if (outputFormat && outputFormat !== "url" && outputFormat !== "hex") warnings.push({
|
|
616
|
+
type: "unsupported",
|
|
617
|
+
feature: "outputFormat",
|
|
618
|
+
details: `Unsupported outputFormat: ${outputFormat}. Using 'url' instead.`
|
|
619
|
+
});
|
|
620
|
+
return {
|
|
621
|
+
requestBody,
|
|
622
|
+
warnings
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
async doGenerate(options) {
|
|
626
|
+
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
627
|
+
const { requestBody, warnings } = await this.getArgs(options);
|
|
628
|
+
const requestUrl = this.config.url({
|
|
629
|
+
path: `https://fal.run/${this.modelId}`,
|
|
630
|
+
modelId: this.modelId
|
|
631
|
+
});
|
|
632
|
+
const { value: json, responseHeaders, rawValue } = await postJsonToApi({
|
|
633
|
+
url: requestUrl,
|
|
634
|
+
headers: combineHeaders(this.config.headers?.(), options.headers),
|
|
635
|
+
body: requestBody,
|
|
636
|
+
failedResponseHandler: falFailedResponseHandler,
|
|
637
|
+
successfulResponseHandler: createJsonResponseHandler(falSpeechResponseSchema),
|
|
638
|
+
abortSignal: options.abortSignal,
|
|
639
|
+
fetch: this.config.fetch
|
|
640
|
+
});
|
|
641
|
+
const audioUrl = json.audio.url;
|
|
642
|
+
const { value: audio } = await getFromApi({
|
|
643
|
+
url: audioUrl,
|
|
644
|
+
validateUrl: true,
|
|
645
|
+
trustedOrigin: requestUrl,
|
|
646
|
+
failedResponseHandler: createStatusCodeErrorResponseHandler(),
|
|
647
|
+
successfulResponseHandler: createBinaryResponseHandler(),
|
|
648
|
+
abortSignal: options.abortSignal,
|
|
649
|
+
fetch: this.config.fetch
|
|
650
|
+
});
|
|
651
|
+
return {
|
|
652
|
+
audio,
|
|
653
|
+
warnings,
|
|
654
|
+
request: { body: JSON.stringify(requestBody) },
|
|
655
|
+
response: {
|
|
656
|
+
timestamp: currentDate,
|
|
657
|
+
modelId: this.modelId,
|
|
658
|
+
headers: responseHeaders,
|
|
659
|
+
body: rawValue
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
843
663
|
};
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
664
|
+
const falSpeechResponseSchema = z.object({
|
|
665
|
+
audio: z.object({ url: z.string() }),
|
|
666
|
+
duration_ms: z.number().optional(),
|
|
667
|
+
request_id: z.string().optional()
|
|
848
668
|
});
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
getFromApi as getFromApi4,
|
|
861
|
-
parseProviderOptions as parseProviderOptions4,
|
|
862
|
-
postJsonToApi as postJsonToApi4
|
|
863
|
-
} from "@ai-sdk/provider-utils";
|
|
864
|
-
import { z as z9 } from "zod/v4";
|
|
865
|
-
|
|
866
|
-
// src/fal-video-model-options.ts
|
|
867
|
-
import { lazySchema as lazySchema2, zodSchema as zodSchema2 } from "@ai-sdk/provider-utils";
|
|
868
|
-
import { z as z8 } from "zod/v4";
|
|
869
|
-
var falVideoModelOptionsSchema = lazySchema2(
|
|
870
|
-
() => zodSchema2(
|
|
871
|
-
z8.looseObject({
|
|
872
|
-
// Video loop - only for Luma models
|
|
873
|
-
loop: z8.boolean().nullish(),
|
|
874
|
-
// Motion strength (provider-specific)
|
|
875
|
-
motionStrength: z8.number().min(0).max(1).nullish(),
|
|
876
|
-
// Resolution (model-specific, e.g., '480p', '720p', '1080p')
|
|
877
|
-
resolution: z8.string().nullish(),
|
|
878
|
-
// Model-specific parameters
|
|
879
|
-
negativePrompt: z8.string().nullish(),
|
|
880
|
-
promptOptimizer: z8.boolean().nullish()
|
|
881
|
-
})
|
|
882
|
-
)
|
|
883
|
-
);
|
|
884
|
-
|
|
885
|
-
// src/fal-video-model.ts
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/fal-video-model-options.ts
|
|
671
|
+
const falVideoModelOptionsSchema = lazySchema(() => zodSchema(z.looseObject({
|
|
672
|
+
loop: z.boolean().nullish(),
|
|
673
|
+
motionStrength: z.number().min(0).max(1).nullish(),
|
|
674
|
+
resolution: z.string().nullish(),
|
|
675
|
+
negativePrompt: z.string().nullish(),
|
|
676
|
+
promptOptimizer: z.boolean().nullish()
|
|
677
|
+
})));
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/fal-video-model.ts
|
|
886
680
|
var FalVideoModel = class {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
currentDate
|
|
1094
|
-
}) {
|
|
1095
|
-
const videoUrl = response.video?.url;
|
|
1096
|
-
if (!videoUrl || !response.video) {
|
|
1097
|
-
throw new AISDKError2({
|
|
1098
|
-
name: "FAL_VIDEO_GENERATION_ERROR",
|
|
1099
|
-
message: "No video URL in response"
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
return {
|
|
1103
|
-
status: "completed",
|
|
1104
|
-
videos: [
|
|
1105
|
-
{
|
|
1106
|
-
type: "url",
|
|
1107
|
-
url: videoUrl,
|
|
1108
|
-
mediaType: response.video.content_type || "video/mp4"
|
|
1109
|
-
}
|
|
1110
|
-
],
|
|
1111
|
-
warnings,
|
|
1112
|
-
response: {
|
|
1113
|
-
timestamp: currentDate,
|
|
1114
|
-
modelId: this.modelId,
|
|
1115
|
-
headers: responseHeaders
|
|
1116
|
-
},
|
|
1117
|
-
providerMetadata: {
|
|
1118
|
-
fal: {
|
|
1119
|
-
videos: [
|
|
1120
|
-
{
|
|
1121
|
-
url: videoUrl,
|
|
1122
|
-
width: response.video.width,
|
|
1123
|
-
height: response.video.height,
|
|
1124
|
-
duration: response.video.duration,
|
|
1125
|
-
fps: response.video.fps,
|
|
1126
|
-
contentType: response.video.content_type
|
|
1127
|
-
}
|
|
1128
|
-
],
|
|
1129
|
-
...response.seed !== void 0 ? { seed: response.seed } : {},
|
|
1130
|
-
...response.timings ? { timings: response.timings } : {},
|
|
1131
|
-
...response.has_nsfw_concepts !== void 0 ? { has_nsfw_concepts: response.has_nsfw_concepts } : {},
|
|
1132
|
-
...response.prompt ? { prompt: response.prompt } : {}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
};
|
|
1136
|
-
}
|
|
681
|
+
get provider() {
|
|
682
|
+
return this.config.provider;
|
|
683
|
+
}
|
|
684
|
+
get normalizedModelId() {
|
|
685
|
+
return this.modelId.replace(/^fal-ai\//, "").replace(/^fal\//, "");
|
|
686
|
+
}
|
|
687
|
+
constructor(modelId, config) {
|
|
688
|
+
this.modelId = modelId;
|
|
689
|
+
this.config = config;
|
|
690
|
+
this.specificationVersion = "v4";
|
|
691
|
+
this.maxVideosPerCall = 1;
|
|
692
|
+
}
|
|
693
|
+
async handleWebhookOption(options) {
|
|
694
|
+
const { url, received } = await options.webhook();
|
|
695
|
+
return {
|
|
696
|
+
webhookUrl: url,
|
|
697
|
+
received
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
async doStart(options) {
|
|
701
|
+
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
702
|
+
const warnings = [];
|
|
703
|
+
const { body } = await this.buildRequestBody(options);
|
|
704
|
+
const { responseUrl, submitUrl } = await this.submitToQueue({
|
|
705
|
+
headers: options.headers,
|
|
706
|
+
abortSignal: options.abortSignal,
|
|
707
|
+
webhookUrl: options.webhookUrl
|
|
708
|
+
}, body);
|
|
709
|
+
return {
|
|
710
|
+
operation: {
|
|
711
|
+
responseUrl,
|
|
712
|
+
submitUrl
|
|
713
|
+
},
|
|
714
|
+
warnings,
|
|
715
|
+
response: {
|
|
716
|
+
timestamp: currentDate,
|
|
717
|
+
modelId: this.modelId,
|
|
718
|
+
headers: void 0
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
async doStatus(options) {
|
|
723
|
+
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
724
|
+
const { responseUrl, submitUrl } = options.operation;
|
|
725
|
+
try {
|
|
726
|
+
const { value: response, responseHeaders } = await this.fetchStatus({
|
|
727
|
+
responseUrl,
|
|
728
|
+
submitUrl,
|
|
729
|
+
headers: options.headers,
|
|
730
|
+
abortSignal: options.abortSignal
|
|
731
|
+
});
|
|
732
|
+
return this.buildResult({
|
|
733
|
+
response,
|
|
734
|
+
responseHeaders,
|
|
735
|
+
warnings: [],
|
|
736
|
+
currentDate
|
|
737
|
+
});
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (error instanceof Error && error.message === "Request is still in progress") return {
|
|
740
|
+
status: "pending",
|
|
741
|
+
response: {
|
|
742
|
+
timestamp: currentDate,
|
|
743
|
+
modelId: this.modelId,
|
|
744
|
+
headers: void 0
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
if (APICallError.isInstance(error)) return {
|
|
748
|
+
status: "error",
|
|
749
|
+
error: error.message,
|
|
750
|
+
response: {
|
|
751
|
+
timestamp: currentDate,
|
|
752
|
+
modelId: this.modelId,
|
|
753
|
+
headers: error.responseHeaders
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
throw error;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async buildRequestBody(options) {
|
|
760
|
+
const falOptions = await parseProviderOptions({
|
|
761
|
+
provider: "fal",
|
|
762
|
+
providerOptions: options.providerOptions,
|
|
763
|
+
schema: falVideoModelOptionsSchema
|
|
764
|
+
});
|
|
765
|
+
const body = {};
|
|
766
|
+
if (options.prompt != null) body.prompt = options.prompt;
|
|
767
|
+
if (options.image != null) {
|
|
768
|
+
if (options.image.type === "url") body.image_url = options.image.url;
|
|
769
|
+
else body.image_url = convertImageModelFileToDataUri(options.image);
|
|
770
|
+
}
|
|
771
|
+
if (options.aspectRatio) body.aspect_ratio = options.aspectRatio;
|
|
772
|
+
if (options.duration) body.duration = `${options.duration}s`;
|
|
773
|
+
if (options.seed) body.seed = options.seed;
|
|
774
|
+
if (falOptions != null) {
|
|
775
|
+
const opts = falOptions;
|
|
776
|
+
if (opts.loop !== void 0 && opts.loop !== null) body.loop = opts.loop;
|
|
777
|
+
if (opts.motionStrength !== void 0 && opts.motionStrength !== null) body.motion_strength = opts.motionStrength;
|
|
778
|
+
if (opts.resolution !== void 0 && opts.resolution !== null) body.resolution = opts.resolution;
|
|
779
|
+
if (opts.negativePrompt !== void 0 && opts.negativePrompt !== null) body.negative_prompt = opts.negativePrompt;
|
|
780
|
+
if (opts.promptOptimizer !== void 0 && opts.promptOptimizer !== null) body.prompt_optimizer = opts.promptOptimizer;
|
|
781
|
+
for (const [key, value] of Object.entries(opts)) if (![
|
|
782
|
+
"loop",
|
|
783
|
+
"motionStrength",
|
|
784
|
+
"resolution",
|
|
785
|
+
"negativePrompt",
|
|
786
|
+
"promptOptimizer"
|
|
787
|
+
].includes(key)) body[key] = value;
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
body,
|
|
791
|
+
falOptions
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
async submitToQueue(options, body) {
|
|
795
|
+
let queuePath = `https://queue.fal.run/fal-ai/${this.normalizedModelId}`;
|
|
796
|
+
if (options.webhookUrl) queuePath += `?fal_webhook=${encodeURIComponent(options.webhookUrl)}`;
|
|
797
|
+
const submitUrl = this.config.url({
|
|
798
|
+
path: queuePath,
|
|
799
|
+
modelId: this.modelId
|
|
800
|
+
});
|
|
801
|
+
const { value: queueResponse } = await postJsonToApi({
|
|
802
|
+
url: submitUrl,
|
|
803
|
+
headers: combineHeaders(this.config.headers?.(), options.headers),
|
|
804
|
+
body,
|
|
805
|
+
failedResponseHandler: falFailedResponseHandler,
|
|
806
|
+
successfulResponseHandler: createJsonResponseHandler(falJobResponseSchema),
|
|
807
|
+
abortSignal: options.abortSignal,
|
|
808
|
+
fetch: this.config.fetch
|
|
809
|
+
});
|
|
810
|
+
const responseUrl = queueResponse.response_url;
|
|
811
|
+
if (!responseUrl) throw new AISDKError({
|
|
812
|
+
name: "FAL_VIDEO_GENERATION_ERROR",
|
|
813
|
+
message: "No response URL returned from queue endpoint"
|
|
814
|
+
});
|
|
815
|
+
return {
|
|
816
|
+
responseUrl,
|
|
817
|
+
submitUrl
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
async fetchStatus({ responseUrl, submitUrl, headers, abortSignal }) {
|
|
821
|
+
const statusUrl = this.config.url({
|
|
822
|
+
path: responseUrl,
|
|
823
|
+
modelId: this.modelId
|
|
824
|
+
});
|
|
825
|
+
return getFromApi({
|
|
826
|
+
url: statusUrl,
|
|
827
|
+
validateUrl: true,
|
|
828
|
+
credentialedOrigin: submitUrl,
|
|
829
|
+
trustedOrigin: submitUrl,
|
|
830
|
+
headers: combineHeaders(this.config.headers?.(), headers),
|
|
831
|
+
failedResponseHandler: async ({ response, url, requestBodyValues }) => {
|
|
832
|
+
const body = await response.clone().json();
|
|
833
|
+
if (body.detail === "Request is still in progress") return {
|
|
834
|
+
value: /* @__PURE__ */ new Error("Request is still in progress"),
|
|
835
|
+
rawValue: body,
|
|
836
|
+
responseHeaders: {}
|
|
837
|
+
};
|
|
838
|
+
return createJsonErrorResponseHandler({
|
|
839
|
+
errorSchema: falErrorDataSchema,
|
|
840
|
+
errorToMessage: (data) => data.error.message
|
|
841
|
+
})({
|
|
842
|
+
response,
|
|
843
|
+
url,
|
|
844
|
+
requestBodyValues
|
|
845
|
+
});
|
|
846
|
+
},
|
|
847
|
+
successfulResponseHandler: createJsonResponseHandler(falVideoResponseSchema),
|
|
848
|
+
abortSignal,
|
|
849
|
+
fetch: this.config.fetch
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
buildResult({ response, responseHeaders, warnings, currentDate }) {
|
|
853
|
+
const videoUrl = response.video?.url;
|
|
854
|
+
if (!videoUrl || !response.video) throw new AISDKError({
|
|
855
|
+
name: "FAL_VIDEO_GENERATION_ERROR",
|
|
856
|
+
message: "No video URL in response"
|
|
857
|
+
});
|
|
858
|
+
return {
|
|
859
|
+
status: "completed",
|
|
860
|
+
videos: [{
|
|
861
|
+
type: "url",
|
|
862
|
+
url: videoUrl,
|
|
863
|
+
mediaType: response.video.content_type || "video/mp4"
|
|
864
|
+
}],
|
|
865
|
+
warnings,
|
|
866
|
+
response: {
|
|
867
|
+
timestamp: currentDate,
|
|
868
|
+
modelId: this.modelId,
|
|
869
|
+
headers: responseHeaders
|
|
870
|
+
},
|
|
871
|
+
providerMetadata: { fal: {
|
|
872
|
+
videos: [{
|
|
873
|
+
url: videoUrl,
|
|
874
|
+
width: response.video.width,
|
|
875
|
+
height: response.video.height,
|
|
876
|
+
duration: response.video.duration,
|
|
877
|
+
fps: response.video.fps,
|
|
878
|
+
contentType: response.video.content_type
|
|
879
|
+
}],
|
|
880
|
+
...response.seed !== void 0 ? { seed: response.seed } : {},
|
|
881
|
+
...response.timings ? { timings: response.timings } : {},
|
|
882
|
+
...response.has_nsfw_concepts !== void 0 ? { has_nsfw_concepts: response.has_nsfw_concepts } : {},
|
|
883
|
+
...response.prompt ? { prompt: response.prompt } : {}
|
|
884
|
+
} }
|
|
885
|
+
};
|
|
886
|
+
}
|
|
1137
887
|
};
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
888
|
+
const falJobResponseSchema = z.object({
|
|
889
|
+
request_id: z.string().nullish(),
|
|
890
|
+
response_url: z.string().nullish()
|
|
1141
891
|
});
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
has_nsfw_concepts: z9.array(z9.boolean()).nullish(),
|
|
1156
|
-
prompt: z9.string().nullish()
|
|
892
|
+
const falVideoResponseSchema = z.object({
|
|
893
|
+
video: z.object({
|
|
894
|
+
url: z.string(),
|
|
895
|
+
width: z.number().nullish(),
|
|
896
|
+
height: z.number().nullish(),
|
|
897
|
+
duration: z.number().nullish(),
|
|
898
|
+
fps: z.number().nullish(),
|
|
899
|
+
content_type: z.string().nullish()
|
|
900
|
+
}).nullish(),
|
|
901
|
+
seed: z.number().nullish(),
|
|
902
|
+
timings: z.object({ inference: z.number().nullish() }).nullish(),
|
|
903
|
+
has_nsfw_concepts: z.array(z.boolean()).nullish(),
|
|
904
|
+
prompt: z.string().nullish()
|
|
1157
905
|
});
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
function loadFalApiKey({
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
if (typeof process === "undefined") {
|
|
1175
|
-
throw new Error(
|
|
1176
|
-
`${description} API key is missing. Pass it using the 'apiKey' parameter. Environment variables are not supported in this environment.`
|
|
1177
|
-
);
|
|
1178
|
-
}
|
|
1179
|
-
let envApiKey = process.env.FAL_API_KEY;
|
|
1180
|
-
if (envApiKey == null) {
|
|
1181
|
-
envApiKey = process.env.FAL_KEY;
|
|
1182
|
-
}
|
|
1183
|
-
if (envApiKey == null) {
|
|
1184
|
-
throw new Error(
|
|
1185
|
-
`${description} API key is missing. Pass it using the 'apiKey' parameter or set either the FAL_API_KEY or FAL_KEY environment variable.`
|
|
1186
|
-
);
|
|
1187
|
-
}
|
|
1188
|
-
if (typeof envApiKey !== "string") {
|
|
1189
|
-
throw new Error(
|
|
1190
|
-
`${description} API key must be a string. The value of the environment variable is not a string.`
|
|
1191
|
-
);
|
|
1192
|
-
}
|
|
1193
|
-
return envApiKey;
|
|
906
|
+
//#endregion
|
|
907
|
+
//#region src/version.ts
|
|
908
|
+
const VERSION = "3.0.54";
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/fal-provider.ts
|
|
911
|
+
const defaultBaseURL = "https://fal.run";
|
|
912
|
+
function loadFalApiKey({ apiKey, description = "fal.ai" }) {
|
|
913
|
+
if (typeof apiKey === "string") return apiKey;
|
|
914
|
+
if (apiKey != null) throw new Error(`${description} API key must be a string.`);
|
|
915
|
+
if (typeof process === "undefined") throw new Error(`${description} API key is missing. Pass it using the 'apiKey' parameter. Environment variables are not supported in this environment.`);
|
|
916
|
+
let envApiKey = process.env.FAL_API_KEY;
|
|
917
|
+
if (envApiKey == null) envApiKey = process.env.FAL_KEY;
|
|
918
|
+
if (envApiKey == null) throw new Error(`${description} API key is missing. Pass it using the 'apiKey' parameter or set either the FAL_API_KEY or FAL_KEY environment variable.`);
|
|
919
|
+
if (typeof envApiKey !== "string") throw new Error(`${description} API key must be a string. The value of the environment variable is not a string.`);
|
|
920
|
+
return envApiKey;
|
|
1194
921
|
}
|
|
922
|
+
/**
|
|
923
|
+
* Create a fal.ai provider instance.
|
|
924
|
+
*/
|
|
1195
925
|
function createFal(options = {}) {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
textEmbeddingModel: embeddingModel,
|
|
1249
|
-
transcription: createTranscriptionModel,
|
|
1250
|
-
video: createVideoModel,
|
|
1251
|
-
videoModel: createVideoModel
|
|
1252
|
-
};
|
|
926
|
+
const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);
|
|
927
|
+
const getHeaders = () => withUserAgentSuffix({
|
|
928
|
+
Authorization: `Key ${loadFalApiKey({ apiKey: options.apiKey })}`,
|
|
929
|
+
...options.headers
|
|
930
|
+
}, `ai-sdk-fal/${VERSION}`);
|
|
931
|
+
const createImageModel = (modelId) => new FalImageModel(modelId, {
|
|
932
|
+
provider: "fal.image",
|
|
933
|
+
baseURL: baseURL ?? defaultBaseURL,
|
|
934
|
+
headers: getHeaders,
|
|
935
|
+
fetch: options.fetch
|
|
936
|
+
});
|
|
937
|
+
const createSpeechModel = (modelId) => new FalSpeechModel(modelId, {
|
|
938
|
+
provider: `fal.speech`,
|
|
939
|
+
url: ({ path }) => path,
|
|
940
|
+
headers: getHeaders,
|
|
941
|
+
fetch: options.fetch
|
|
942
|
+
});
|
|
943
|
+
const createTranscriptionModel = (modelId) => new FalTranscriptionModel(modelId, {
|
|
944
|
+
provider: `fal.transcription`,
|
|
945
|
+
url: ({ path }) => path,
|
|
946
|
+
headers: getHeaders,
|
|
947
|
+
fetch: options.fetch
|
|
948
|
+
});
|
|
949
|
+
const createVideoModel = (modelId) => new FalVideoModel(modelId, {
|
|
950
|
+
provider: "fal.video",
|
|
951
|
+
url: ({ path }) => path,
|
|
952
|
+
headers: getHeaders,
|
|
953
|
+
fetch: options.fetch
|
|
954
|
+
});
|
|
955
|
+
const embeddingModel = (modelId) => {
|
|
956
|
+
throw new NoSuchModelError({
|
|
957
|
+
modelId,
|
|
958
|
+
modelType: "embeddingModel"
|
|
959
|
+
});
|
|
960
|
+
};
|
|
961
|
+
return {
|
|
962
|
+
specificationVersion: "v4",
|
|
963
|
+
imageModel: createImageModel,
|
|
964
|
+
image: createImageModel,
|
|
965
|
+
languageModel: (modelId) => {
|
|
966
|
+
throw new NoSuchModelError({
|
|
967
|
+
modelId,
|
|
968
|
+
modelType: "languageModel"
|
|
969
|
+
});
|
|
970
|
+
},
|
|
971
|
+
speech: createSpeechModel,
|
|
972
|
+
embeddingModel,
|
|
973
|
+
textEmbeddingModel: embeddingModel,
|
|
974
|
+
transcription: createTranscriptionModel,
|
|
975
|
+
video: createVideoModel,
|
|
976
|
+
videoModel: createVideoModel
|
|
977
|
+
};
|
|
1253
978
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
};
|
|
979
|
+
/**
|
|
980
|
+
* Default fal.ai provider instance.
|
|
981
|
+
*/
|
|
982
|
+
const fal = createFal();
|
|
983
|
+
//#endregion
|
|
984
|
+
export { VERSION, createFal, fal };
|
|
985
|
+
|
|
1260
986
|
//# sourceMappingURL=index.js.map
|