@ai-sdk/fal 2.0.17 → 2.0.19

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # @ai-sdk/fal
2
2
 
3
+ ## 2.0.19
4
+
5
+ ### Patch Changes
6
+
7
+ - 7168375: feat (ai, provider): default global provider video model resolution
8
+ - Updated dependencies [7168375]
9
+ - @ai-sdk/provider@3.0.8
10
+ - @ai-sdk/provider-utils@4.0.14
11
+
12
+ ## 2.0.18
13
+
14
+ ### Patch Changes
15
+
16
+ - 53f6731: feat (ai, provider): experimental generate video support
17
+ - Updated dependencies [53f6731]
18
+ - @ai-sdk/provider@3.0.7
19
+ - @ai-sdk/provider-utils@4.0.13
20
+
3
21
  ## 2.0.17
4
22
 
5
23
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ProviderV3, ImageModelV3, TranscriptionModelV3, SpeechModelV3 } from '@ai-sdk/provider';
1
+ import { ProviderV3, ImageModelV3, TranscriptionModelV3, Experimental_VideoModelV3, SpeechModelV3 } from '@ai-sdk/provider';
2
2
  import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
3
3
  import { FetchFunction, InferSchema } from '@ai-sdk/provider-utils';
4
4
 
@@ -8,6 +8,8 @@ type FalTranscriptionModelId = 'whisper' | 'wizper' | (string & {});
8
8
 
9
9
  type FalSpeechModelId = 'fal-ai/minimax/voice-clone' | 'fal-ai/minimax/voice-design' | 'fal-ai/dia-tts/voice-clone' | 'fal-ai/minimax/speech-02-hd' | 'fal-ai/minimax/speech-02-turbo' | 'fal-ai/dia-tts' | 'resemble-ai/chatterboxhd/text-to-speech' | (string & {});
10
10
 
11
+ type FalVideoModelId = 'luma-dream-machine' | 'luma-ray-2' | 'luma-ray-2-flash' | 'minimax-video' | 'minimax-video-01' | 'hunyuan-video' | (string & {});
12
+
11
13
  interface FalProviderSettings {
12
14
  /**
13
15
  * fal.ai API key. Default value is taken from the `FAL_API_KEY` environment
@@ -42,6 +44,14 @@ interface FalProvider extends ProviderV3 {
42
44
  * Creates a model for transcription.
43
45
  */
44
46
  transcription(modelId: FalTranscriptionModelId): TranscriptionModelV3;
47
+ /**
48
+ * Creates a model for video generation.
49
+ */
50
+ video(modelId: FalVideoModelId): Experimental_VideoModelV3;
51
+ /**
52
+ * Creates a model for video generation.
53
+ */
54
+ videoModel(modelId: FalVideoModelId): Experimental_VideoModelV3;
45
55
  /**
46
56
  * Creates a model for speech generation.
47
57
  */
@@ -63,6 +73,17 @@ declare const fal: FalProvider;
63
73
  declare const falImageProviderOptionsSchema: _ai_sdk_provider_utils.LazySchema<Record<string, unknown>>;
64
74
  type FalImageProviderOptions = InferSchema<typeof falImageProviderOptionsSchema>;
65
75
 
76
+ type FalVideoProviderOptions = {
77
+ loop?: boolean | null;
78
+ motionStrength?: number | null;
79
+ pollIntervalMs?: number | null;
80
+ pollTimeoutMs?: number | null;
81
+ resolution?: string | null;
82
+ negativePrompt?: string | null;
83
+ promptOptimizer?: boolean | null;
84
+ [key: string]: unknown;
85
+ };
86
+
66
87
  declare const VERSION: string;
67
88
 
68
- export { type FalImageProviderOptions, type FalProvider, type FalProviderSettings, VERSION, createFal, fal };
89
+ export { type FalImageProviderOptions, type FalProvider, type FalProviderSettings, type FalVideoModelId, type FalVideoProviderOptions, VERSION, createFal, fal };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ProviderV3, ImageModelV3, TranscriptionModelV3, SpeechModelV3 } from '@ai-sdk/provider';
1
+ import { ProviderV3, ImageModelV3, TranscriptionModelV3, Experimental_VideoModelV3, SpeechModelV3 } from '@ai-sdk/provider';
2
2
  import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
