@ai-sdk/fal 3.0.51 → 3.0.53

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/dist/index.js CHANGED
@@ -1,1260 +1,986 @@
1
- // src/fal-provider.ts
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
- var falImageModelOptionsSchema = lazySchema(
35
- () => zodSchema(
36
- z.looseObject({
37
- /** @deprecated use prompt.images instead */
38
- imageUrl: z.string().nullish().meta({
39
- deprecated: true,
40
- description: "Use `prompt.images` instead"
41
- }),
42
- maskUrl: z.string().nullish().meta({ deprecated: true, description: "Use `prompt.mask` instead" }),
43
- guidanceScale: z.number().min(1).max(20).nullish(),
44
- numInferenceSteps: z.number().min(1).max(50).nullish(),
45
- enableSafetyChecker: z.boolean().nullish(),
46
- outputFormat: z.enum(["jpeg", "png"]).nullish(),
47
- syncMode: z.boolean().nullish(),
48
- strength: z.number().nullish(),
49
- acceleration: z.enum(["none", "regular", "high"]).nullish(),
50
- safetyTolerance: z.enum(["1", "2", "3", "4", "5", "6"]).or(z.number().min(1).max(6)).nullish(),
51
- /**
52
- * When true, converts multiple input images to `image_urls` array instead of `image_url` string.
53
- */
54
- useMultipleImages: z.boolean().nullish(),
55
- // Deprecated snake_case versions
56
- image_url: z.string().nullish(),
57
- mask_url: z.string().nullish(),
58
- guidance_scale: z.number().min(1).max(20).nullish(),
59
- num_inference_steps: z.number().min(1).max(50).nullish(),
60
- enable_safety_checker: z.boolean().nullish(),
61
- output_format: z.enum(["jpeg", "png"]).nullish(),
62
- sync_mode: z.boolean().nullish(),
63
- safety_tolerance: z.enum(["1", "2", "3", "4", "5", "6"]).or(z.number().min(1).max(6)).nullish()
64
- }).transform((data) => {
65
- const result = {};
66
- const deprecatedKeys = [];
67
- const mapKey = (snakeKey, camelKey) => {
68
- const snakeValue = data[snakeKey];
69
- const camelValue = data[camelKey];
70
- if (snakeValue !== void 0 && snakeValue !== null) {
71
- deprecatedKeys.push(snakeKey);
72
- result[camelKey] = snakeValue;
73
- } else if (camelValue !== void 0 && camelValue !== null) {
74
- result[camelKey] = camelValue;
75
- }
76
- };
77
- mapKey("image_url", "imageUrl");
78
- mapKey("mask_url", "maskUrl");
79
- mapKey("guidance_scale", "guidanceScale");
80
- mapKey("num_inference_steps", "numInferenceSteps");
81
- mapKey("enable_safety_checker", "enableSafetyChecker");
82
- mapKey("output_format", "outputFormat");
83
- mapKey("sync_mode", "syncMode");
84
- mapKey("safety_tolerance", "safetyTolerance");
85
- if (data.strength !== void 0 && data.strength !== null) {
86
- result.strength = data.strength;
87
- }
88
- if (data.acceleration !== void 0 && data.acceleration !== null) {
89
- result.acceleration = data.acceleration;
90
- }
91
- if (data.useMultipleImages !== void 0 && data.useMultipleImages !== null) {
92
- result.useMultipleImages = data.useMultipleImages;
93
- }
94
- for (const [key, value] of Object.entries(data)) {
95
- if (![
96
- // camelCase known keys
97
- "imageUrl",
98
- "maskUrl",
99
- "guidanceScale",
100
- "numInferenceSteps",
101
- "enableSafetyChecker",
102
- "outputFormat",
103
- "syncMode",
104
- "strength",
105
- "acceleration",
106
- "safetyTolerance",
107
- "useMultipleImages",
108
- // snake_case known keys
109
- "image_url",
110
- "mask_url",
111
- "guidance_scale",
112
- "num_inference_steps",
113
- "enable_safety_checker",
114
- "output_format",
115
- "sync_mode",
116
- "safety_tolerance"
117
- ].includes(key)) {
118
- result[key] = value;
119
- }
120
- }
121
- if (deprecatedKeys.length > 0) {
122
- result.__deprecatedKeys = deprecatedKeys;
123
- }
124
- return result;
125
- })
126
- )
127
- );
128
-
129
- // src/fal-image-model.ts
130
- var FalImageModel = class _FalImageModel {
131
- constructor(modelId, config) {
132
- this.modelId = modelId;
133
- this.config = config;
134
- this.specificationVersion = "v4";
135
- this.maxImagesPerCall = 1;
136
- }
137
- get supportsFileInputs() {
138
- if ([
139
- "fal-ai/flux-2/edit",
140
- "fal-ai/flux-pro/kontext",
141
- "fal-ai/flux-pro/kontext/max",
142
- "fal-ai/flux-general/image-to-image",
143
- "fal-ai/flux-general/inpainting",
144
- "fal-ai/flux-lora/image-to-image",
145
- "fal-ai/flux-lora/inpainting",
146
- "fal-ai/flux/dev/image-to-image",
147
- "fal-ai/flux/krea/image-to-image",
148
- "fal-ai/recraft/v3/image-to-image"
149
- ].includes(this.modelId)) {
150
- return true;
151
- }
152
- return [
153
- "bria/text-to-image/3.2",
154
- "fal-ai/bria/text-to-image/base",
155
- "fal-ai/bria/text-to-image/fast",
156
- "fal-ai/bria/text-to-image/hd",
157
- "fal-ai/bytedance/dreamina/v3.1/text-to-image",
158
- "fal-ai/flux-kontext-lora/text-to-image",
159
- "fal-ai/recraft/v3/text-to-image",
160
- "fal-ai/wan/v2.2-5b/text-to-image",
161
- "fal-ai/wan/v2.2-a14b/text-to-image"
162
- ].includes(this.modelId) ? false : void 0;
163
- }
164
- get supportsMaskInputs() {
165
- if (this.modelId === "fal-ai/flux-general/inpainting" || this.modelId === "fal-ai/flux-lora/inpainting") {
166
- return true;
167
- }
168
- return this.supportsFileInputs == null ? void 0 : false;
169
- }
170
- get provider() {
171
- return this.config.provider;
172
- }
173
- static [WORKFLOW_SERIALIZE](model) {
174
- return serializeModelOptions({
175
- modelId: model.modelId,
176
- config: model.config
177
- });
178
- }
179
- static [WORKFLOW_DESERIALIZE](options) {
180
- return new _FalImageModel(options.modelId, options.config);
181
- }
182
- async getArgs({
183
- prompt,
184
- n,
185
- size,
186
- aspectRatio,
187
- seed,
188
- providerOptions,
189
- files,
190
- mask
191
- }) {
192
- const warnings = [];
193
- let imageSize;
194
- if (size) {
195
- const [width, height] = size.split("x").map(Number);
196
- imageSize = { width, height };
197
- } else if (aspectRatio) {
198
- imageSize = convertAspectRatioToSize(aspectRatio);
199
- }
200
- const falOptions = await parseProviderOptions({
201
- provider: "fal",
202
- providerOptions,
203
- schema: falImageModelOptionsSchema
204
- });
205
- const requestBody = {
206
- prompt,
207
- seed,
208
- image_size: imageSize,
209
- num_images: n
210
- };
211
- if (files != null && files.length > 0) {
212
- const useMultipleImages = falOptions?.useMultipleImages === true;
213
- if (useMultipleImages) {
214
- requestBody.image_urls = files.map(
215
- (file) => convertImageModelFileToDataUri(file)
216
- );
217
- } else {
218
- requestBody.image_url = convertImageModelFileToDataUri(files[0]);
219
- if (files.length > 1) {
220
- warnings.push({
221
- type: "other",
222
- 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)."
223
- });
224
- }
225
- }
226
- }
227
- if (mask != null) {
228
- requestBody.mask_url = convertImageModelFileToDataUri(mask);
229
- }
230
- if (falOptions) {
231
- const deprecatedKeys = "__deprecatedKeys" in falOptions ? falOptions.__deprecatedKeys : void 0;
232
- if (deprecatedKeys && deprecatedKeys.length > 0) {
233
- warnings.push({
234
- type: "other",
235
- 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) => {
236
- const camelCase = key.replace(
237
- /_([a-z])/g,
238
- (_, letter) => letter.toUpperCase()
239
- );
240
- return `'${key}' (use '${camelCase}')`;
241
- }).join(", ")}`
242
- });
243
- }
244
- const fieldMapping = {
245
- imageUrl: "image_url",
246
- maskUrl: "mask_url",
247
- guidanceScale: "guidance_scale",
248
- numInferenceSteps: "num_inference_steps",
249
- enableSafetyChecker: "enable_safety_checker",
250
- outputFormat: "output_format",
251
- syncMode: "sync_mode",
252
- safetyTolerance: "safety_tolerance"
253
- };
254
- for (const [key, value] of Object.entries(falOptions)) {
255
- if (key === "__deprecatedKeys") continue;
256
- if (key === "useMultipleImages") continue;
257
- const apiKey = fieldMapping[key] ?? key;
258
- if (value !== void 0) {
259
- requestBody[apiKey] = value;
260
- }
261
- }
262
- }
263
- return { requestBody, warnings };
264
- }
265
- async doGenerate(options) {
266
- const { requestBody, warnings } = await this.getArgs(options);
267
- const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
268
- const { value, responseHeaders } = await postJsonToApi({
269
- url: `${this.config.baseURL}/${this.modelId}`,
270
- headers: combineHeaders(
271
- this.config.headers ? await resolve(this.config.headers) : void 0,
272
- options.headers
273
- ),
274
- body: requestBody,
275
- failedResponseHandler: falFailedResponseHandler,
276
- successfulResponseHandler: createJsonResponseHandler(
277
- falImageResponseSchema
278
- ),
279
- abortSignal: options.abortSignal,
280
- fetch: this.config.fetch
281
- });
282
- const {
283
- images: targetImages,
284
- // prompt is just passed through and not a revised prompt per image
285
- prompt: _prompt,
286
- // NSFW information is normalized merged into `providerMetadata.fal.images`
287
- has_nsfw_concepts,
288
- nsfw_content_detected,
289
- // pass through other properties to providerMetadata
290
- ...responseMetaData
291
- } = value;
292
- const downloadedImages = await Promise.all(
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
- return Object.fromEntries(
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
- switch (aspectRatio) {
356
- case "1:1":
357
- return "square_hd";
358
- case "16:9":
359
- return "landscape_16_9";
360
- case "9:16":
361
- return "portrait_16_9";
362
- case "4:3":
363
- return "landscape_4_3";
364
- case "3:4":
365
- return "portrait_4_3";
366
- case "16:10":
367
- return { width: 1280, height: 800 };
368
- case "10:16":
369
- return { width: 800, height: 1280 };
370
- case "21:9":
371
- return { width: 2560, height: 1080 };
372
- case "9:21":
373
- return { width: 1080, height: 2560 };
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
- var falValidationErrorSchema = z2.object({
377
- detail: z2.array(
378
- z2.object({
379
- loc: z2.array(z2.string()),
380
- msg: z2.string(),
381
- type: z2.string()
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
- var falHttpErrorSchema = z2.object({
386
- message: z2.string()
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
- var falErrorSchema = z2.union([falValidationErrorSchema, falHttpErrorSchema]);
389
- var falImageSchema = z2.object({
390
- url: z2.string(),
391
- width: z2.number().nullish(),
392
- height: z2.number().nullish(),
393
- // e.g. https://fal.ai/models/fal-ai/fashn/tryon/v1.6/api#schema-output
394
- content_type: z2.string().nullish(),
395
- // e.g. https://fal.ai/models/fal-ai/flowedit/api#schema-output
396
- file_name: z2.string().nullish(),
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
- var loraFileSchema = z2.object({
401
- url: z2.string(),
402
- content_type: z2.string().optional(),
403
- file_name: z2.string().nullable().optional(),
404
- file_data: z2.string().optional(),
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
- return falValidationErrorSchema.safeParse(error).success;
336
+ return falValidationErrorSchema.safeParse(error).success;
428
337
  }
429
- var falFailedResponseHandler = createJsonErrorResponseHandler({
430
- errorSchema: falErrorSchema,
431
- errorToMessage: (error) => {
432
- if (isValidationError(error)) {
433
- return error.detail.map((detail) => `${detail.loc.join(".")}: ${detail.msg}`).join("\n");
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
- var falFailedResponseHandler2 = createJsonErrorResponseHandler2({
468
- errorSchema: falErrorDataSchema,
469
- errorToMessage: (data) => data.error.message
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
- // src/fal-transcription-model-options.ts
473
- import { z as z4 } from "zod/v4";
474
- var falTranscriptionModelOptionsSchema = z4.object({
475
- /**
476
- * Language of the audio file. If set to null, the language will be automatically detected. Defaults to null.
477
- *
478
- * If translate is selected as the task, the audio will be translated to English, regardless of the language selected.
479
- */
480
- language: z4.union([z4.enum(["en"]), z4.string()]).nullish().default("en"),
481
- /**
482
- * Whether to diarize the audio file. Defaults to true.
483
- */
484
- diarize: z4.boolean().nullish().default(true),
485
- /**
486
- * Level of the chunks to return. Either segment or word. Default value: "segment"
487
- */
488
- chunkLevel: z4.enum(["segment", "word"]).nullish().default("segment"),
489
- /**
490
- * Version of the model to use. All of the models are the Whisper large variant. Default value: "3"
491
- */
492
- version: z4.enum(["3"]).nullish().default("3"),
493
- /**
494
- * Default value: 64
495
- */
496
- batchSize: z4.number().nullish().default(64),
497
- /**
498
- * Number of speakers in the audio file. Defaults to null. If not provided, the number of speakers will be automatically detected.
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
- // src/fal-transcription-model.ts
504
- var FalTranscriptionModel = class _FalTranscriptionModel {
505
- constructor(modelId, config) {
506
- this.modelId = modelId;
507
- this.config = config;
508
- this.specificationVersion = "v4";
509
- }
510
- get provider() {
511
- return this.config.provider;
512
- }
513
- static [WORKFLOW_SERIALIZE2](model) {
514
- return serializeModelOptions2({
515
- modelId: model.modelId,
516
- config: model.config
517
- });
518
- }
519
- static [WORKFLOW_DESERIALIZE2](options) {
520
- return new _FalTranscriptionModel(options.modelId, options.config);
521
- }
522
- async getArgs({
523
- providerOptions
524
- }) {
525
- const warnings = [];
526
- const falOptions = await parseProviderOptions2({
527
- provider: "fal",
528
- providerOptions,
529
- schema: falTranscriptionModelOptionsSchema
530
- });
531
- const body = {
532
- task: "transcribe",
533
- diarize: true,
534
- chunk_level: "word"
535
- };
536
- if (falOptions) {
537
- body.language = falOptions.language;
538
- body.version = falOptions.version ?? void 0;
539
- body.batch_size = falOptions.batchSize ?? void 0;
540
- body.num_speakers = falOptions.numSpeakers ?? void 0;
541
- if (typeof falOptions.diarize === "boolean") {
542
- body.diarize = falOptions.diarize;
543
- }
544
- if (falOptions.chunkLevel) {
545
- body.chunk_level = falOptions.chunkLevel;
546
- }
547
- }
548
- return {
549
- body,
550
- warnings
551
- };
552
- }
553
- async doGenerate(options) {
554
- const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
555
- const { body, warnings } = await this.getArgs(options);
556
- const base64Audio = typeof options.audio === "string" ? options.audio : convertUint8ArrayToBase64(options.audio);
557
- const audioUrl = `data:${options.mediaType};base64,${base64Audio}`;
558
- const { value: queueResponse } = await postJsonToApi2({
559
- url: this.config.url({
560
- path: `https://queue.fal.run/fal-ai/${this.modelId}`,
561
- modelId: this.modelId
562
- }),
563
- headers: combineHeaders2(this.config.headers?.(), options.headers),
564
- body: {
565
- ...body,
566
- audio_url: audioUrl
567
- },
568
- failedResponseHandler: falFailedResponseHandler2,
569
- successfulResponseHandler: createJsonResponseHandler2(falJobResponseSchema),
570
- abortSignal: options.abortSignal,
571
- fetch: this.config.fetch
572
- });
573
- const startTime = Date.now();
574
- const timeoutMs = 6e4;
575
- const pollIntervalMs = 1e3;
576
- let response;
577
- let responseHeaders;
578
- let rawResponse;
579
- while (true) {
580
- try {
581
- const {
582
- value: statusResponse,
583
- responseHeaders: statusHeaders,
584
- rawValue: statusRawResponse
585
- } = await getFromApi2({
586
- url: this.config.url({
587
- path: `https://queue.fal.run/fal-ai/${this.modelId}/requests/${queueResponse.request_id}`,
588
- modelId: this.modelId
589
- }),
590
- validateUrl: false,
591
- headers: combineHeaders2(this.config.headers?.(), options.headers),
592
- failedResponseHandler: async ({
593
- requestBodyValues,
594
- response: response2,
595
- url
596
- }) => {
597
- const clone = response2.clone();
598
- const body2 = await clone.json();
599
- if (body2.detail === "Request is still in progress") {
600
- return {
601
- value: new Error("Request is still in progress"),
602
- rawValue: body2,
603
- responseHeaders: {}
604
- };
605
- }
606
- return createJsonErrorResponseHandler3({
607
- errorSchema: falErrorDataSchema,
608
- errorToMessage: (data) => data.error.message
609
- })({ requestBodyValues, response: response2, url });
610
- },
611
- successfulResponseHandler: createJsonResponseHandler2(
612
- falTranscriptionResponseSchema
613
- ),
614
- abortSignal: options.abortSignal,
615
- fetch: this.config.fetch
616
- });
617
- response = statusResponse;
618
- responseHeaders = statusHeaders;
619
- rawResponse = statusRawResponse;
620
- break;
621
- } catch (error) {
622
- if (error instanceof Error && error.message === "Request is still in progress") {
623
- } else {
624
- throw error;
625
- }
626
- }
627
- if (Date.now() - startTime > timeoutMs) {
628
- throw new AISDKError({
629
- message: "Transcription request timed out after 60 seconds",
630
- name: "TranscriptionRequestTimedOut",
631
- cause: response
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
- var falJobResponseSchema = z5.object({
656
- request_id: z5.string().nullish()
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
- var falTranscriptionResponseSchema = z5.object({
659
- text: z5.string(),
660
- chunks: z5.array(
661
- z5.object({
662
- text: z5.string(),
663
- timestamp: z5.array(z5.number()).nullish()
664
- })
665
- ).nullish(),
666
- inferred_languages: z5.array(z5.string()).nullish()
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
- // src/fal-speech-model.ts
670
- import {
671
- combineHeaders as combineHeaders3,
672
- createBinaryResponseHandler as createBinaryResponseHandler2,
673
- createJsonResponseHandler as createJsonResponseHandler3,
674
- createStatusCodeErrorResponseHandler as createStatusCodeErrorResponseHandler2,
675
- getFromApi as getFromApi3,
676
- parseProviderOptions as parseProviderOptions3,
677
- postJsonToApi as postJsonToApi3,
678
- serializeModelOptions as serializeModelOptions3,
679
- WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3,
680
- WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3
681
- } from "@ai-sdk/provider-utils";
682
- import { z as z7 } from "zod/v4";
683
-
684
- // src/fal-speech-model-options.ts
685
- import { z as z6 } from "zod/v4";
686
-
687
- // src/fal-api-types.ts
688
- var FAL_LANGUAGE_BOOSTS = [
689
- "Chinese",
690
- "Chinese,Yue",
691
- "English",
692
- "Arabic",
693
- "Russian",
694
- "Spanish",
695
- "French",
696
- "Portuguese",
697
- "German",
698
- "Turkish",
699
- "Dutch",
700
- "Ukrainian",
701
- "Vietnamese",
702
- "Indonesian",
703
- "Japanese",
704
- "Italian",
705
- "Korean",
706
- "Thai",
707
- "Polish",
708
- "Romanian",
709
- "Greek",
710
- "Czech",
711
- "Finnish",
712
- "Hindi",
713
- "auto"
714
- ];
715
- var FAL_EMOTIONS = [
716
- "happy",
717
- "sad",
718
- "angry",
719
- "fearful",
720
- "disgusted",
721
- "surprised",
722
- "neutral"
723
- ];
724
-
725
- // src/fal-speech-model-options.ts
726
- var falSpeechModelOptionsSchema = z6.looseObject({
727
- voice_setting: z6.object({
728
- speed: z6.number().nullish(),
729
- vol: z6.number().nullish(),
730
- voice_id: z6.string().nullish(),
731
- pitch: z6.number().nullish(),
732
- english_normalization: z6.boolean().nullish(),
733
- emotion: z6.enum(FAL_EMOTIONS).nullish()
734
- }).partial().nullish(),
735
- audio_setting: z6.record(z6.string(), z6.unknown()).nullish(),
736
- language_boost: z6.enum(FAL_LANGUAGE_BOOSTS).nullish(),
737
- pronunciation_dict: z6.record(z6.string(), z6.string()).nullish()
738
- });
739
-
740
- // src/fal-speech-model.ts
741
- var FalSpeechModel = class _FalSpeechModel {
742
- constructor(modelId, config) {
743
- this.modelId = modelId;
744
- this.config = config;
745
- this.specificationVersion = "v4";
746
- }
747
- get provider() {
748
- return this.config.provider;
749
- }
750
- static [WORKFLOW_SERIALIZE3](model) {
751
- return serializeModelOptions3({
752
- modelId: model.modelId,
753
- config: model.config
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
- var falSpeechResponseSchema = z7.object({
845
- audio: z7.object({ url: z7.string() }),
846
- duration_ms: z7.number().optional(),
847
- request_id: z7.string().optional()
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
- // src/fal-video-model.ts
851
- import {
852
- AISDKError as AISDKError2,
853
- APICallError
854
- } from "@ai-sdk/provider";
855
- import {
856
- combineHeaders as combineHeaders4,
857
- convertImageModelFileToDataUri as convertImageModelFileToDataUri2,
858
- createJsonErrorResponseHandler as createJsonErrorResponseHandler4,
859
- createJsonResponseHandler as createJsonResponseHandler4,
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
- constructor(modelId, config) {
888
- this.modelId = modelId;
889
- this.config = config;
890
- this.specificationVersion = "v4";
891
- this.maxVideosPerCall = 1;
892
- }
893
- // FAL video models support 1 video at a time
894
- get provider() {
895
- return this.config.provider;
896
- }
897
- get normalizedModelId() {
898
- return this.modelId.replace(/^fal-ai\//, "").replace(/^fal\//, "");
899
- }
900
- async handleWebhookOption(options) {
901
- const { url, received } = await options.webhook();
902
- return { webhookUrl: url, received };
903
- }
904
- async doStart(options) {
905
- const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
906
- const warnings = [];
907
- const { body } = await this.buildRequestBody(options);
908
- const { responseUrl, submitUrl } = await this.submitToQueue(
909
- {
910
- headers: options.headers,
911
- abortSignal: options.abortSignal,
912
- webhookUrl: options.webhookUrl
913
- },
914
- body
915
- );
916
- return {
917
- operation: { responseUrl, submitUrl },
918
- warnings,
919
- response: {
920
- timestamp: currentDate,
921
- modelId: this.modelId,
922
- headers: void 0
923
- }
924
- };
925
- }
926
- async doStatus(options) {
927
- const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
928
- const { responseUrl, submitUrl } = options.operation;
929
- try {
930
- const { value: response, responseHeaders } = await this.fetchStatus({
931
- responseUrl,
932
- submitUrl,
933
- headers: options.headers,
934
- abortSignal: options.abortSignal
935
- });
936
- return this.buildResult({
937
- response,
938
- responseHeaders,
939
- warnings: [],
940
- currentDate
941
- });
942
- } catch (error) {
943
- if (error instanceof Error && error.message === "Request is still in progress") {
944
- return {
945
- status: "pending",
946
- response: {
947
- timestamp: currentDate,
948
- modelId: this.modelId,
949
- headers: void 0
950
- }
951
- };
952
- }
953
- if (APICallError.isInstance(error)) {
954
- return {
955
- status: "error",
956
- error: error.message,
957
- response: {
958
- timestamp: currentDate,
959
- modelId: this.modelId,
960
- headers: error.responseHeaders
961
- }
962
- };
963
- }
964
- throw error;
965
- }
966
- }
967
- async buildRequestBody(options) {
968
- const falOptions = await parseProviderOptions4({
969
- provider: "fal",
970
- providerOptions: options.providerOptions,
971
- schema: falVideoModelOptionsSchema
972
- });
973
- const body = {};
974
- if (options.prompt != null) {
975
- body.prompt = options.prompt;
976
- }
977
- if (options.image != null) {
978
- if (options.image.type === "url") {
979
- body.image_url = options.image.url;
980
- } else {
981
- body.image_url = convertImageModelFileToDataUri2(options.image);
982
- }
983
- }
984
- if (options.aspectRatio) {
985
- body.aspect_ratio = options.aspectRatio;
986
- }
987
- if (options.duration) {
988
- body.duration = `${options.duration}s`;
989
- }
990
- if (options.seed) {
991
- body.seed = options.seed;
992
- }
993
- if (falOptions != null) {
994
- const opts = falOptions;
995
- if (opts.loop !== void 0 && opts.loop !== null) {
996
- body.loop = opts.loop;
997
- }
998
- if (opts.motionStrength !== void 0 && opts.motionStrength !== null) {
999
- body.motion_strength = opts.motionStrength;
1000
- }
1001
- if (opts.resolution !== void 0 && opts.resolution !== null) {
1002
- body.resolution = opts.resolution;
1003
- }
1004
- if (opts.negativePrompt !== void 0 && opts.negativePrompt !== null) {
1005
- body.negative_prompt = opts.negativePrompt;
1006
- }
1007
- if (opts.promptOptimizer !== void 0 && opts.promptOptimizer !== null) {
1008
- body.prompt_optimizer = opts.promptOptimizer;
1009
- }
1010
- for (const [key, value] of Object.entries(opts)) {
1011
- if (![
1012
- "loop",
1013
- "motionStrength",
1014
- "resolution",
1015
- "negativePrompt",
1016
- "promptOptimizer"
1017
- ].includes(key)) {
1018
- body[key] = value;
1019
- }
1020
- }
1021
- }
1022
- return { body, falOptions };
1023
- }
1024
- async submitToQueue(options, body) {
1025
- let queuePath = `https://queue.fal.run/fal-ai/${this.normalizedModelId}`;
1026
- if (options.webhookUrl) {
1027
- queuePath += `?fal_webhook=${encodeURIComponent(options.webhookUrl)}`;
1028
- }
1029
- const submitUrl = this.config.url({
1030
- path: queuePath,
1031
- modelId: this.modelId
1032
- });
1033
- const { value: queueResponse } = await postJsonToApi4({
1034
- url: submitUrl,
1035
- headers: combineHeaders4(this.config.headers?.(), options.headers),
1036
- body,
1037
- failedResponseHandler: falFailedResponseHandler2,
1038
- successfulResponseHandler: createJsonResponseHandler4(falJobResponseSchema2),
1039
- abortSignal: options.abortSignal,
1040
- fetch: this.config.fetch
1041
- });
1042
- const responseUrl = queueResponse.response_url;
1043
- if (!responseUrl) {
1044
- throw new AISDKError2({
1045
- name: "FAL_VIDEO_GENERATION_ERROR",
1046
- message: "No response URL returned from queue endpoint"
1047
- });
1048
- }
1049
- return { responseUrl, submitUrl };
1050
- }
1051
- async fetchStatus({
1052
- responseUrl,
1053
- submitUrl,
1054
- headers,
1055
- abortSignal
1056
- }) {
1057
- const statusUrl = this.config.url({
1058
- path: responseUrl,
1059
- modelId: this.modelId
1060
- });
1061
- return getFromApi4({
1062
- url: statusUrl,
1063
- // statusUrl comes from the queue response body.
1064
- validateUrl: true,
1065
- credentialedOrigin: submitUrl,
1066
- trustedOrigin: submitUrl,
1067
- headers: combineHeaders4(this.config.headers?.(), headers),
1068
- failedResponseHandler: async ({ response, url, requestBodyValues }) => {
1069
- const body = await response.clone().json();
1070
- if (body.detail === "Request is still in progress") {
1071
- return {
1072
- value: new Error("Request is still in progress"),
1073
- rawValue: body,
1074
- responseHeaders: {}
1075
- };
1076
- }
1077
- return createJsonErrorResponseHandler4({
1078
- errorSchema: falErrorDataSchema,
1079
- errorToMessage: (data) => data.error.message
1080
- })({ response, url, requestBodyValues });
1081
- },
1082
- successfulResponseHandler: createJsonResponseHandler4(
1083
- falVideoResponseSchema
1084
- ),
1085
- abortSignal,
1086
- fetch: this.config.fetch
1087
- });
1088
- }
1089
- buildResult({
1090
- response,
1091
- responseHeaders,
1092
- warnings,
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
- var falJobResponseSchema2 = z9.object({
1139
- request_id: z9.string().nullish(),
1140
- response_url: z9.string().nullish()
888
+ const falJobResponseSchema = z.object({
889
+ request_id: z.string().nullish(),
890
+ response_url: z.string().nullish()
1141
891
  });
1142
- var falVideoResponseSchema = z9.object({
1143
- video: z9.object({
1144
- url: z9.string(),
1145
- width: z9.number().nullish(),
1146
- height: z9.number().nullish(),
1147
- duration: z9.number().nullish(),
1148
- fps: z9.number().nullish(),
1149
- content_type: z9.string().nullish()
1150
- }).nullish(),
1151
- seed: z9.number().nullish(),
1152
- timings: z9.object({
1153
- inference: z9.number().nullish()
1154
- }).nullish(),
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
- // src/version.ts
1160
- var VERSION = true ? "3.0.51" : "0.0.0-test";
1161
-
1162
- // src/fal-provider.ts
1163
- var defaultBaseURL = "https://fal.run";
1164
- function loadFalApiKey({
1165
- apiKey,
1166
- description = "fal.ai"
1167
- }) {
1168
- if (typeof apiKey === "string") {
1169
- return apiKey;
1170
- }
1171
- if (apiKey != null) {
1172
- throw new Error(`${description} API key must be a string.`);
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.53";
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
- const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);
1197
- const getHeaders = () => withUserAgentSuffix(
1198
- {
1199
- Authorization: `Key ${loadFalApiKey({
1200
- apiKey: options.apiKey
1201
- })}`,
1202
- ...options.headers
1203
- },
1204
- `ai-sdk/fal/${VERSION}`
1205
- );
1206
- const createImageModel = (modelId) => new FalImageModel(modelId, {
1207
- provider: "fal.image",
1208
- baseURL: baseURL ?? defaultBaseURL,
1209
- headers: getHeaders,
1210
- fetch: options.fetch
1211
- });
1212
- const createSpeechModel = (modelId) => new FalSpeechModel(modelId, {
1213
- provider: `fal.speech`,
1214
- url: ({ path }) => path,
1215
- headers: getHeaders,
1216
- fetch: options.fetch
1217
- });
1218
- const createTranscriptionModel = (modelId) => new FalTranscriptionModel(modelId, {
1219
- provider: `fal.transcription`,
1220
- url: ({ path }) => path,
1221
- headers: getHeaders,
1222
- fetch: options.fetch
1223
- });
1224
- const createVideoModel = (modelId) => new FalVideoModel(modelId, {
1225
- provider: "fal.video",
1226
- url: ({ path }) => path,
1227
- headers: getHeaders,
1228
- fetch: options.fetch
1229
- });
1230
- const embeddingModel = (modelId) => {
1231
- throw new NoSuchModelError({
1232
- modelId,
1233
- modelType: "embeddingModel"
1234
- });
1235
- };
1236
- return {
1237
- specificationVersion: "v4",
1238
- imageModel: createImageModel,
1239
- image: createImageModel,
1240
- languageModel: (modelId) => {
1241
- throw new NoSuchModelError({
1242
- modelId,
1243
- modelType: "languageModel"
1244
- });
1245
- },
1246
- speech: createSpeechModel,
1247
- embeddingModel,
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
- var fal = createFal();
1255
- export {
1256
- VERSION,
1257
- createFal,
1258
- fal
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