3
3
  import { FetchFunction, InferSchema } from '@ai-sdk/provider-utils';
4
4
 
@@ -8,6 +8,8 @@ type FalTranscriptionModelId = 'whisper' | 'wizper' | (string & {});
8
8
 
9
9
  type FalSpeechModelId = 'fal-ai/minimax/voice-clone' | 'fal-ai/minimax/voice-design' | 'fal-ai/dia-tts/voice-clone' | 'fal-ai/minimax/speech-02-hd' | 'fal-ai/minimax/speech-02-turbo' | 'fal-ai/dia-tts' | 'resemble-ai/chatterboxhd/text-to-speech' | (string & {});
10
10
 
11
+ type FalVideoModelId = 'luma-dream-machine' | 'luma-ray-2' | 'luma-ray-2-flash' | 'minimax-video' | 'minimax-video-01' | 'hunyuan-video' | (string & {});
12
+
11
13
  interface FalProviderSettings {
12
14
  /**
13
15
  * fal.ai API key. Default value is taken from the `FAL_API_KEY` environment
@@ -42,6 +44,14 @@ interface FalProvider extends ProviderV3 {
42
44
  * Creates a model for transcription.
43
45
  */
44
46
  transcription(modelId: FalTranscriptionModelId): TranscriptionModelV3;
47
+ /**
48
+ * Creates a model for video generation.
49
+ */
50
+ video(modelId: FalVideoModelId): Experimental_VideoModelV3;
51
+ /**
52
+ * Creates a model for video generation.
53
+ */
54
+ videoModel(modelId: FalVideoModelId): Experimental_VideoModelV3;
45
55
  /**
46
56
  * Creates a model for speech generation.
47
57
  */
@@ -63,6 +73,17 @@ declare const fal: FalProvider;
63
73
  declare const falImageProviderOptionsSchema: _ai_sdk_provider_utils.LazySchema<Record<string, unknown>>;
64
74
  type FalImageProviderOptions = InferSchema<typeof falImageProviderOptionsSchema>;
65
75
 
76
+ type FalVideoProviderOptions = {
77
+ loop?: boolean | null;
78
+ motionStrength?: number | null;
79
+ pollIntervalMs?: number | null;
80
+ pollTimeoutMs?: number | null;
81
+ resolution?: string | null;
82
+ negativePrompt?: string | null;
83
+ promptOptimizer?: boolean | null;
84
+ [key: string]: unknown;
85
+ };
86
+
66
87
  declare const VERSION: string;
67
88
 
68
- export { type FalImageProviderOptions, type FalProvider, type FalProviderSettings, VERSION, createFal, fal };
89
+ export { type FalImageProviderOptions, type FalProvider, type FalProviderSettings, type FalVideoModelId, type FalVideoProviderOptions, VERSION, createFal, fal };
package/dist/index.js CHANGED
@@ -27,8 +27,8 @@ __export(src_exports, {
27
27
  module.exports = __toCommonJS(src_exports);
28
28
 
29
29
  // src/fal-provider.ts
30
- var import_provider2 = require("@ai-sdk/provider");
31
- var import_provider_utils6 = require("@ai-sdk/provider-utils");
30
+ var import_provider3 = require("@ai-sdk/provider");
31
+ var import_provider_utils7 = require("@ai-sdk/provider-utils");
32
32
 
33
33
  // src/fal-image-model.ts
34
34
  var import_provider_utils2 = require("@ai-sdk/provider-utils");
@@ -763,8 +763,246 @@ var falSpeechResponseSchema = import_v45.z.object({
763
763
  request_id: import_v45.z.string().optional()
764
764
  });
765
765
 
766
+ // src/fal-video-model.ts
767
+ var import_provider2 = require("@ai-sdk/provider");
768
+ var import_provider_utils6 = require("@ai-sdk/provider-utils");
769
+ var import_v46 = require("zod/v4");
770
+ var falVideoProviderOptionsSchema = (0, import_provider_utils6.lazySchema)(
771
+ () => (0, import_provider_utils6.zodSchema)(
772
+ import_v46.z.object({
773
+ // Video loop - only for Luma models
774
+ loop: import_v46.z.boolean().nullish(),
775
+ // Motion strength (provider-specific)
776
+ motionStrength: import_v46.z.number().min(0).max(1).nullish(),
777
+ // Polling configuration
778
+ pollIntervalMs: import_v46.z.number().positive().nullish(),
779
+ pollTimeoutMs: import_v46.z.number().positive().nullish(),
780
+ // Resolution (model-specific, e.g., '480p', '720p', '1080p')
781
+ resolution: import_v46.z.string().nullish(),
782
+ // Model-specific parameters
783
+ negativePrompt: import_v46.z.string().nullish(),
784
+ promptOptimizer: import_v46.z.boolean().nullish()
785
+ }).passthrough()
786
+ )
787
+ );
788
+ var FalVideoModel = class {
789
+ constructor(modelId, config) {
790
+ this.modelId = modelId;
791
+ this.config = config;
792
+ this.specificationVersion = "v3";
793
+ this.maxVideosPerCall = 1;
794
+ }
795
+ // FAL video models support 1 video at a time
796
+ get provider() {
797
+ return this.config.provider;
798
+ }
799
+ get normalizedModelId() {
800
+ return this.modelId.replace(/^fal-ai\//, "").replace(/^fal\//, "");
801
+ }
802
+ async doGenerate(options) {
803
+ var _a, _b, _c, _d, _e, _f, _g;
804
+ const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
805
+ const warnings = [];
806
+ const falOptions = await (0, import_provider_utils6.parseProviderOptions)({
807
+ provider: "fal",
808
+ providerOptions: options.providerOptions,
809
+ schema: falVideoProviderOptionsSchema
810
+ });
811
+ const body = {};
812
+ if (options.prompt != null) {
813
+ body.prompt = options.prompt;
814
+ }
815
+ if (options.image != null) {
816
+ if (options.image.type === "url") {
817
+ body.image_url = options.image.url;
818
+ } else {
819
+ body.image_url = (0, import_provider_utils6.convertImageModelFileToDataUri)(options.image);
820
+ }
821
+ }
822
+ if (options.aspectRatio) {
823
+ body.aspect_ratio = options.aspectRatio;
824
+ }
825
+ if (options.duration) {
826
+ body.duration = `${options.duration}s`;
827
+ }
828
+ if (options.seed) {
829
+ body.seed = options.seed;
830
+ }
831
+ if (falOptions != null) {
832
+ const opts = falOptions;
833
+ if (opts.loop !== void 0 && opts.loop !== null) {
834
+ body.loop = opts.loop;
835
+ }
836
+ if (opts.motionStrength !== void 0 && opts.motionStrength !== null) {
837
+ body.motion_strength = opts.motionStrength;
838
+ }
839
+ if (opts.resolution !== void 0 && opts.resolution !== null) {
840
+ body.resolution = opts.resolution;
841
+ }
842
+ if (opts.negativePrompt !== void 0 && opts.negativePrompt !== null) {
843
+ body.negative_prompt = opts.negativePrompt;
844
+ }
845
+ if (opts.promptOptimizer !== void 0 && opts.promptOptimizer !== null) {
846
+ body.prompt_optimizer = opts.promptOptimizer;
847
+ }
848
+ for (const [key, value] of Object.entries(opts)) {
849
+ if (![
850
+ "loop",
851
+ "motionStrength",
852
+ "pollIntervalMs",
853
+ "pollTimeoutMs",
854
+ "resolution",
855
+ "negativePrompt",
856
+ "promptOptimizer"
857
+ ].includes(key)) {
858
+ body[key] = value;
859
+ }
860
+ }
861
+ }
862
+ const { value: queueResponse } = await (0, import_provider_utils6.postJsonToApi)({
863
+ url: this.config.url({
864
+ path: `https://queue.fal.run/fal-ai/${this.normalizedModelId}`,
865
+ modelId: this.modelId
866
+ }),
867
+ headers: (0, import_provider_utils6.combineHeaders)(this.config.headers(), options.headers),
868
+ body,
869
+ failedResponseHandler: falFailedResponseHandler2,
870
+ successfulResponseHandler: (0, import_provider_utils6.createJsonResponseHandler)(falJobResponseSchema2),
871
+ abortSignal: options.abortSignal,
872
+ fetch: this.config.fetch
873
+ });
874
+ const responseUrl = queueResponse.response_url;
875
+ if (!responseUrl) {
876
+ throw new import_provider2.AISDKError({
877
+ name: "FAL_VIDEO_GENERATION_ERROR",
878
+ message: "No response URL returned from queue endpoint"
879
+ });
880
+ }
881
+ const pollIntervalMs = (_d = falOptions == null ? void 0 : falOptions.pollIntervalMs) != null ? _d : 2e3;
882
+ const pollTimeoutMs = (_e = falOptions == null ? void 0 : falOptions.pollTimeoutMs) != null ? _e : 3e5;
883
+ const startTime = Date.now();
884
+ let response;
885
+ let responseHeaders;
886
+ while (true) {
887
+ try {
888
+ const { value: statusResponse, responseHeaders: statusHeaders } = await (0, import_provider_utils6.getFromApi)({
889
+ url: this.config.url({
890
+ path: responseUrl,
891
+ modelId: this.modelId
892
+ }),
893
+ headers: (0, import_provider_utils6.combineHeaders)(this.config.headers(), options.headers),
894
+ failedResponseHandler: async ({
895
+ response: response2,
896
+ url,
897
+ requestBodyValues
898
+ }) => {
899
+ const body2 = await response2.clone().json();
900
+ if (body2.detail === "Request is still in progress") {
901
+ return {
902
+ value: new Error("Request is still in progress"),
903
+ rawValue: body2,
904
+ responseHeaders: {}
905
+ };
906
+ }
907
+ return (0, import_provider_utils6.createJsonErrorResponseHandler)({
908
+ errorSchema: falErrorDataSchema,
909
+ errorToMessage: (data) => data.error.message
910
+ })({ response: response2, url, requestBodyValues });
911
+ },
912
+ successfulResponseHandler: (0, import_provider_utils6.createJsonResponseHandler)(
913
+ falVideoResponseSchema
914
+ ),
915
+ abortSignal: options.abortSignal,
916
+ fetch: this.config.fetch
917
+ });
918
+ response = statusResponse;
919
+ responseHeaders = statusHeaders;
920
+ break;
921
+ } catch (error) {
922
+ if (error instanceof Error && error.message === "Request is still in progress") {
923
+ } else {
924
+ throw error;
925
+ }
926
+ }
927
+ if (Date.now() - startTime > pollTimeoutMs) {
928
+ throw new import_provider2.AISDKError({
929
+ name: "FAL_VIDEO_GENERATION_TIMEOUT",
930
+ message: `Video generation request timed out after ${pollTimeoutMs}ms`
931
+ });
932
+ }
933
+ await (0, import_provider_utils6.delay)(pollIntervalMs);
934
+ if ((_f = options.abortSignal) == null ? void 0 : _f.aborted) {
935
+ throw new import_provider2.AISDKError({
936
+ name: "FAL_VIDEO_GENERATION_ABORTED",
937
+ message: "Video generation request was aborted"
938
+ });
939
+ }
940
+ }
941
+ const videoUrl = (_g = response.video) == null ? void 0 : _g.url;
942
+ if (!videoUrl || !response.video) {
943
+ throw new import_provider2.AISDKError({
944
+ name: "FAL_VIDEO_GENERATION_ERROR",
945
+ message: "No video URL in response"
946
+ });
947
+ }
948
+ return {
949
+ videos: [
950
+ {
951
+ type: "url",
952
+ url: videoUrl,
953
+ mediaType: response.video.content_type || "video/mp4"
954
+ }
955
+ ],
956
+ warnings,
957
+ response: {
958
+ timestamp: currentDate,
959
+ modelId: this.modelId,
960
+ headers: responseHeaders
961
+ },
962
+ providerMetadata: {
963
+ fal: {
964
+ videos: [
965
+ {
966
+ url: videoUrl,
967
+ width: response.video.width,
968
+ height: response.video.height,
969
+ duration: response.video.duration,
970
+ fps: response.video.fps,
971
+ contentType: response.video.content_type
972
+ }
973
+ ],
974
+ ...response.seed !== void 0 ? { seed: response.seed } : {},
975
+ ...response.timings ? { timings: response.timings } : {},
976
+ ...response.has_nsfw_concepts !== void 0 ? { has_nsfw_concepts: response.has_nsfw_concepts } : {},
977
+ ...response.prompt ? { prompt: response.prompt } : {}
978
+ }
979
+ }
980
+ };
981
+ }
982
+ };
983
+ var falJobResponseSchema2 = import_v46.z.object({
984
+ request_id: import_v46.z.string().nullish(),
985
+ response_url: import_v46.z.string().nullish()
986
+ });
987
+ var falVideoResponseSchema = import_v46.z.object({
988
+ video: import_v46.z.object({
989
+ url: import_v46.z.string(),
990
+ width: import_v46.z.number().nullish(),
991
+ height: import_v46.z.number().nullish(),
992
+ duration: import_v46.z.number().nullish(),
993
+ fps: import_v46.z.number().nullish(),
994
+ content_type: import_v46.z.string().nullish()
995
+ }).nullish(),
996
+ seed: import_v46.z.number().nullish(),
997
+ timings: import_v46.z.object({
998
+ inference: import_v46.z.number().nullish()
999
+ }).nullish(),
1000
+ has_nsfw_concepts: import_v46.z.array(import_v46.z.boolean()).nullish(),
1001
+ prompt: import_v46.z.string().nullish()
1002
+ });
1003
+
766
1004
  // src/version.ts
767
- var VERSION = true ? "2.0.17" : "0.0.0-test";
1005
+ var VERSION = true ? "2.0.19" : "0.0.0-test";
768
1006
 
769
1007
  // src/fal-provider.ts
770
1008
  var defaultBaseURL = "https://fal.run";
@@ -801,8 +1039,8 @@ function loadFalApiKey({
801
1039
  }
802
1040
  function createFal(options = {}) {
803
1041
  var _a;
804
- const baseURL = (0, import_provider_utils6.withoutTrailingSlash)((_a = options.baseURL) != null ? _a : defaultBaseURL);
805
- const getHeaders = () => (0, import_provider_utils6.withUserAgentSuffix)(
1042
+ const baseURL = (0, import_provider_utils7.withoutTrailingSlash)((_a = options.baseURL) != null ? _a : defaultBaseURL);
1043
+ const getHeaders = () => (0, import_provider_utils7.withUserAgentSuffix)(
806
1044
  {
807
1045
  Authorization: `Key ${loadFalApiKey({
808
1046
  apiKey: options.apiKey
@@ -829,8 +1067,14 @@ function createFal(options = {}) {
829
1067
  headers: getHeaders,
830
1068
  fetch: options.fetch
831
1069
  });
1070
+ const createVideoModel = (modelId) => new FalVideoModel(modelId, {
1071
+ provider: "fal.video",
1072
+ url: ({ path }) => path,
1073
+ headers: getHeaders,
1074
+ fetch: options.fetch
1075
+ });
832
1076
  const embeddingModel = (modelId) => {
833
- throw new import_provider2.NoSuchModelError({
1077
+ throw new import_provider3.NoSuchModelError({
834
1078
  modelId,
835
1079
  modelType: "embeddingModel"
836
1080
  });
@@ -840,7 +1084,7 @@ function createFal(options = {}) {
840
1084
  imageModel: createImageModel,
841
1085
  image: createImageModel,
842
1086
  languageModel: (modelId) => {
843
- throw new import_provider2.NoSuchModelError({
1087
+ throw new import_provider3.NoSuchModelError({
844
1088
  modelId,
845
1089
  modelType: "languageModel"
846
1090
  });
@@ -848,7 +1092,9 @@ function createFal(options = {}) {
848
1092
  speech: createSpeechModel,
849
1093
  embeddingModel,
850
1094
  textEmbeddingModel: embeddingModel,
851
- transcription: createTranscriptionModel
1095
+ transcription: createTranscriptionModel,
1096
+ video: createVideoModel,
1097
+ videoModel: createVideoModel
852
1098
  };
853
1099
  }
854
1100
  var fal = createFal();