@picsart/ai-sdk 6.10.2 → 6.11.1

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.
Files changed (3) hide show
  1. package/index.d.ts +37 -30
  2. package/index.js +785 -757
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -2866,327 +2866,623 @@ var { MODELS: MODELS8 } = defineModels("hunyuan", [
2866
2866
  }
2867
2867
  ]);
2868
2868
 
2869
- // src/vendors/catalog/hailuo.ts
2870
- var buildT2V = (withDuration) => (ctx) => ({
2871
- prompt: ctx.prompt,
2872
- ...ctx.enhancePrompt !== void 0 ? { prompt_optimizer: ctx.enhancePrompt } : {},
2873
- ...withDuration && ctx.duration ? { duration: ctx.duration } : {}
2874
- });
2875
- var buildI2V = (withDuration) => (ctx) => ({
2869
+ // src/vendors/catalog/minimax.ts
2870
+ var buildMinimaxMusicPayload = (ctx) => ({
2876
2871
  prompt: ctx.prompt,
2877
- image_url: ctx.imageUrls?.[0],
2878
- ...ctx.enhancePrompt !== void 0 ? { prompt_optimizer: ctx.enhancePrompt } : {},
2879
- ...withDuration && ctx.duration ? { duration: ctx.duration } : {}
2872
+ ...ctx.lyricsPrompt ? { lyrics: ctx.lyricsPrompt } : {},
2873
+ ...ctx.lyricsOptimizer != null ? { lyrics_optimizer: ctx.lyricsOptimizer } : {},
2874
+ ...ctx.isInstrumental != null ? { is_instrumental: ctx.isInstrumental } : {},
2875
+ audio_setting: {
2876
+ sample_rate: ctx.sampleRate ?? 44100,
2877
+ bitrate: ctx.bitrate ?? 256e3,
2878
+ format: ctx.format ?? "mp3"
2879
+ }
2880
2880
  });
2881
- var buildMinimaxH3 = (ctx) => {
2882
- const content = [{ type: "text", text: ctx.prompt }];
2883
- if (ctx.startFrame) content.push({ type: "image_url", image_url: { url: ctx.startFrame }, role: "first_frame" });
2884
- if (ctx.endFrame) content.push({ type: "image_url", image_url: { url: ctx.endFrame }, role: "last_frame" });
2885
- for (const url of ctx.imageUrls ?? []) content.push({ type: "image_url", image_url: { url }, role: "reference_image" });
2886
- for (const url of ctx.videoUrls ?? []) content.push({ type: "video_url", video_url: { url }, role: "reference_video" });
2887
- for (const url of ctx.audioUrls ?? []) content.push({ type: "audio_url", audio_url: { url }, role: "reference_audio" });
2888
- return {
2889
- model: "MiniMax-H3",
2890
- content,
2891
- resolution: ctx.resolution ?? "2K",
2892
- duration: ctx.duration ?? 5,
2893
- ...ctx.aspectRatio ? { ratio: ctx.aspectRatio } : {}
2894
- };
2881
+ var H3_MAX_PROMPT_MAX = 5e4;
2882
+ var H3_IMAGE_BOUNDS = {
2883
+ minSidePixels: 256,
2884
+ maxSidePixels: 5760,
2885
+ minAspectRatio: 0.4,
2886
+ maxAspectRatio: 2.5
2887
+ };
2888
+ var H3_VIDEO_BOUNDS = {
2889
+ minDurationSec: 2,
2890
+ maxDurationSec: 15,
2891
+ maxFrameRate: 60,
2892
+ maxBytes: 52428800
2893
+ };
2894
+ var H3_AUDIO_BOUNDS = { minDurationSec: 2, maxDurationSec: 15 };
2895
+ var FRAME_REF_EXCLUSIVE = "References and start/end frames cannot be combined.";
2896
+ var AUDIO_NEEDS_VISUAL = "Audio cannot be the only reference \u2014 add an image or video.";
2897
+ var RATIO_FOLLOWS_FRAME = "Aspect ratio follows the frame image.";
2898
+ var ADAPTIVE_NEEDS_REFS = "Adaptive aspect ratio requires reference inputs.";
2899
+ var H3_MAX_RATIOS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"];
2900
+ var REFERENCE_EXCLUDES_FRAMES = {
2901
+ startFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2902
+ endFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE }
2895
2903
  };
2896
- var FRAME_REF_EXCLUSIVE = "First/last frame and reference inputs cannot be combined.";
2897
- var LAST_NEEDS_FIRST = "An end frame requires a start frame.";
2898
- var AUDIO_NEEDS_VISUAL = "Reference audio needs a reference image or video.";
2899
- var minimaxH3Constraints = [
2900
- // Frame roles ⊥ reference roles (declared both ways so either input disables the other).
2904
+ var FRAME_EXCLUDES_REFERENCES = {
2905
+ imageUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2906
+ videoUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2907
+ audioUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE }
2908
+ };
2909
+ var h3MaxConstraints = [
2910
+ { when: { startFrame: { exists: true } }, then: FRAME_EXCLUDES_REFERENCES },
2911
+ { when: { endFrame: { exists: true } }, then: FRAME_EXCLUDES_REFERENCES },
2912
+ { when: { imageUrls: { exists: true } }, then: REFERENCE_EXCLUDES_FRAMES },
2913
+ { when: { videoUrls: { exists: true } }, then: REFERENCE_EXCLUDES_FRAMES },
2914
+ { when: { audioUrls: { exists: true } }, then: REFERENCE_EXCLUDES_FRAMES },
2915
+ // With a frame the output follows that image, so the ratio is not a choice.
2916
+ // An end frame alone is valid — it generates towards that keyframe.
2901
2917
  { when: { startFrame: { exists: true } }, then: {
2902
- imageUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2903
- videoUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2904
- audioUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE }
2918
+ aspectRatio: { disabled: true, reason: RATIO_FOLLOWS_FRAME }
2905
2919
  } },
2906
2920
  { when: { endFrame: { exists: true } }, then: {
2907
- imageUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2908
- videoUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2909
- audioUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE }
2910
- } },
2911
- { when: { imageUrls: { exists: true } }, then: {
2912
- startFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2913
- endFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE }
2914
- } },
2915
- { when: { videoUrls: { exists: true } }, then: {
2916
- startFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2917
- endFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE }
2918
- } },
2919
- { when: { audioUrls: { exists: true } }, then: {
2920
- startFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE },
2921
- endFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE }
2921
+ aspectRatio: { disabled: true, reason: RATIO_FOLLOWS_FRAME }
2922
2922
  } },
2923
- // last_frame requires first_frame.
2924
- { when: { startFrame: { exists: false } }, then: {
2925
- endFrame: { disabled: true, reason: LAST_NEEDS_FIRST }
2923
+ // 'adaptive' means "follow the references" — no meaning without them.
2924
+ { when: { imageUrls: { exists: false }, videoUrls: { exists: false }, audioUrls: { exists: false } }, then: {
2925
+ aspectRatio: { allowed: [...H3_MAX_RATIOS], reason: ADAPTIVE_NEEDS_REFS }
2926
2926
  } },
2927
- // reference_audio cannot be the only reference input.
2927
+ // Audio carries no visual signal, so it cannot be the only reference.
2928
2928
  { when: { imageUrls: { exists: false }, videoUrls: { exists: false } }, then: {
2929
2929
  audioUrls: { disabled: true, reason: AUDIO_NEEDS_VISUAL }
2930
2930
  } }
2931
2931
  ];
2932
- var base = {
2933
- mode: "video"
2934
- };
2935
2932
  var { MODELS: MODELS9 } = defineModels("minimax", [
2933
+ // minimax-02-hd (minimax-tts) was removed in 6.0: its backend workflow was
2934
+ // never deployed anywhere, so no generation ever existed to resolve — nothing
2935
+ // to deprecate. Re-add as a fresh entry if MiniMax TTS ever ships.
2936
2936
  {
2937
- ...base,
2938
- id: "hailuo-2.3",
2939
- name: "Hailuo 2.3",
2940
- modelId: "hailuo-2.3",
2941
- addedAt: "2026-02-06",
2942
- inputType: "t2v",
2943
- workflow: "minimax/hailuo-2.3/standard/text-to-video",
2944
- editWorkflow: "minimax/hailuo-2.3/standard/image-to-video",
2945
- buildPayload: buildT2V(true),
2946
- buildEditPayload: buildI2V(true),
2947
- estimatedTime: 150,
2948
- description: "Stylized 768p animation with strong character expression and emotion.",
2949
- features: [feat("Image Input", "input"), feat("Start Frame", "frame"), feat("768p", "resolution"), feat("10 sec", "duration")],
2950
- paramConfig: {
2951
- ...params.prompt({ maxLength: 2e3 }),
2952
- ...params.enhancePrompt(),
2953
- ...params.duration([6, 10]),
2954
- ...params.imageInput()
2955
- }
2956
- },
2957
- {
2958
- ...base,
2959
- id: "hailuo-2.3-pro",
2960
- name: "Hailuo 2.3 Pro",
2961
- modelId: "hailuo-2.3-pro",
2937
+ id: "minimax-music-v2",
2938
+ name: "MiniMax Music v2",
2962
2939
  addedAt: "2026-02-06",
2963
- inputType: "t2v",
2964
- workflow: "minimax/hailuo-2.3/pro/text-to-video",
2965
- editWorkflow: "minimax/hailuo-2.3/pro/image-to-video",
2966
- buildPayload: buildT2V(false),
2967
- buildEditPayload: buildI2V(false),
2968
- estimatedTime: 165,
2969
- description: "1080p output focused on detailed scenes and polished short-form content.",
2970
- features: [feat("Image Input", "input"), feat("Start Frame", "frame"), feat("1080p", "resolution"), feat("6 sec", "duration")],
2940
+ workflow: "minimax-music/v2",
2941
+ buildPayload: buildMinimaxMusicPayload,
2942
+ estimatedTime: 39,
2943
+ mode: "audio",
2944
+ inputType: "music",
2945
+ description: "Text-to-music with vocals or instrumentals from a style prompt and lyrics prompt.",
2946
+ features: [feat("Music", "characteristic"), feat("Vocals", "characteristic")],
2971
2947
  paramConfig: {
2972
- ...params.prompt({ maxLength: 2e3 }),
2973
- ...params.enhancePrompt(),
2974
- ...params.imageInput()
2948
+ ...params.prompt({ maxLength: 2e3, placeholder: "Describe the genre, mood, instruments, tempo, and production style..." }),
2949
+ ...p.text("lyricsPrompt", {
2950
+ maxLength: 3500,
2951
+ label: "Lyrics",
2952
+ placeholder: "Write lyrics, or describe the lyrical theme. Optional for instrumental or optimizer-generated lyrics."
2953
+ }),
2954
+ ...p.boolean("lyricsOptimizer", false, "Lyrics Optimizer"),
2955
+ ...p.boolean("isInstrumental", false, "Instrumental"),
2956
+ ...p.enum("sampleRate", [16e3, 24e3, 32e3, 44100], 44100, { label: "Sample Rate" }),
2957
+ ...p.enum("bitrate", [32e3, 64e3, 128e3, 256e3], 256e3, { label: "Bitrate" }),
2958
+ ...p.enum("format", ["mp3", "wav", "pcm"], "mp3", { label: "Format" })
2975
2959
  }
2976
2960
  },
2977
2961
  {
2978
- ...base,
2979
- id: "hailuo-2.3-fast",
2980
- name: "Hailuo 2.3 Fast",
2981
- modelId: "hailuo-2.3-fast",
2982
- addedAt: "2026-02-06",
2983
- inputType: "i2v",
2984
- workflow: "minimax/hailuo-2.3-fast/standard/image-to-video",
2985
- buildPayload: buildI2V(true),
2986
- estimatedTime: 173,
2987
- description: "Quick 768p previews with expressive characters for rapid experimentation.",
2988
- features: [feat("Image Input", "input"), feat("Start Frame", "frame"), feat("768p", "resolution"), feat("10 sec", "duration")],
2962
+ id: "minimax-music-v3",
2963
+ name: "MiniMax Music v3",
2964
+ addedAt: "2026-08-14",
2965
+ workflow: "minimax-music/v3",
2966
+ estimatedTime: 40,
2967
+ mode: "audio",
2968
+ inputType: "music",
2969
+ description: "Text-to-music with vocals or instrumentals from a style prompt and optional lyrics, with configurable audio encoding.",
2970
+ features: [feat("Music", "characteristic"), feat("Vocals", "characteristic")],
2989
2971
  paramConfig: {
2990
- ...params.prompt({ maxLength: 2e3 }),
2991
- ...params.enhancePrompt(),
2992
- ...params.duration([6, 10]),
2993
- ...params.imageInput(1, "Start Image", true)
2972
+ ...params.prompt({ maxLength: 2e3, placeholder: "Describe the genre, mood, instruments, tempo, and production style..." }),
2973
+ ...p.text("lyricsPrompt", {
2974
+ maxLength: 3500,
2975
+ label: "Lyrics",
2976
+ placeholder: "Write lyrics; \\n separates lines, [Intro]/[Verse]/[Chorus] tags supported. Optional for instrumental or optimizer-generated lyrics."
2977
+ }),
2978
+ ...p.boolean("lyricsOptimizer", false, "Lyrics Optimizer"),
2979
+ ...p.boolean("isInstrumental", false, "Instrumental"),
2980
+ ...p.enum("sampleRate", [16e3, 24e3, 32e3, 44100], 44100, { label: "Sample Rate" }),
2981
+ ...p.enum("bitrate", [32e3, 64e3, 128e3, 256e3], 256e3, { label: "Bitrate" }),
2982
+ ...p.enum("format", ["mp3", "wav", "pcm"], "mp3", { label: "Format" })
2994
2983
  }
2995
2984
  },
2996
2985
  {
2997
- ...base,
2998
- id: "hailuo-2.3-fast-pro",
2999
- name: "Hailuo 2.3 Fast Pro",
3000
- modelId: "hailuo-2.3-fast-pro",
3001
- addedAt: "2026-02-06",
3002
- inputType: "i2v",
3003
- workflow: "minimax/hailuo-2.3-fast/pro/image-to-video",
3004
- buildPayload: buildI2V(false),
3005
- estimatedTime: 162,
3006
- description: "Fast 1080p output for short, polished clips with varied styles.",
3007
- features: [feat("Image Input", "input"), feat("Start Frame", "frame"), feat("1080p", "resolution"), feat("6 sec", "duration")],
2986
+ // One entry for all three H3 Max modes: the inputs pick it — reference
2987
+ // images, videos or audio generate from references; a start and/or end
2988
+ // frame animates those frames; neither generates from the prompt alone.
2989
+ id: "minimax-h3-max",
2990
+ name: "MiniMax H3 Max",
2991
+ modelId: "fal-ai-h3-max",
2992
+ addedAt: "2026-08-28",
2993
+ workflow: "minimax/h3-max/video-generation",
2994
+ estimatedTime: 5,
2995
+ mode: "video",
2996
+ inputType: "t2v",
2997
+ description: "Top-tier MiniMax H3 Max video from text, a start/end frame, or reference images, videos, and audio \u2014 refer to references in the prompt as Image 1, Video 1, Audio 1, in input order. Up to 15s at 1080p.",
2998
+ features: [
2999
+ feat("Start/End Frame", "frame"),
3000
+ feat("Multi-Image Input", "input"),
3001
+ feat("Video Input", "input"),
3002
+ feat("Audio Input", "input"),
3003
+ feat("1080p", "resolution"),
3004
+ feat("5-15 sec", "duration")
3005
+ ],
3008
3006
  paramConfig: {
3009
- ...params.prompt({ maxLength: 2e3 }),
3010
- ...params.enhancePrompt(),
3011
- ...params.imageInput(1, "Start Image", true)
3012
- }
3007
+ ...params.prompt({ maxLength: H3_MAX_PROMPT_MAX }),
3008
+ ...params.startFrame("Start Frame", false, H3_IMAGE_BOUNDS),
3009
+ ...params.endFrame("End Frame", H3_IMAGE_BOUNDS),
3010
+ // Reference slots, reference-to-video route. Per-file limits are
3011
+ // declared; the ≤15s combined cap per modality and the ≤12 files across
3012
+ // images + videos + audios stay backend-enforced.
3013
+ ...params.imageInput(9, "Reference Images", false, "reference", H3_IMAGE_BOUNDS),
3014
+ ...params.videoInputs(3, "Reference Videos", false, H3_VIDEO_BOUNDS),
3015
+ ...params.audioInputs(3, "Reference Audios", false, H3_AUDIO_BOUNDS),
3016
+ // 1080p is a latent refinement of a native 768p generation.
3017
+ ...params.resolution(["480p", "768p", "1080p"], "768p"),
3018
+ ...params.durationRange(5, 15, 5),
3019
+ // 'adaptive' means "follow the references" — constrained below.
3020
+ ...params.aspectRatio(["adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"], "16:9"),
3021
+ ...p.enum("promptExpansionMode", ["balanced", "quality"], "balanced", { label: "Prompt Expansion" }),
3022
+ // -1 (sentinel) means "pick a random seed"; the builder drops it.
3023
+ ...p.range("seed", -1, 2147483647, -1),
3024
+ ...p.boolean("enableSafetyChecker", true, "Safety Checker")
3025
+ },
3026
+ constraints: h3MaxConstraints
3013
3027
  },
3014
3028
  {
3015
- ...base,
3016
- id: "minimax-h3",
3017
- name: "MiniMax H3",
3018
- modelId: "minimax-h3",
3019
- addedAt: "2026-07-30",
3029
+ // Turbo sibling of minimax-h3-max — same model family, tuned for speed
3030
+ // (faster-than-realtime generation), text and frames only.
3031
+ id: "minimax-h3-max-turbo",
3032
+ name: "MiniMax H3 Max Turbo",
3033
+ modelId: "fal-ai-h3-max-turbo",
3034
+ addedAt: "2026-09-03",
3035
+ workflow: "minimax/h3-max-turbo/text-to-video",
3036
+ editWorkflow: "minimax/h3-max-turbo/image-to-video",
3037
+ estimatedTime: 5,
3038
+ mode: "video",
3020
3039
  inputType: "t2v",
3021
- workflow: "minimax/v2/video-generation",
3022
- buildPayload: buildMinimaxH3,
3023
- estimatedTime: 300,
3024
- description: "MiniMax H3 2K video from text, start/last frame, or image/video/audio references.",
3040
+ description: "Faster-than-realtime MiniMax H3 Max Turbo video from text or a start/end frame, with prompt expansion. Up to 15s at 1080p.",
3025
3041
  features: [
3026
- feat("Start Frame", "frame"),
3027
- feat("End Frame", "frame"),
3028
- feat("Reference Video", "input"),
3029
- feat("2K", "resolution"),
3030
- feat("15 sec", "duration")
3042
+ feat("Fast", "characteristic"),
3043
+ feat("Image Input", "input"),
3044
+ feat("Start/End Frame", "frame"),
3045
+ feat("1080p", "resolution"),
3046
+ feat("5-15 sec", "duration")
3031
3047
  ],
3032
3048
  paramConfig: {
3033
- ...params.prompt({ maxLength: 7e3 }),
3034
- ...params.startFrame(),
3035
- ...params.endFrame(),
3036
- ...params.imageInput(9, "Reference Images", false),
3037
- ...params.videoInputs(3, "Reference Videos", false),
3038
- ...params.audioInputs(3, "Reference Audios", false),
3039
- // Backend enum is case-insensitive (['768P','2K','768p','2k']); the
3040
- // uppercase forms are canonical.
3041
- ...params.resolution(["768P", "2K"], "2K"),
3049
+ ...params.prompt({ maxLength: H3_MAX_PROMPT_MAX }),
3050
+ ...params.startFrame("Start Frame", false, H3_IMAGE_BOUNDS),
3051
+ ...params.endFrame("End Frame", H3_IMAGE_BOUNDS),
3052
+ ...params.resolution(["480p", "768p", "1080p"], "768p"),
3042
3053
  ...params.durationRange(5, 15, 5),
3043
- ...p.aspectRatio(["adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"], "adaptive")
3054
+ ...params.aspectRatio(["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"], "16:9"),
3055
+ ...p.enum("promptExpansionMode", ["balanced", "quality"], "balanced", { label: "Prompt Expansion" }),
3056
+ // -1 (sentinel) means "pick a random seed"; the builder drops it.
3057
+ ...p.range("seed", -1, 2147483647, -1),
3058
+ ...p.boolean("enableSafetyChecker", true, "Safety Checker")
3044
3059
  },
3045
- constraints: minimaxH3Constraints
3060
+ constraints: [
3061
+ // Either frame routes to image-to-video, which derives the ratio from
3062
+ // the image it was given (no aspect_ratio on that wire). An end frame
3063
+ // alone is valid — fal documents it as end-only keyframe generation.
3064
+ { when: { startFrame: { exists: true } }, then: {
3065
+ aspectRatio: { disabled: true, reason: "Aspect ratio follows the frame image." }
3066
+ } },
3067
+ { when: { endFrame: { exists: true } }, then: {
3068
+ aspectRatio: { disabled: true, reason: "Aspect ratio follows the frame image." }
3069
+ } }
3070
+ ]
3071
+ },
3072
+ {
3073
+ // Camera-controls sibling of minimax-h3-max: animates a single start
3074
+ // frame along scripted camera keyframes. The prompt is optional — blank
3075
+ // freezes the scene and moves only the camera. No aspect ratio on this
3076
+ // wire; the output follows the frame image.
3077
+ id: "minimax-h3-max-camera-controls",
3078
+ name: "MiniMax H3 Max Camera Controls",
3079
+ modelId: "fal-ai-h3-max-camera-controls",
3080
+ addedAt: "2026-09-14",
3081
+ workflow: "minimax/h3-max/camera-controls",
3082
+ estimatedTime: 5,
3083
+ mode: "video",
3084
+ inputType: "i2v",
3085
+ description: "MiniMax H3 Max video from a start frame with scripted camera motion \u2014 2-12 keyframes set the camera angle, height, and distance over time. Leave the prompt blank to freeze the scene and move only the camera. Up to 15s at 1080p.",
3086
+ features: [
3087
+ feat("Camera Controls", "characteristic"),
3088
+ feat("Image Input", "input"),
3089
+ feat("1080p", "resolution"),
3090
+ feat("5-15 sec", "duration")
3091
+ ],
3092
+ paramConfig: {
3093
+ ...params.prompt({
3094
+ maxLength: H3_MAX_PROMPT_MAX,
3095
+ required: false,
3096
+ placeholder: "Optional \u2014 leave blank to freeze the scene and move only the camera..."
3097
+ }),
3098
+ ...params.startFrame("Start Frame", true, H3_IMAGE_BOUNDS),
3099
+ // Native 480p default here (the siblings default to 768p); 1080p is a
3100
+ // latent refinement of a native 768p generation.
3101
+ ...params.resolution(["480p", "768p", "1080p"], "480p"),
3102
+ ...params.durationRange(5, 15, 5),
3103
+ // Ordered camera keyframes: the first pose is held before its time and
3104
+ // the final pose for the remainder. Azimuth keeps signed full turns,
3105
+ // capped at ±32 turns of total travel; distance is in normalized scene
3106
+ // units and must stay above zero (the wire has no upper bound).
3107
+ cameraTrajectory: {
3108
+ label: "Camera Trajectory",
3109
+ descriptor: {
3110
+ kind: "object",
3111
+ array: { min: 2, max: 12 },
3112
+ fields: {
3113
+ time: { kind: "range", min: 0, max: 1, step: 0.01, default: 0 },
3114
+ azimuth: { kind: "range", min: -11520, max: 11520, default: 0 },
3115
+ elevation: { kind: "range", min: -90, max: 90, default: 0 },
3116
+ distance: { kind: "range", min: 0.01, max: 100, default: 1 }
3117
+ }
3118
+ }
3119
+ },
3120
+ ...p.enum("promptExpansionMode", ["balanced", "quality"], "balanced", { label: "Prompt Expansion" }),
3121
+ // -1 (sentinel) means "pick a random seed"; the builder drops it.
3122
+ ...p.range("seed", -1, 2147483647, -1),
3123
+ ...p.boolean("enableSafetyChecker", true, "Safety Checker")
3124
+ }
3125
+ },
3126
+ {
3127
+ // Lip-sync sibling of minimax-h3-max: animates a portrait to a supplied
3128
+ // soundtrack. No prompt and no duration on this wire — the audio drives
3129
+ // both: the vendor requires at least 5s, clips anything past 14.8s, and
3130
+ // the output matches the clipped track. The output follows the image, so
3131
+ // there is no aspect ratio either.
3132
+ id: "minimax-h3-max-lip-sync",
3133
+ name: "MiniMax H3 Max Lip Sync",
3134
+ modelId: "fal-ai-h3-max-lip-sync",
3135
+ addedAt: "2026-09-22",
3136
+ workflow: "minimax/h3-max/lip-sync/image-to-video",
3137
+ estimatedTime: 120,
3138
+ mode: "video",
3139
+ inputType: "i2v",
3140
+ description: "MiniMax H3 Max lip-synced video from a portrait and an audio track \u2014 mouth movements follow the soundtrack, optionally guided by a transcript. 5-14.8s of audio, up to 2K.",
3141
+ features: [
3142
+ feat("Lip Sync", "characteristic"),
3143
+ feat("Image Input", "input"),
3144
+ feat("Audio Input", "audio"),
3145
+ feat("2K", "resolution"),
3146
+ feat("5-15 sec", "duration")
3147
+ ],
3148
+ paramConfig: {
3149
+ ...params.startFrame("Portrait Image", true),
3150
+ ...params.audioInput("Audio Track", true),
3151
+ // Unlike the siblings this one also offers 2K; 1080p and 2K are latent
3152
+ // refinements of a native 768p generation.
3153
+ ...params.resolution(["480p", "768p", "1080p", "2k"], "768p"),
3154
+ // Transcribes the audio to guide the sync; off follows the waveform alone.
3155
+ ...p.boolean("enableTranscription", false, "Transcription"),
3156
+ // -1 (sentinel) means "pick a random seed"; the builder drops it.
3157
+ ...p.range("seed", -1, 2147483647, -1),
3158
+ ...p.boolean("enableSafetyChecker", true, "Safety Checker")
3159
+ }
3046
3160
  }
3047
3161
  ]);
3048
3162
 
3049
- // src/vendors/catalog/wan.ts
3050
- var buildWanT2VPayload = (ctx) => ({
3051
- prompt: ctx.prompt,
3052
- resolution: ctx.resolution ?? "720p",
3053
- duration: ctx.duration ?? 5,
3054
- ...ctx.startFrame ? { image_url: ctx.startFrame } : {},
3055
- ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3056
- ...ctx.aspectRatio ? { aspect_ratio: ctx.aspectRatio } : {}
3057
- });
3058
- var buildWanI2VPayload = (ctx) => ({
3059
- prompt: ctx.prompt,
3060
- image_url: ctx.startFrame,
3061
- resolution: ctx.resolution === "1080p" ? "1080p" : "720p",
3062
- duration: String(ctx.duration ?? 5),
3063
- ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {}
3064
- });
3065
- var buildWanR2VPayload = (ctx) => ({
3066
- prompt: ctx.prompt,
3067
- video_urls: [ctx.videoUrl],
3068
- resolution: ctx.resolution === "1080p" ? "1080p" : "720p",
3069
- duration: String(ctx.duration ?? 5)
3070
- });
3071
- var buildWanImagePayload = (ctx) => ({
3163
+ // src/vendors/catalog/hailuo.ts
3164
+ var buildT2V = (withDuration) => (ctx) => ({
3072
3165
  prompt: ctx.prompt,
3073
- model: "wan-2.6",
3074
- count: ctx.count ?? 1,
3075
- ...ctx.negativePrompt ? { modelOptions: { negative_prompt: ctx.negativePrompt } } : {}
3166
+ ...ctx.enhancePrompt !== void 0 ? { prompt_optimizer: ctx.enhancePrompt } : {},
3167
+ ...withDuration && ctx.duration ? { duration: ctx.duration } : {}
3076
3168
  });
3077
- var buildWan27T2VPayload = (ctx) => ({
3169
+ var buildI2V = (withDuration) => (ctx) => ({
3078
3170
  prompt: ctx.prompt,
3079
- resolution: ctx.resolution ?? "720P",
3080
- ratio: ctx.aspectRatio ?? "16:9",
3081
- duration: ctx.duration ?? 5,
3082
- prompt_extend: ctx.enhancePrompt ?? true,
3083
- ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3084
- ...ctx.audioUrl ? { audio_url: ctx.audioUrl } : {},
3085
- ...ctx.seed != null ? { seed: ctx.seed } : {}
3171
+ image_url: ctx.imageUrls?.[0],
3172
+ ...ctx.enhancePrompt !== void 0 ? { prompt_optimizer: ctx.enhancePrompt } : {},
3173
+ ...withDuration && ctx.duration ? { duration: ctx.duration } : {}
3086
3174
  });
3087
- var buildWan27I2VPayload = (ctx) => {
3088
- const firstFrameUrl = ctx.startFrame ?? ctx.imageUrls?.[0];
3089
- const media = [];
3090
- if (firstFrameUrl) media.push({ type: "first_frame", url: firstFrameUrl });
3091
- if (ctx.endFrame) media.push({ type: "last_frame", url: ctx.endFrame });
3092
- if (ctx.audioUrl) media.push({ type: "driving_audio", url: ctx.audioUrl });
3093
- return {
3094
- media,
3095
- resolution: ctx.resolution ?? "720P",
3096
- duration: ctx.duration ?? 5,
3097
- prompt_extend: ctx.enhancePrompt ?? true,
3098
- ...ctx.prompt ? { prompt: ctx.prompt } : {},
3099
- ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3100
- ...ctx.seed != null ? { seed: ctx.seed } : {}
3101
- };
3102
- };
3103
- var buildWan27R2VPayload = (ctx) => {
3104
- const media = [];
3105
- if (ctx.imageUrls?.length) {
3106
- for (const url of ctx.imageUrls) media.push({ type: "reference_image", url });
3107
- }
3108
- if (ctx.videoUrl) media.push({ type: "reference_video", url: ctx.videoUrl });
3109
- if (media.length > 5) {
3110
- throw new ApiError("Wan 2.7 R2V accepts at most 5 reference items (images + video combined).", {
3111
- status: 400,
3112
- code: "validation_error"
3113
- });
3114
- }
3175
+ var buildMinimaxH3 = (ctx) => {
3176
+ const content = [{ type: "text", text: ctx.prompt }];
3177
+ if (ctx.startFrame) content.push({ type: "image_url", image_url: { url: ctx.startFrame }, role: "first_frame" });
3178
+ if (ctx.endFrame) content.push({ type: "image_url", image_url: { url: ctx.endFrame }, role: "last_frame" });
3179
+ for (const url of ctx.imageUrls ?? []) content.push({ type: "image_url", image_url: { url }, role: "reference_image" });
3180
+ for (const url of ctx.videoUrls ?? []) content.push({ type: "video_url", video_url: { url }, role: "reference_video" });
3181
+ for (const url of ctx.audioUrls ?? []) content.push({ type: "audio_url", audio_url: { url }, role: "reference_audio" });
3115
3182
  return {
3116
- prompt: ctx.prompt,
3117
- media,
3118
- resolution: ctx.resolution ?? "720P",
3119
- ratio: ctx.aspectRatio ?? "16:9",
3183
+ model: "MiniMax-H3",
3184
+ content,
3185
+ resolution: ctx.resolution ?? "2K",
3120
3186
  duration: ctx.duration ?? 5,
3121
- ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3122
- ...ctx.seed != null ? { seed: ctx.seed } : {}
3123
- };
3124
- };
3125
- var buildWan27VideoEditPayload = (ctx) => {
3126
- const media = [];
3127
- if (ctx.videoUrl) media.push({ type: "video", url: ctx.videoUrl });
3128
- if (ctx.imageUrls?.length) {
3129
- for (const url of ctx.imageUrls.slice(0, 4)) media.push({ type: "reference_image", url });
3130
- }
3131
- return {
3132
- media,
3133
- resolution: ctx.resolution ?? "720P",
3134
- ...ctx.prompt ? { prompt: ctx.prompt } : {},
3135
- ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3136
- ...ctx.aspectRatio ? { ratio: ctx.aspectRatio } : {},
3137
- // duration is truncation-only: unset (vendor default 0) keeps the input length.
3138
- ...ctx.duration ? { duration: ctx.duration } : {},
3139
- ...ctx.audioSetting ? { audio_setting: ctx.audioSetting } : {},
3140
- ...ctx.seed != null ? { seed: ctx.seed } : {}
3187
+ ...ctx.aspectRatio ? { ratio: ctx.aspectRatio } : {}
3141
3188
  };
3142
3189
  };
3143
- var WAN27_AR = ["16:9", "9:16", "1:1", "4:3", "3:4"];
3144
- var WAN27_RES = ["720P", "1080P"];
3145
- var WAN_V3_FRAME_REF_REASON = "Start/End frames cannot be combined with reference images, videos, or audios";
3146
- var wanV3Constraints = [
3147
- // any reference input active → disable frame slots
3190
+ var FRAME_REF_EXCLUSIVE2 = "First/last frame and reference inputs cannot be combined.";
3191
+ var LAST_NEEDS_FIRST = "An end frame requires a start frame.";
3192
+ var AUDIO_NEEDS_VISUAL2 = "Reference audio needs a reference image or video.";
3193
+ var minimaxH3Constraints = [
3194
+ // Frame roles ⊥ reference roles (declared both ways so either input disables the other).
3195
+ { when: { startFrame: { exists: true } }, then: {
3196
+ imageUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
3197
+ videoUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
3198
+ audioUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 }
3199
+ } },
3200
+ { when: { endFrame: { exists: true } }, then: {
3201
+ imageUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
3202
+ videoUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
3203
+ audioUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 }
3204
+ } },
3148
3205
  { when: { imageUrls: { exists: true } }, then: {
3149
- startFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3150
- endFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3206
+ startFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
3207
+ endFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 }
3151
3208
  } },
3152
3209
  { when: { videoUrls: { exists: true } }, then: {
3153
- startFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3154
- endFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3210
+ startFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
3211
+ endFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 }
3155
3212
  } },
3156
3213
  { when: { audioUrls: { exists: true } }, then: {
3157
- startFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3158
- endFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3214
+ startFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
3215
+ endFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 }
3159
3216
  } },
3160
- // any frame slot active → disable reference inputs (mirror, blocks inverse order)
3161
- { when: { startFrame: { exists: true } }, then: {
3162
- imageUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3163
- videoUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3164
- audioUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3217
+ // last_frame requires first_frame.
3218
+ { when: { startFrame: { exists: false } }, then: {
3219
+ endFrame: { disabled: true, reason: LAST_NEEDS_FIRST }
3165
3220
  } },
3166
- { when: { endFrame: { exists: true } }, then: {
3167
- imageUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3168
- videoUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3169
- audioUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3221
+ // reference_audio cannot be the only reference input.
3222
+ { when: { imageUrls: { exists: false }, videoUrls: { exists: false } }, then: {
3223
+ audioUrls: { disabled: true, reason: AUDIO_NEEDS_VISUAL2 }
3170
3224
  } }
3171
3225
  ];
3172
- var wanV3Features = [feat("Image Input", "input"), feat("Video Input", "input"), feat("Audio", "audio"), feat("Start/End Frame", "frame"), feat("1080P", "resolution"), feat("Adaptive Ratio", "resolution")];
3173
- var wanV3ParamConfig = {
3174
- // Vendor: 'either prompt or media' — the builder enforces the cross-field rule.
3175
- ...params.prompt({ required: false, maxLength: 5e3 }),
3176
- // Vendor: integer 2-30, or -1 = Smart duration mode (model picks the length).
3177
- duration: {
3178
- label: "Duration (s)",
3179
- descriptor: {
3180
- kind: "enum",
3181
- valueType: "number",
3182
- options: [{ id: -1, label: "Auto" }, { id: 5 }, { id: 10 }, { id: 15 }, { id: 30 }],
3183
- default: 5
3184
- }
3185
- },
3186
- ...params.resolution(["480P", "720P", "1080P"], "1080P"),
3187
- // Vendor default is 'adaptive' (model chooses from intent and input media).
3188
- ...params.aspectRatio(["16:9", "9:16", "1:1", "4:3", "3:4", "adaptive"], "adaptive"),
3189
- ...params.generateAudio(true),
3226
+ var base = {
3227
+ mode: "video"
3228
+ };
3229
+ var { MODELS: MODELS10 } = defineModels("minimax", [
3230
+ {
3231
+ ...base,
3232
+ id: "hailuo-2.3",
3233
+ name: "Hailuo 2.3",
3234
+ modelId: "hailuo-2.3",
3235
+ addedAt: "2026-02-06",
3236
+ inputType: "t2v",
3237
+ workflow: "minimax/hailuo-2.3/standard/text-to-video",
3238
+ editWorkflow: "minimax/hailuo-2.3/standard/image-to-video",
3239
+ buildPayload: buildT2V(true),
3240
+ buildEditPayload: buildI2V(true),
3241
+ estimatedTime: 150,
3242
+ description: "Stylized 768p animation with strong character expression and emotion.",
3243
+ features: [feat("Image Input", "input"), feat("Start Frame", "frame"), feat("768p", "resolution"), feat("10 sec", "duration")],
3244
+ paramConfig: {
3245
+ ...params.prompt({ maxLength: 2e3 }),
3246
+ ...params.enhancePrompt(),
3247
+ ...params.duration([6, 10]),
3248
+ ...params.imageInput()
3249
+ }
3250
+ },
3251
+ {
3252
+ ...base,
3253
+ id: "hailuo-2.3-pro",
3254
+ name: "Hailuo 2.3 Pro",
3255
+ modelId: "hailuo-2.3-pro",
3256
+ addedAt: "2026-02-06",
3257
+ inputType: "t2v",
3258
+ workflow: "minimax/hailuo-2.3/pro/text-to-video",
3259
+ editWorkflow: "minimax/hailuo-2.3/pro/image-to-video",
3260
+ buildPayload: buildT2V(false),
3261
+ buildEditPayload: buildI2V(false),
3262
+ estimatedTime: 165,
3263
+ description: "1080p output focused on detailed scenes and polished short-form content.",
3264
+ features: [feat("Image Input", "input"), feat("Start Frame", "frame"), feat("1080p", "resolution"), feat("6 sec", "duration")],
3265
+ paramConfig: {
3266
+ ...params.prompt({ maxLength: 2e3 }),
3267
+ ...params.enhancePrompt(),
3268
+ ...params.imageInput()
3269
+ }
3270
+ },
3271
+ {
3272
+ ...base,
3273
+ id: "hailuo-2.3-fast",
3274
+ name: "Hailuo 2.3 Fast",
3275
+ modelId: "hailuo-2.3-fast",
3276
+ addedAt: "2026-02-06",
3277
+ inputType: "i2v",
3278
+ workflow: "minimax/hailuo-2.3-fast/standard/image-to-video",
3279
+ buildPayload: buildI2V(true),
3280
+ estimatedTime: 173,
3281
+ description: "Quick 768p previews with expressive characters for rapid experimentation.",
3282
+ features: [feat("Image Input", "input"), feat("Start Frame", "frame"), feat("768p", "resolution"), feat("10 sec", "duration")],
3283
+ paramConfig: {
3284
+ ...params.prompt({ maxLength: 2e3 }),
3285
+ ...params.enhancePrompt(),
3286
+ ...params.duration([6, 10]),
3287
+ ...params.imageInput(1, "Start Image", true)
3288
+ }
3289
+ },
3290
+ {
3291
+ ...base,
3292
+ id: "hailuo-2.3-fast-pro",
3293
+ name: "Hailuo 2.3 Fast Pro",
3294
+ modelId: "hailuo-2.3-fast-pro",
3295
+ addedAt: "2026-02-06",
3296
+ inputType: "i2v",
3297
+ workflow: "minimax/hailuo-2.3-fast/pro/image-to-video",
3298
+ buildPayload: buildI2V(false),
3299
+ estimatedTime: 162,
3300
+ description: "Fast 1080p output for short, polished clips with varied styles.",
3301
+ features: [feat("Image Input", "input"), feat("Start Frame", "frame"), feat("1080p", "resolution"), feat("6 sec", "duration")],
3302
+ paramConfig: {
3303
+ ...params.prompt({ maxLength: 2e3 }),
3304
+ ...params.enhancePrompt(),
3305
+ ...params.imageInput(1, "Start Image", true)
3306
+ }
3307
+ },
3308
+ {
3309
+ ...base,
3310
+ id: "minimax-h3",
3311
+ name: "MiniMax H3",
3312
+ modelId: "minimax-h3",
3313
+ addedAt: "2026-07-30",
3314
+ inputType: "t2v",
3315
+ workflow: "minimax/v2/video-generation",
3316
+ buildPayload: buildMinimaxH3,
3317
+ estimatedTime: 300,
3318
+ description: "MiniMax H3 2K video from text, start/last frame, or image/video/audio references.",
3319
+ features: [
3320
+ feat("Start Frame", "frame"),
3321
+ feat("End Frame", "frame"),
3322
+ feat("Reference Video", "input"),
3323
+ feat("2K", "resolution"),
3324
+ feat("15 sec", "duration")
3325
+ ],
3326
+ paramConfig: {
3327
+ ...params.prompt({ maxLength: 7e3 }),
3328
+ // Same per-file limits as H3 Max (minimax.ts): the vendor prints the
3329
+ // same numbers on this endpoint.
3330
+ ...params.startFrame("Start Frame", false, H3_IMAGE_BOUNDS),
3331
+ ...params.endFrame("End Frame", H3_IMAGE_BOUNDS),
3332
+ ...params.imageInput(9, "Reference Images", false, "reference", H3_IMAGE_BOUNDS),
3333
+ ...params.videoInputs(3, "Reference Videos", false, H3_VIDEO_BOUNDS),
3334
+ ...params.audioInputs(3, "Reference Audios", false, H3_AUDIO_BOUNDS),
3335
+ // Backend enum is case-insensitive (['768P','2K','768p','2k']); the
3336
+ // uppercase forms are canonical.
3337
+ ...params.resolution(["768P", "2K"], "2K"),
3338
+ ...params.durationRange(5, 15, 5),
3339
+ ...p.aspectRatio(["adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"], "adaptive")
3340
+ },
3341
+ constraints: minimaxH3Constraints
3342
+ }
3343
+ ]);
3344
+
3345
+ // src/vendors/catalog/wan.ts
3346
+ var buildWanT2VPayload = (ctx) => ({
3347
+ prompt: ctx.prompt,
3348
+ resolution: ctx.resolution ?? "720p",
3349
+ duration: ctx.duration ?? 5,
3350
+ ...ctx.startFrame ? { image_url: ctx.startFrame } : {},
3351
+ ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3352
+ ...ctx.aspectRatio ? { aspect_ratio: ctx.aspectRatio } : {}
3353
+ });
3354
+ var buildWanI2VPayload = (ctx) => ({
3355
+ prompt: ctx.prompt,
3356
+ image_url: ctx.startFrame,
3357
+ resolution: ctx.resolution === "1080p" ? "1080p" : "720p",
3358
+ duration: String(ctx.duration ?? 5),
3359
+ ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {}
3360
+ });
3361
+ var buildWanR2VPayload = (ctx) => ({
3362
+ prompt: ctx.prompt,
3363
+ video_urls: [ctx.videoUrl],
3364
+ resolution: ctx.resolution === "1080p" ? "1080p" : "720p",
3365
+ duration: String(ctx.duration ?? 5)
3366
+ });
3367
+ var buildWanImagePayload = (ctx) => ({
3368
+ prompt: ctx.prompt,
3369
+ model: "wan-2.6",
3370
+ count: ctx.count ?? 1,
3371
+ ...ctx.negativePrompt ? { modelOptions: { negative_prompt: ctx.negativePrompt } } : {}
3372
+ });
3373
+ var buildWan27T2VPayload = (ctx) => ({
3374
+ prompt: ctx.prompt,
3375
+ resolution: ctx.resolution ?? "720P",
3376
+ ratio: ctx.aspectRatio ?? "16:9",
3377
+ duration: ctx.duration ?? 5,
3378
+ prompt_extend: ctx.enhancePrompt ?? true,
3379
+ ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3380
+ ...ctx.audioUrl ? { audio_url: ctx.audioUrl } : {},
3381
+ ...ctx.seed != null ? { seed: ctx.seed } : {}
3382
+ });
3383
+ var buildWan27I2VPayload = (ctx) => {
3384
+ const firstFrameUrl = ctx.startFrame ?? ctx.imageUrls?.[0];
3385
+ const media = [];
3386
+ if (firstFrameUrl) media.push({ type: "first_frame", url: firstFrameUrl });
3387
+ if (ctx.endFrame) media.push({ type: "last_frame", url: ctx.endFrame });
3388
+ if (ctx.audioUrl) media.push({ type: "driving_audio", url: ctx.audioUrl });
3389
+ return {
3390
+ media,
3391
+ resolution: ctx.resolution ?? "720P",
3392
+ duration: ctx.duration ?? 5,
3393
+ prompt_extend: ctx.enhancePrompt ?? true,
3394
+ ...ctx.prompt ? { prompt: ctx.prompt } : {},
3395
+ ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3396
+ ...ctx.seed != null ? { seed: ctx.seed } : {}
3397
+ };
3398
+ };
3399
+ var buildWan27R2VPayload = (ctx) => {
3400
+ const media = [];
3401
+ if (ctx.imageUrls?.length) {
3402
+ for (const url of ctx.imageUrls) media.push({ type: "reference_image", url });
3403
+ }
3404
+ if (ctx.videoUrl) media.push({ type: "reference_video", url: ctx.videoUrl });
3405
+ if (media.length > 5) {
3406
+ throw new ApiError("Wan 2.7 R2V accepts at most 5 reference items (images + video combined).", {
3407
+ status: 400,
3408
+ code: "validation_error"
3409
+ });
3410
+ }
3411
+ return {
3412
+ prompt: ctx.prompt,
3413
+ media,
3414
+ resolution: ctx.resolution ?? "720P",
3415
+ ratio: ctx.aspectRatio ?? "16:9",
3416
+ duration: ctx.duration ?? 5,
3417
+ ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3418
+ ...ctx.seed != null ? { seed: ctx.seed } : {}
3419
+ };
3420
+ };
3421
+ var buildWan27VideoEditPayload = (ctx) => {
3422
+ const media = [];
3423
+ if (ctx.videoUrl) media.push({ type: "video", url: ctx.videoUrl });
3424
+ if (ctx.imageUrls?.length) {
3425
+ for (const url of ctx.imageUrls.slice(0, 4)) media.push({ type: "reference_image", url });
3426
+ }
3427
+ return {
3428
+ media,
3429
+ resolution: ctx.resolution ?? "720P",
3430
+ ...ctx.prompt ? { prompt: ctx.prompt } : {},
3431
+ ...ctx.negativePrompt ? { negative_prompt: ctx.negativePrompt } : {},
3432
+ ...ctx.aspectRatio ? { ratio: ctx.aspectRatio } : {},
3433
+ // duration is truncation-only: unset (vendor default 0) keeps the input length.
3434
+ ...ctx.duration ? { duration: ctx.duration } : {},
3435
+ ...ctx.audioSetting ? { audio_setting: ctx.audioSetting } : {},
3436
+ ...ctx.seed != null ? { seed: ctx.seed } : {}
3437
+ };
3438
+ };
3439
+ var WAN27_AR = ["16:9", "9:16", "1:1", "4:3", "3:4"];
3440
+ var WAN27_RES = ["720P", "1080P"];
3441
+ var WAN_V3_FRAME_REF_REASON = "Start/End frames cannot be combined with reference images, videos, or audios";
3442
+ var wanV3Constraints = [
3443
+ // any reference input active → disable frame slots
3444
+ { when: { imageUrls: { exists: true } }, then: {
3445
+ startFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3446
+ endFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3447
+ } },
3448
+ { when: { videoUrls: { exists: true } }, then: {
3449
+ startFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3450
+ endFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3451
+ } },
3452
+ { when: { audioUrls: { exists: true } }, then: {
3453
+ startFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3454
+ endFrame: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3455
+ } },
3456
+ // any frame slot active → disable reference inputs (mirror, blocks inverse order)
3457
+ { when: { startFrame: { exists: true } }, then: {
3458
+ imageUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3459
+ videoUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3460
+ audioUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3461
+ } },
3462
+ { when: { endFrame: { exists: true } }, then: {
3463
+ imageUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3464
+ videoUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON },
3465
+ audioUrls: { disabled: true, reason: WAN_V3_FRAME_REF_REASON }
3466
+ } }
3467
+ ];
3468
+ var wanV3Features = [feat("Image Input", "input"), feat("Video Input", "input"), feat("Audio", "audio"), feat("Start/End Frame", "frame"), feat("1080P", "resolution"), feat("Adaptive Ratio", "resolution")];
3469
+ var wanV3ParamConfig = {
3470
+ // Vendor: 'either prompt or media' — the builder enforces the cross-field rule.
3471
+ ...params.prompt({ required: false, maxLength: 5e3 }),
3472
+ // Vendor: integer 2-30, or -1 = Smart duration mode (model picks the length).
3473
+ duration: {
3474
+ label: "Duration (s)",
3475
+ descriptor: {
3476
+ kind: "enum",
3477
+ valueType: "number",
3478
+ options: [{ id: -1, label: "Auto" }, { id: 5 }, { id: 10 }, { id: 15 }, { id: 30 }],
3479
+ default: 5
3480
+ }
3481
+ },
3482
+ ...params.resolution(["480P", "720P", "1080P"], "1080P"),
3483
+ // Vendor default is 'adaptive' (model chooses from intent and input media).
3484
+ ...params.aspectRatio(["16:9", "9:16", "1:1", "4:3", "3:4", "adaptive"], "adaptive"),
3485
+ ...params.generateAudio(true),
3190
3486
  ...params.startFrame(),
3191
3487
  ...params.endFrame(),
3192
3488
  ...params.imageInput(10, "Reference Images"),
@@ -3197,7 +3493,7 @@ var wanV3ParamConfig = {
3197
3493
  // No default: a materialized seed would pin every generation to one value.
3198
3494
  ...params.seed()
3199
3495
  };
3200
- var { MODELS: MODELS10 } = defineModels("wan", [
3496
+ var { MODELS: MODELS11 } = defineModels("wan", [
3201
3497
  // ── Video ─────────────────────────────────────────
3202
3498
  {
3203
3499
  id: "wan-2.6-t2v",
@@ -3448,7 +3744,7 @@ var makeWanV3VideoPayload = (model) => (input) => {
3448
3744
  ...input.seed != null ? { seed: input.seed } : {}
3449
3745
  };
3450
3746
  };
3451
- registerPayloads(MODELS10, {
3747
+ registerPayloads(MODELS11, {
3452
3748
  "wan-3.0-video": makeWanV3VideoPayload("wan3.0-video"),
3453
3749
  "wan-3.0-video-prime": makeWanV3VideoPayload("wan3.0-video-prime")
3454
3750
  });
@@ -3597,7 +3893,7 @@ var ray32EditConstraints = [
3597
3893
  exrExport: { disabled: true, reason: HDR_540P }
3598
3894
  } }
3599
3895
  ];
3600
- var { MODELS: MODELS11 } = defineModels("luma", [
3896
+ var { MODELS: MODELS12 } = defineModels("luma", [
3601
3897
  {
3602
3898
  id: "luma-ray-2",
3603
3899
  name: "Luma Ray 2",
@@ -3807,7 +4103,7 @@ var buildRay32ReframePayload = (input) => ({
3807
4103
  resolution: input.resolution ?? "720p"
3808
4104
  }
3809
4105
  });
3810
- registerPayloads(MODELS11, {
4106
+ registerPayloads(MODELS12, {
3811
4107
  "luma-ray-3.2": buildRay32VideoPayload,
3812
4108
  "luma-ray-3.2-edit": buildRay32EditPayload,
3813
4109
  "luma-ray-3.2-reframe-video": buildRay32ReframePayload
@@ -4196,7 +4492,7 @@ var seedance25DraftTaskParam = {
4196
4492
  }
4197
4493
  }
4198
4494
  };
4199
- var { MODELS: MODELS12 } = defineModels("seedance", [
4495
+ var { MODELS: MODELS13 } = defineModels("seedance", [
4200
4496
  {
4201
4497
  id: "seedance-2.5",
4202
4498
  name: "Seedance 2.5",
@@ -4781,7 +5077,7 @@ var seedreamV2Params = {
4781
5077
  ...params.imageInput(2, "Source Images"),
4782
5078
  ...params.negativePrompt()
4783
5079
  };
4784
- var { MODELS: MODELS13 } = defineModels("seedream", [
5080
+ var { MODELS: MODELS14 } = defineModels("seedream", [
4785
5081
  {
4786
5082
  id: "seedream-5.0-pro",
4787
5083
  name: "Seedream 5.0 Pro",
@@ -4925,7 +5221,7 @@ var refMutexConstraints = [
4925
5221
  imageUrls: { disabled: true, reason: REF_MUTEX_REASON }
4926
5222
  } }
4927
5223
  ];
4928
- var { MODELS: MODELS14 } = defineModels("seedaudio", [
5224
+ var { MODELS: MODELS15 } = defineModels("seedaudio", [
4929
5225
  {
4930
5226
  id: "seed-audio-1.0-multilingual",
4931
5227
  name: "Seed Audio Multilingual",
@@ -4991,7 +5287,7 @@ var buildSeedAudioPayload = (model) => (input) => {
4991
5287
  ...references ? { references } : {}
4992
5288
  };
4993
5289
  };
4994
- registerPayloads(MODELS14, {
5290
+ registerPayloads(MODELS15, {
4995
5291
  "seed-audio-1.0": buildSeedAudioPayload("seed-audio-1.0"),
4996
5292
  "seed-audio-1.0-multilingual": buildSeedAudioPayload("seed-audio-1.0-multilingual")
4997
5293
  });
@@ -5007,7 +5303,7 @@ var buildRevePayload = (ctx) => {
5007
5303
  ...hasImages ? { image_url: ctx.imageUrls[0] } : {}
5008
5304
  };
5009
5305
  };
5010
- var { MODELS: MODELS15 } = defineModels("reve", [
5306
+ var { MODELS: MODELS16 } = defineModels("reve", [
5011
5307
  {
5012
5308
  id: "reve",
5013
5309
  name: "Reve",
@@ -5103,7 +5399,7 @@ var GROK_DURATIONS = [3, 5, 6, 8, 10, 12, 15];
5103
5399
  var GROK_VIDEO_RESOLUTIONS = ["480p", "720p"];
5104
5400
  var GROK_VIDEO_RESOLUTIONS_15 = ["480p", "720p", "1080p"];
5105
5401
  var GROK_IMAGE_RESOLUTIONS = ["1k", "2k"];
5106
- var { MODELS: MODELS16 } = defineModels("grok", [
5402
+ var { MODELS: MODELS17 } = defineModels("grok", [
5107
5403
  // ── Video ─────────────────────────────────────────
5108
5404
  {
5109
5405
  id: "grok-imagine-video",
@@ -5292,10 +5588,10 @@ var buildGrokImage2EditPayload = (input) => {
5292
5588
  ...imagePart2
5293
5589
  };
5294
5590
  };
5295
- registerPayloads(MODELS16, {
5591
+ registerPayloads(MODELS17, {
5296
5592
  "grok-imagine-image-2.0": buildGrokImage2Payload
5297
5593
  });
5298
- registerEditPayloads(MODELS16, {
5594
+ registerEditPayloads(MODELS17, {
5299
5595
  "grok-imagine-image-2.0": buildGrokImage2EditPayload
5300
5596
  });
5301
5597
 
@@ -5333,7 +5629,7 @@ var buildPikaFramesPayload = (ctx) => ({
5333
5629
  var PIKA_DURATIONS = [5, 10];
5334
5630
  var PIKA_AR = ["16:9", "9:16", "1:1", "4:5", "5:4", "3:2", "2:3"];
5335
5631
  var PIKA_RESOLUTIONS = ["720p", "1080p"];
5336
- var { MODELS: MODELS17 } = defineModels("pika", [
5632
+ var { MODELS: MODELS18 } = defineModels("pika", [
5337
5633
  {
5338
5634
  id: "pika-2.2",
5339
5635
  name: "Pika",
@@ -5462,7 +5758,7 @@ var veoLiteParamConfig = {
5462
5758
  var veoLiteConstraints = [
5463
5759
  { when: { resolution: { is: "1080p" } }, then: { duration: { allowed: [8], reason: "1080p supports 8s only" } } }
5464
5760
  ];
5465
- var { MODELS: MODELS18 } = defineModels("google", [
5761
+ var { MODELS: MODELS19 } = defineModels("google", [
5466
5762
  {
5467
5763
  id: "veo-3.1",
5468
5764
  name: "Veo 3.1",
@@ -5598,7 +5894,7 @@ var buildRunwayGen3aTurboPayload = (ctx) => ({
5598
5894
  ratio: RUNWAY_GEN3A_RATIO_MAP[ctx.aspectRatio ?? "16:9"] ?? "1280:768",
5599
5895
  duration: ctx.duration ?? 5
5600
5896
  });
5601
- var { MODELS: MODELS19 } = defineModels("runway", [
5897
+ var { MODELS: MODELS20 } = defineModels("runway", [
5602
5898
  {
5603
5899
  id: "runway-avatar-video",
5604
5900
  name: "Runway Avatar",
@@ -5793,7 +6089,7 @@ var flux3VideoConstraints = [
5793
6089
  resolution: { allowed: ["hd"], reason: "Draft mode only supports HD resolution." }
5794
6090
  } }
5795
6091
  ];
5796
- var { MODELS: MODELS20 } = defineModels("flux", [
6092
+ var { MODELS: MODELS21 } = defineModels("flux", [
5797
6093
  {
5798
6094
  ...fluxV2Base,
5799
6095
  id: "flux-2-pro",
@@ -6042,7 +6338,7 @@ var buildFlux3VideoPayload = (input) => {
6042
6338
  ...input.draft ? { draft: input.draft } : {}
6043
6339
  };
6044
6340
  };
6045
- registerPayloads(MODELS20, {
6341
+ registerPayloads(MODELS21, {
6046
6342
  "flux-3-video": buildFlux3VideoPayload
6047
6343
  });
6048
6344
 
@@ -6124,7 +6420,7 @@ var thinkingBudgetParam = {
6124
6420
  descriptor: { kind: "range", min: 128, max: 24576, step: 128, default: 128 }
6125
6421
  }
6126
6422
  };
6127
- var { MODELS: MODELS21 } = defineModels("google", [
6423
+ var { MODELS: MODELS22 } = defineModels("google", [
6128
6424
  // ── Image ─────────────────────────────────────────────────────────
6129
6425
  {
6130
6426
  id: "gemini-3.1-flash-image",
@@ -6322,7 +6618,7 @@ var buildOmniFlash11Payload = (input) => ({
6322
6618
  ...input.videoUrl ? { video: { url: input.videoUrl } } : {},
6323
6619
  ...input.videoUrls?.length ? { referenceVideos: input.videoUrls.map((url) => ({ url })) } : {}
6324
6620
  });
6325
- registerPayloads(MODELS21, {
6621
+ registerPayloads(MODELS22, {
6326
6622
  "gemini-omni-1.1-flash-preview": buildOmniFlash11Payload
6327
6623
  });
6328
6624
 
@@ -6428,7 +6724,7 @@ var gptImageBgConstraints = [
6428
6724
  then: { outputFormat: { allowed: ["png", "webp"], reason: "Transparent background needs PNG or WEBP \u2014 JPEG has no alpha channel." } }
6429
6725
  }
6430
6726
  ];
6431
- var { MODELS: MODELS22 } = defineModels("openai", [
6727
+ var { MODELS: MODELS23 } = defineModels("openai", [
6432
6728
  // ── Image ─────────────────────────────────────────
6433
6729
  {
6434
6730
  id: "gpt-image-2.5-sunburst",
@@ -6575,11 +6871,11 @@ var editFor = (model) => (input) => ({
6575
6871
  ...input.background ? { background: input.background } : {},
6576
6872
  ...input.outputFormat ? { output_format: input.outputFormat } : {}
6577
6873
  });
6578
- registerPayloads(MODELS22, {
6874
+ registerPayloads(MODELS23, {
6579
6875
  "gpt-image-2.5-flare": generateFor("gpt-image-2.5-flare"),
6580
6876
  "gpt-image-2.5-sunburst": generateFor("gpt-image-2.5-sunburst")
6581
6877
  });
6582
- registerEditPayloads(MODELS22, {
6878
+ registerEditPayloads(MODELS23, {
6583
6879
  "gpt-image-2.5-flare": editFor("gpt-image-2.5-flare"),
6584
6880
  "gpt-image-2.5-sunburst": editFor("gpt-image-2.5-sunburst")
6585
6881
  });
@@ -6643,7 +6939,7 @@ var ttsParamConfig = (promptMaxLength, withLanguage) => ({
6643
6939
  ...params.voiceId([], DEFAULT_VOICE_ID, { catalog: VOICE_CATALOG }),
6644
6940
  ...voiceSettingsParams(true)
6645
6941
  });
6646
- var { MODELS: MODELS23 } = defineModels("elevenlabs", [
6942
+ var { MODELS: MODELS24 } = defineModels("elevenlabs", [
6647
6943
  // ── TTS ───────────────────────────────────────────────────────────
6648
6944
  {
6649
6945
  id: "eleven-v3",
@@ -6859,480 +7155,199 @@ var { MODELS: MODELS23 } = defineModels("elevenlabs", [
6859
7155
  label: "Voice ID (a custom voice from your workspace)",
6860
7156
  required: true,
6861
7157
  descriptor: { kind: "text", placeholder: "Premade/catalog voices are rejected by ElevenLabs" }
6862
- },
6863
- ...params.prompt({ minLength: 5, maxLength: 1e3 })
6864
- }
6865
- },
6866
- {
6867
- id: "eleven-voice-design-v3",
6868
- name: "Eleven Voice Design v3",
6869
- addedAt: "2026-03-24",
6870
- modelId: "eleven_ttv_v3",
6871
- workflow: "elevenlabs/v1/voice-design",
6872
- buildPayload: buildElevenLabsVoiceDesignPayload("eleven_ttv_v3"),
6873
- estimatedTime: 15,
6874
- mode: "audio",
6875
- inputType: "tts",
6876
- description: "Design a new voice from a text description using v3 engine.",
6877
- features: [feat("Voice Design", "characteristic"), feat("Preview", "characteristic")],
6878
- paramConfig: { ...params.prompt({ minLength: 20, maxLength: 1e3 }) }
6879
- },
6880
- {
6881
- id: "eleven-voice-design-v2",
6882
- name: "Eleven Voice Design Multilingual v2",
6883
- addedAt: "2026-03-24",
6884
- modelId: "eleven_multilingual_ttv_v2",
6885
- workflow: "elevenlabs/v1/voice-design",
6886
- buildPayload: buildElevenLabsVoiceDesignPayload("eleven_multilingual_ttv_v2"),
6887
- estimatedTime: 15,
6888
- mode: "audio",
6889
- inputType: "tts",
6890
- description: "Design a new voice from a text description with multilingual support.",
6891
- features: [feat("Voice Design", "characteristic"), feat("Multilingual", "characteristic"), feat("Preview", "characteristic")],
6892
- paramConfig: { ...params.prompt({ minLength: 20, maxLength: 1e3 }) }
6893
- },
6894
- {
6895
- id: "eleven-voice-create",
6896
- name: "Eleven Voice Previews",
6897
- // Pricing registers this operation as `eleven-voice-create-previews`, which
6898
- // does not match the model id — without the hint the credits lookup misses.
6899
- modelId: "eleven-voice-create-previews",
6900
- addedAt: "2026-03-24",
6901
- workflow: "elevenlabs/v1/voice-create-previews",
6902
- buildPayload: buildElevenLabsVoicePreviewsPayload,
6903
- estimatedTime: 15,
6904
- mode: "audio",
6905
- inputType: "tts",
6906
- description: "Generate voice previews from a description to audition before committing.",
6907
- features: [feat("Voice Design", "characteristic"), feat("Preview", "characteristic")],
6908
- paramConfig: { ...params.prompt({ minLength: 20, maxLength: 1e3 }) }
6909
- }
6910
- ]);
6911
-
6912
- // src/vendors/catalog/elevenlabs.payloads.ts
6913
- var buildElevenLabsMusicPayload = (input) => ({
6914
- prompt: input.prompt,
6915
- music_length_seconds: input.duration ?? 30,
6916
- model_id: "music_v2",
6917
- force_instrumental: input.isInstrumental ?? false
6918
- });
6919
- var voiceSettings = (input) => {
6920
- const settings = {
6921
- ...input.stability != null ? { stability: input.stability } : {},
6922
- ...input.similarityBoost != null ? { similarity_boost: input.similarityBoost } : {},
6923
- ...input.styleExaggeration != null ? { style: input.styleExaggeration } : {},
6924
- ...input.speed != null ? { speed: input.speed } : {},
6925
- ...input.useSpeakerBoost != null ? { use_speaker_boost: input.useSpeakerBoost } : {}
6926
- };
6927
- return Object.keys(settings).length > 0 ? { voice_settings: settings } : {};
6928
- };
6929
- var buildElevenLabsTTSPayload = (modelId) => (input) => ({
6930
- text: input.prompt,
6931
- voice_id: input.voiceId ?? DEFAULT_VOICE_ID,
6932
- model_id: modelId,
6933
- ...input.language ? { language_code: input.language } : {},
6934
- ...voiceSettings(input)
6935
- });
6936
- var buildElevenLabsSTSPayload = (modelId) => (input) => ({
6937
- audio_url: input.audioUrl,
6938
- voice_id: input.voiceId ?? DEFAULT_VOICE_ID,
6939
- model_id: modelId,
6940
- remove_background_noise: input.removeBackgroundNoise ?? false,
6941
- ...voiceSettings(input)
6942
- });
6943
- var buildElevenLabsDialoguePayload = (input) => ({
6944
- conversation: input.dialogue.map((line) => ({
6945
- voice_id: line.voiceId,
6946
- text: line.text
6947
- })),
6948
- model_id: "eleven_v3",
6949
- ...input.stability != null ? { settings: { stability: input.stability } } : {},
6950
- ...input.language ? { language_code: input.language } : {},
6951
- ...input.seed != null ? { seed: input.seed } : {}
6952
- });
6953
- registerPayloads(MODELS23, {
6954
- "elevenlabs-music-v2": buildElevenLabsMusicPayload,
6955
- "eleven-v3": buildElevenLabsTTSPayload("eleven_v3"),
6956
- "eleven-multilingual-v2": buildElevenLabsTTSPayload("eleven_multilingual_v2"),
6957
- "eleven-sts-v2": buildElevenLabsSTSPayload("eleven_english_sts_v2"),
6958
- "eleven-multilingual-sts-v2": buildElevenLabsSTSPayload("eleven_multilingual_sts_v2"),
6959
- "eleven-text-to-dialogue": buildElevenLabsDialoguePayload
6960
- });
6961
-
6962
- // src/vendors/catalog/heygen.ts
6963
- var buildHeyGenPhotoAvatarPayload = (ctx) => ({
6964
- image_url: ctx.imageUrls?.[0],
6965
- script: ctx.prompt,
6966
- voice_id: ctx.voiceId || void 0,
6967
- ...ctx.resolution ? { resolution: ctx.resolution } : {},
6968
- ...ctx.aspectRatio ? { aspect_ratio: ctx.aspectRatio } : {}
6969
- });
6970
- var buildHeyGenVideoAvatarPayload = (ctx) => {
6971
- const c = ctx;
6972
- return {
6973
- avatar_id: ctx.videoId || void 0,
6974
- script: ctx.prompt,
6975
- voice_id: ctx.voiceId || void 0,
6976
- ...ctx.resolution ? { resolution: ctx.resolution } : {},
6977
- ...ctx.aspectRatio ? { aspect_ratio: ctx.aspectRatio } : {},
6978
- ...c.engine ? { engine: c.engine } : {}
6979
- };
6980
- };
6981
- var engineParam = {
6982
- engine: {
6983
- label: "Engine",
6984
- required: false,
6985
- descriptor: {
6986
- kind: "enum",
6987
- valueType: "string",
6988
- options: [
6989
- { id: "avatar_iv", label: "Avatar IV" },
6990
- { id: "avatar_v", label: "Avatar V" }
6991
- ],
6992
- default: "avatar_iv"
6993
- }
6994
- }
6995
- };
6996
- var dynamicVoiceConfig = {
6997
- ...params.voiceId([], "", {
6998
- required: true,
6999
- catalog: { workflow: "heygen/v1/catalog/voices" }
7000
- })
7001
- };
7002
- var { MODELS: MODELS24 } = defineModels("heygen", [
7003
- // ── Photo Avatar (i2v) ────────────────────────────────────────────
7004
- {
7005
- id: "heygen-talking-photo",
7006
- modelId: "heygen-avatar-iv",
7007
- addedAt: "2026-03-17",
7008
- name: "HeyGen Talking Photo",
7009
- workflow: "heygen/v1/video/generate",
7010
- buildPayload: buildHeyGenPhotoAvatarPayload,
7011
- estimatedTime: 120,
7012
- mode: "video",
7013
- inputType: "i2v",
7014
- description: "Animate any photo into a speaking avatar with natural lip-sync.",
7015
- features: [
7016
- feat("Image Input", "input"),
7017
- feat("Voice Selection", "audio"),
7018
- feat("Lip Sync", "characteristic")
7019
- ],
7020
- paramConfig: {
7021
- ...params.imageInput(1, "Portrait Image", true),
7022
- ...params.resolution(["4k", "1080p", "720p"], "720p"),
7023
- ...params.aspectRatio(["16:9", "9:16", "4:5", "5:4", "1:1", "auto"]),
7024
- ...dynamicVoiceConfig,
7025
- ...params.prompt({ minLength: 20, maxLength: 5e3, placeholder: "Write the script your avatar will speak (at least 20 characters)..." })
7026
- }
7027
- },
7028
- // ── Video Avatar (t2v) ────────────────────────────────────────────
7029
- {
7030
- id: "heygen-video-avatar",
7031
- modelId: "heygen-avatar-iv",
7032
- addedAt: "2026-03-17",
7033
- name: "HeyGen Video Avatar",
7034
- workflow: "heygen/v1/video/generate",
7035
- buildPayload: buildHeyGenVideoAvatarPayload,
7036
- estimatedTime: 90,
7037
- mode: "video",
7038
- inputType: "t2v",
7039
- description: "Generate a speaking avatar video from a stock avatar and text script.",
7040
- features: [
7041
- feat("Avatar Selection", "characteristic"),
7042
- feat("Voice Selection", "audio"),
7043
- feat("Lip Sync", "characteristic")
7044
- ],
7045
- paramConfig: {
7046
- ...params.videoId([], "", {
7047
- required: true,
7048
- catalog: { workflow: "heygen/v1/catalog/avatars" }
7049
- }),
7050
- ...engineParam,
7051
- ...params.resolution(["4k", "1080p", "720p"], "720p"),
7052
- ...params.aspectRatio(["16:9", "9:16", "4:5", "5:4", "1:1", "auto"]),
7053
- ...dynamicVoiceConfig,
7054
- ...params.prompt({ minLength: 20, maxLength: 5e3, placeholder: "Write the script your avatar will speak (at least 20 characters)..." })
7055
- }
7056
- }
7057
- ]);
7058
-
7059
- // src/vendors/catalog/minimax.ts
7060
- var buildMinimaxMusicPayload = (ctx) => ({
7061
- prompt: ctx.prompt,
7062
- ...ctx.lyricsPrompt ? { lyrics: ctx.lyricsPrompt } : {},
7063
- ...ctx.lyricsOptimizer != null ? { lyrics_optimizer: ctx.lyricsOptimizer } : {},
7064
- ...ctx.isInstrumental != null ? { is_instrumental: ctx.isInstrumental } : {},
7065
- audio_setting: {
7066
- sample_rate: ctx.sampleRate ?? 44100,
7067
- bitrate: ctx.bitrate ?? 256e3,
7068
- format: ctx.format ?? "mp3"
7069
- }
7070
- });
7071
- var H3_MAX_PROMPT_MAX = 5e4;
7072
- var FRAME_REF_EXCLUSIVE2 = "References and start/end frames cannot be combined.";
7073
- var AUDIO_NEEDS_VISUAL2 = "Audio cannot be the only reference \u2014 add an image or video.";
7074
- var RATIO_FOLLOWS_FRAME = "Aspect ratio follows the frame image.";
7075
- var ADAPTIVE_NEEDS_REFS = "Adaptive aspect ratio requires reference inputs.";
7076
- var H3_MAX_RATIOS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"];
7077
- var REFERENCE_EXCLUDES_FRAMES = {
7078
- startFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
7079
- endFrame: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 }
7080
- };
7081
- var FRAME_EXCLUDES_REFERENCES = {
7082
- imageUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
7083
- videoUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 },
7084
- audioUrls: { disabled: true, reason: FRAME_REF_EXCLUSIVE2 }
7085
- };
7086
- var h3MaxConstraints = [
7087
- { when: { startFrame: { exists: true } }, then: FRAME_EXCLUDES_REFERENCES },
7088
- { when: { endFrame: { exists: true } }, then: FRAME_EXCLUDES_REFERENCES },
7089
- { when: { imageUrls: { exists: true } }, then: REFERENCE_EXCLUDES_FRAMES },
7090
- { when: { videoUrls: { exists: true } }, then: REFERENCE_EXCLUDES_FRAMES },
7091
- { when: { audioUrls: { exists: true } }, then: REFERENCE_EXCLUDES_FRAMES },
7092
- // With a frame the output follows that image, so the ratio is not a choice.
7093
- // An end frame alone is valid — it generates towards that keyframe.
7094
- { when: { startFrame: { exists: true } }, then: {
7095
- aspectRatio: { disabled: true, reason: RATIO_FOLLOWS_FRAME }
7096
- } },
7097
- { when: { endFrame: { exists: true } }, then: {
7098
- aspectRatio: { disabled: true, reason: RATIO_FOLLOWS_FRAME }
7099
- } },
7100
- // 'adaptive' means "follow the references" — no meaning without them.
7101
- { when: { imageUrls: { exists: false }, videoUrls: { exists: false }, audioUrls: { exists: false } }, then: {
7102
- aspectRatio: { allowed: [...H3_MAX_RATIOS], reason: ADAPTIVE_NEEDS_REFS }
7103
- } },
7104
- // Audio carries no visual signal, so it cannot be the only reference.
7105
- { when: { imageUrls: { exists: false }, videoUrls: { exists: false } }, then: {
7106
- audioUrls: { disabled: true, reason: AUDIO_NEEDS_VISUAL2 }
7107
- } }
7108
- ];
7109
- var { MODELS: MODELS25 } = defineModels("minimax", [
7110
- // minimax-02-hd (minimax-tts) was removed in 6.0: its backend workflow was
7111
- // never deployed anywhere, so no generation ever existed to resolve — nothing
7112
- // to deprecate. Re-add as a fresh entry if MiniMax TTS ever ships.
7113
- {
7114
- id: "minimax-music-v2",
7115
- name: "MiniMax Music v2",
7116
- addedAt: "2026-02-06",
7117
- workflow: "minimax-music/v2",
7118
- buildPayload: buildMinimaxMusicPayload,
7119
- estimatedTime: 39,
7120
- mode: "audio",
7121
- inputType: "music",
7122
- description: "Text-to-music with vocals or instrumentals from a style prompt and lyrics prompt.",
7123
- features: [feat("Music", "characteristic"), feat("Vocals", "characteristic")],
7124
- paramConfig: {
7125
- ...params.prompt({ maxLength: 2e3, placeholder: "Describe the genre, mood, instruments, tempo, and production style..." }),
7126
- ...p.text("lyricsPrompt", {
7127
- maxLength: 3500,
7128
- label: "Lyrics",
7129
- placeholder: "Write lyrics, or describe the lyrical theme. Optional for instrumental or optimizer-generated lyrics."
7130
- }),
7131
- ...p.boolean("lyricsOptimizer", false, "Lyrics Optimizer"),
7132
- ...p.boolean("isInstrumental", false, "Instrumental"),
7133
- ...p.enum("sampleRate", [16e3, 24e3, 32e3, 44100], 44100, { label: "Sample Rate" }),
7134
- ...p.enum("bitrate", [32e3, 64e3, 128e3, 256e3], 256e3, { label: "Bitrate" }),
7135
- ...p.enum("format", ["mp3", "wav", "pcm"], "mp3", { label: "Format" })
7136
- }
7137
- },
7138
- {
7139
- id: "minimax-music-v3",
7140
- name: "MiniMax Music v3",
7141
- addedAt: "2026-08-14",
7142
- workflow: "minimax-music/v3",
7143
- estimatedTime: 40,
7144
- mode: "audio",
7145
- inputType: "music",
7146
- description: "Text-to-music with vocals or instrumentals from a style prompt and optional lyrics, with configurable audio encoding.",
7147
- features: [feat("Music", "characteristic"), feat("Vocals", "characteristic")],
7148
- paramConfig: {
7149
- ...params.prompt({ maxLength: 2e3, placeholder: "Describe the genre, mood, instruments, tempo, and production style..." }),
7150
- ...p.text("lyricsPrompt", {
7151
- maxLength: 3500,
7152
- label: "Lyrics",
7153
- placeholder: "Write lyrics; \\n separates lines, [Intro]/[Verse]/[Chorus] tags supported. Optional for instrumental or optimizer-generated lyrics."
7154
- }),
7155
- ...p.boolean("lyricsOptimizer", false, "Lyrics Optimizer"),
7156
- ...p.boolean("isInstrumental", false, "Instrumental"),
7157
- ...p.enum("sampleRate", [16e3, 24e3, 32e3, 44100], 44100, { label: "Sample Rate" }),
7158
- ...p.enum("bitrate", [32e3, 64e3, 128e3, 256e3], 256e3, { label: "Bitrate" }),
7159
- ...p.enum("format", ["mp3", "wav", "pcm"], "mp3", { label: "Format" })
7160
- }
7161
- },
7162
- {
7163
- // One entry for all three H3 Max modes: the inputs pick it — reference
7164
- // images, videos or audio generate from references; a start and/or end
7165
- // frame animates those frames; neither generates from the prompt alone.
7166
- id: "minimax-h3-max",
7167
- name: "MiniMax H3 Max",
7168
- modelId: "fal-ai-h3-max",
7169
- addedAt: "2026-08-28",
7170
- workflow: "minimax/h3-max/video-generation",
7171
- estimatedTime: 5,
7172
- mode: "video",
7173
- inputType: "t2v",
7174
- description: "Top-tier MiniMax H3 Max video from text, a start/end frame, or reference images, videos, and audio \u2014 refer to references in the prompt as Image 1, Video 1, Audio 1, in input order. Up to 15s at 1080p.",
7175
- features: [
7176
- feat("Start/End Frame", "frame"),
7177
- feat("Multi-Image Input", "input"),
7178
- feat("Video Input", "input"),
7179
- feat("Audio Input", "input"),
7180
- feat("1080p", "resolution"),
7181
- feat("5-15 sec", "duration")
7182
- ],
7183
- paramConfig: {
7184
- ...params.prompt({ maxLength: H3_MAX_PROMPT_MAX }),
7185
- ...params.startFrame(),
7186
- ...params.endFrame(),
7187
- // Reference slots — reference-to-video route. Clips are 2-15s each
7188
- // (≤15s combined per modality) and images + videos + audios must add up
7189
- // to ≤12 files — backend-enforced; only the per-array maxima live here.
7190
- ...params.imageInput(9, "Reference Images"),
7191
- ...params.videoInputs(3, "Reference Videos"),
7192
- ...params.audioInputs(3, "Reference Audios"),
7193
- // 1080p is a latent refinement of a native 768p generation.
7194
- ...params.resolution(["480p", "768p", "1080p"], "768p"),
7195
- ...params.durationRange(5, 15, 5),
7196
- // 'adaptive' means "follow the references" — constrained below.
7197
- ...params.aspectRatio(["adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16"], "16:9"),
7198
- ...p.enum("promptExpansionMode", ["balanced", "quality"], "balanced", { label: "Prompt Expansion" }),
7199
- // -1 (sentinel) means "pick a random seed"; the builder drops it.
7200
- ...p.range("seed", -1, 2147483647, -1),
7201
- ...p.boolean("enableSafetyChecker", true, "Safety Checker")
7202
- },
7203
- constraints: h3MaxConstraints
7204
- },
7205
- {
7206
- // Turbo sibling of minimax-h3-max — same model family, tuned for speed
7207
- // (faster-than-realtime generation), text and frames only.
7208
- id: "minimax-h3-max-turbo",
7209
- name: "MiniMax H3 Max Turbo",
7210
- modelId: "fal-ai-h3-max-turbo",
7211
- addedAt: "2026-09-03",
7212
- workflow: "minimax/h3-max-turbo/text-to-video",
7213
- editWorkflow: "minimax/h3-max-turbo/image-to-video",
7214
- estimatedTime: 5,
7215
- mode: "video",
7216
- inputType: "t2v",
7217
- description: "Faster-than-realtime MiniMax H3 Max Turbo video from text or a start/end frame, with prompt expansion. Up to 15s at 1080p.",
7218
- features: [
7219
- feat("Fast", "characteristic"),
7220
- feat("Image Input", "input"),
7221
- feat("Start/End Frame", "frame"),
7222
- feat("1080p", "resolution"),
7223
- feat("5-15 sec", "duration")
7224
- ],
7225
- paramConfig: {
7226
- ...params.prompt({ maxLength: H3_MAX_PROMPT_MAX }),
7227
- ...params.startFrame(),
7228
- ...params.endFrame(),
7229
- ...params.resolution(["480p", "768p", "1080p"], "768p"),
7230
- ...params.durationRange(5, 15, 5),
7231
- ...params.aspectRatio(["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"], "16:9"),
7232
- ...p.enum("promptExpansionMode", ["balanced", "quality"], "balanced", { label: "Prompt Expansion" }),
7233
- // -1 (sentinel) means "pick a random seed"; the builder drops it.
7234
- ...p.range("seed", -1, 2147483647, -1),
7235
- ...p.boolean("enableSafetyChecker", true, "Safety Checker")
7236
- },
7237
- constraints: [
7238
- // Either frame routes to image-to-video, which derives the ratio from
7239
- // the image it was given (no aspect_ratio on that wire). An end frame
7240
- // alone is valid — fal documents it as end-only keyframe generation.
7241
- { when: { startFrame: { exists: true } }, then: {
7242
- aspectRatio: { disabled: true, reason: "Aspect ratio follows the frame image." }
7243
- } },
7244
- { when: { endFrame: { exists: true } }, then: {
7245
- aspectRatio: { disabled: true, reason: "Aspect ratio follows the frame image." }
7246
- } }
7247
- ]
7158
+ },
7159
+ ...params.prompt({ minLength: 5, maxLength: 1e3 })
7160
+ }
7248
7161
  },
7249
7162
  {
7250
- // Camera-controls sibling of minimax-h3-max: animates a single start
7251
- // frame along scripted camera keyframes. The prompt is optional — blank
7252
- // freezes the scene and moves only the camera. No aspect ratio on this
7253
- // wire; the output follows the frame image.
7254
- id: "minimax-h3-max-camera-controls",
7255
- name: "MiniMax H3 Max Camera Controls",
7256
- modelId: "fal-ai-h3-max-camera-controls",
7257
- addedAt: "2026-09-14",
7258
- workflow: "minimax/h3-max/camera-controls",
7259
- estimatedTime: 5,
7163
+ id: "eleven-voice-design-v3",
7164
+ name: "Eleven Voice Design v3",
7165
+ addedAt: "2026-03-24",
7166
+ modelId: "eleven_ttv_v3",
7167
+ workflow: "elevenlabs/v1/voice-design",
7168
+ buildPayload: buildElevenLabsVoiceDesignPayload("eleven_ttv_v3"),
7169
+ estimatedTime: 15,
7170
+ mode: "audio",
7171
+ inputType: "tts",
7172
+ description: "Design a new voice from a text description using v3 engine.",
7173
+ features: [feat("Voice Design", "characteristic"), feat("Preview", "characteristic")],
7174
+ paramConfig: { ...params.prompt({ minLength: 20, maxLength: 1e3 }) }
7175
+ },
7176
+ {
7177
+ id: "eleven-voice-design-v2",
7178
+ name: "Eleven Voice Design Multilingual v2",
7179
+ addedAt: "2026-03-24",
7180
+ modelId: "eleven_multilingual_ttv_v2",
7181
+ workflow: "elevenlabs/v1/voice-design",
7182
+ buildPayload: buildElevenLabsVoiceDesignPayload("eleven_multilingual_ttv_v2"),
7183
+ estimatedTime: 15,
7184
+ mode: "audio",
7185
+ inputType: "tts",
7186
+ description: "Design a new voice from a text description with multilingual support.",
7187
+ features: [feat("Voice Design", "characteristic"), feat("Multilingual", "characteristic"), feat("Preview", "characteristic")],
7188
+ paramConfig: { ...params.prompt({ minLength: 20, maxLength: 1e3 }) }
7189
+ },
7190
+ {
7191
+ id: "eleven-voice-create",
7192
+ name: "Eleven Voice Previews",
7193
+ // Pricing registers this operation as `eleven-voice-create-previews`, which
7194
+ // does not match the model id — without the hint the credits lookup misses.
7195
+ modelId: "eleven-voice-create-previews",
7196
+ addedAt: "2026-03-24",
7197
+ workflow: "elevenlabs/v1/voice-create-previews",
7198
+ buildPayload: buildElevenLabsVoicePreviewsPayload,
7199
+ estimatedTime: 15,
7200
+ mode: "audio",
7201
+ inputType: "tts",
7202
+ description: "Generate voice previews from a description to audition before committing.",
7203
+ features: [feat("Voice Design", "characteristic"), feat("Preview", "characteristic")],
7204
+ paramConfig: { ...params.prompt({ minLength: 20, maxLength: 1e3 }) }
7205
+ }
7206
+ ]);
7207
+
7208
+ // src/vendors/catalog/elevenlabs.payloads.ts
7209
+ var buildElevenLabsMusicPayload = (input) => ({
7210
+ prompt: input.prompt,
7211
+ music_length_seconds: input.duration ?? 30,
7212
+ model_id: "music_v2",
7213
+ force_instrumental: input.isInstrumental ?? false
7214
+ });
7215
+ var voiceSettings = (input) => {
7216
+ const settings = {
7217
+ ...input.stability != null ? { stability: input.stability } : {},
7218
+ ...input.similarityBoost != null ? { similarity_boost: input.similarityBoost } : {},
7219
+ ...input.styleExaggeration != null ? { style: input.styleExaggeration } : {},
7220
+ ...input.speed != null ? { speed: input.speed } : {},
7221
+ ...input.useSpeakerBoost != null ? { use_speaker_boost: input.useSpeakerBoost } : {}
7222
+ };
7223
+ return Object.keys(settings).length > 0 ? { voice_settings: settings } : {};
7224
+ };
7225
+ var buildElevenLabsTTSPayload = (modelId) => (input) => ({
7226
+ text: input.prompt,
7227
+ voice_id: input.voiceId ?? DEFAULT_VOICE_ID,
7228
+ model_id: modelId,
7229
+ ...input.language ? { language_code: input.language } : {},
7230
+ ...voiceSettings(input)
7231
+ });
7232
+ var buildElevenLabsSTSPayload = (modelId) => (input) => ({
7233
+ audio_url: input.audioUrl,
7234
+ voice_id: input.voiceId ?? DEFAULT_VOICE_ID,
7235
+ model_id: modelId,
7236
+ remove_background_noise: input.removeBackgroundNoise ?? false,
7237
+ ...voiceSettings(input)
7238
+ });
7239
+ var buildElevenLabsDialoguePayload = (input) => ({
7240
+ conversation: input.dialogue.map((line) => ({
7241
+ voice_id: line.voiceId,
7242
+ text: line.text
7243
+ })),
7244
+ model_id: "eleven_v3",
7245
+ ...input.stability != null ? { settings: { stability: input.stability } } : {},
7246
+ ...input.language ? { language_code: input.language } : {},
7247
+ ...input.seed != null ? { seed: input.seed } : {}
7248
+ });
7249
+ registerPayloads(MODELS24, {
7250
+ "elevenlabs-music-v2": buildElevenLabsMusicPayload,
7251
+ "eleven-v3": buildElevenLabsTTSPayload("eleven_v3"),
7252
+ "eleven-multilingual-v2": buildElevenLabsTTSPayload("eleven_multilingual_v2"),
7253
+ "eleven-sts-v2": buildElevenLabsSTSPayload("eleven_english_sts_v2"),
7254
+ "eleven-multilingual-sts-v2": buildElevenLabsSTSPayload("eleven_multilingual_sts_v2"),
7255
+ "eleven-text-to-dialogue": buildElevenLabsDialoguePayload
7256
+ });
7257
+
7258
+ // src/vendors/catalog/heygen.ts
7259
+ var buildHeyGenPhotoAvatarPayload = (ctx) => ({
7260
+ image_url: ctx.imageUrls?.[0],
7261
+ script: ctx.prompt,
7262
+ voice_id: ctx.voiceId || void 0,
7263
+ ...ctx.resolution ? { resolution: ctx.resolution } : {},
7264
+ ...ctx.aspectRatio ? { aspect_ratio: ctx.aspectRatio } : {}
7265
+ });
7266
+ var buildHeyGenVideoAvatarPayload = (ctx) => {
7267
+ const c = ctx;
7268
+ return {
7269
+ avatar_id: ctx.videoId || void 0,
7270
+ script: ctx.prompt,
7271
+ voice_id: ctx.voiceId || void 0,
7272
+ ...ctx.resolution ? { resolution: ctx.resolution } : {},
7273
+ ...ctx.aspectRatio ? { aspect_ratio: ctx.aspectRatio } : {},
7274
+ ...c.engine ? { engine: c.engine } : {}
7275
+ };
7276
+ };
7277
+ var engineParam = {
7278
+ engine: {
7279
+ label: "Engine",
7280
+ required: false,
7281
+ descriptor: {
7282
+ kind: "enum",
7283
+ valueType: "string",
7284
+ options: [
7285
+ { id: "avatar_iv", label: "Avatar IV" },
7286
+ { id: "avatar_v", label: "Avatar V" }
7287
+ ],
7288
+ default: "avatar_iv"
7289
+ }
7290
+ }
7291
+ };
7292
+ var dynamicVoiceConfig = {
7293
+ ...params.voiceId([], "", {
7294
+ required: true,
7295
+ catalog: { workflow: "heygen/v1/catalog/voices" }
7296
+ })
7297
+ };
7298
+ var { MODELS: MODELS25 } = defineModels("heygen", [
7299
+ // ── Photo Avatar (i2v) ────────────────────────────────────────────
7300
+ {
7301
+ id: "heygen-talking-photo",
7302
+ modelId: "heygen-avatar-iv",
7303
+ addedAt: "2026-03-17",
7304
+ name: "HeyGen Talking Photo",
7305
+ workflow: "heygen/v1/video/generate",
7306
+ buildPayload: buildHeyGenPhotoAvatarPayload,
7307
+ estimatedTime: 120,
7260
7308
  mode: "video",
7261
7309
  inputType: "i2v",
7262
- description: "MiniMax H3 Max video from a start frame with scripted camera motion \u2014 2-12 keyframes set the camera angle, height, and distance over time. Leave the prompt blank to freeze the scene and move only the camera. Up to 15s at 1080p.",
7310
+ description: "Animate any photo into a speaking avatar with natural lip-sync.",
7263
7311
  features: [
7264
- feat("Camera Controls", "characteristic"),
7265
7312
  feat("Image Input", "input"),
7266
- feat("1080p", "resolution"),
7267
- feat("5-15 sec", "duration")
7313
+ feat("Voice Selection", "audio"),
7314
+ feat("Lip Sync", "characteristic")
7268
7315
  ],
7269
7316
  paramConfig: {
7270
- ...params.prompt({
7271
- maxLength: H3_MAX_PROMPT_MAX,
7272
- required: false,
7273
- placeholder: "Optional \u2014 leave blank to freeze the scene and move only the camera..."
7274
- }),
7275
- ...params.startFrame("Start Frame", true),
7276
- // Native 480p default here (the siblings default to 768p); 1080p is a
7277
- // latent refinement of a native 768p generation.
7278
- ...params.resolution(["480p", "768p", "1080p"], "480p"),
7279
- ...params.durationRange(5, 15, 5),
7280
- // Ordered camera keyframes: the first pose is held before its time and
7281
- // the final pose for the remainder. Azimuth keeps signed full turns,
7282
- // capped at ±32 turns of total travel; distance is in normalized scene
7283
- // units and must stay above zero (the wire has no upper bound).
7284
- cameraTrajectory: {
7285
- label: "Camera Trajectory",
7286
- descriptor: {
7287
- kind: "object",
7288
- array: { min: 2, max: 12 },
7289
- fields: {
7290
- time: { kind: "range", min: 0, max: 1, step: 0.01, default: 0 },
7291
- azimuth: { kind: "range", min: -11520, max: 11520, default: 0 },
7292
- elevation: { kind: "range", min: -90, max: 90, default: 0 },
7293
- distance: { kind: "range", min: 0.01, max: 100, default: 1 }
7294
- }
7295
- }
7296
- },
7297
- ...p.enum("promptExpansionMode", ["balanced", "quality"], "balanced", { label: "Prompt Expansion" }),
7298
- // -1 (sentinel) means "pick a random seed"; the builder drops it.
7299
- ...p.range("seed", -1, 2147483647, -1),
7300
- ...p.boolean("enableSafetyChecker", true, "Safety Checker")
7317
+ ...params.imageInput(1, "Portrait Image", true),
7318
+ ...params.resolution(["4k", "1080p", "720p"], "720p"),
7319
+ ...params.aspectRatio(["16:9", "9:16", "4:5", "5:4", "1:1", "auto"]),
7320
+ ...dynamicVoiceConfig,
7321
+ ...params.prompt({ minLength: 20, maxLength: 5e3, placeholder: "Write the script your avatar will speak (at least 20 characters)..." })
7301
7322
  }
7302
7323
  },
7324
+ // ── Video Avatar (t2v) ────────────────────────────────────────────
7303
7325
  {
7304
- // Lip-sync sibling of minimax-h3-max: animates a portrait to a supplied
7305
- // soundtrack. No prompt and no duration on this wire — the audio drives
7306
- // both: the vendor requires at least 5s, clips anything past 14.8s, and
7307
- // the output matches the clipped track. The output follows the image, so
7308
- // there is no aspect ratio either.
7309
- id: "minimax-h3-max-lip-sync",
7310
- name: "MiniMax H3 Max Lip Sync",
7311
- modelId: "fal-ai-h3-max-lip-sync",
7312
- addedAt: "2026-09-22",
7313
- workflow: "minimax/h3-max/lip-sync/image-to-video",
7314
- estimatedTime: 120,
7326
+ id: "heygen-video-avatar",
7327
+ modelId: "heygen-avatar-iv",
7328
+ addedAt: "2026-03-17",
7329
+ name: "HeyGen Video Avatar",
7330
+ workflow: "heygen/v1/video/generate",
7331
+ buildPayload: buildHeyGenVideoAvatarPayload,
7332
+ estimatedTime: 90,
7315
7333
  mode: "video",
7316
- inputType: "i2v",
7317
- description: "MiniMax H3 Max lip-synced video from a portrait and an audio track \u2014 mouth movements follow the soundtrack, optionally guided by a transcript. 5-14.8s of audio, up to 2K.",
7334
+ inputType: "t2v",
7335
+ description: "Generate a speaking avatar video from a stock avatar and text script.",
7318
7336
  features: [
7319
- feat("Lip Sync", "characteristic"),
7320
- feat("Image Input", "input"),
7321
- feat("Audio Input", "audio"),
7322
- feat("2K", "resolution"),
7323
- feat("5-15 sec", "duration")
7337
+ feat("Avatar Selection", "characteristic"),
7338
+ feat("Voice Selection", "audio"),
7339
+ feat("Lip Sync", "characteristic")
7324
7340
  ],
7325
7341
  paramConfig: {
7326
- ...params.startFrame("Portrait Image", true),
7327
- ...params.audioInput("Audio Track", true),
7328
- // Unlike the siblings this one also offers 2K; 1080p and 2K are latent
7329
- // refinements of a native 768p generation.
7330
- ...params.resolution(["480p", "768p", "1080p", "2k"], "768p"),
7331
- // Transcribes the audio to guide the sync; off follows the waveform alone.
7332
- ...p.boolean("enableTranscription", false, "Transcription"),
7333
- // -1 (sentinel) means "pick a random seed"; the builder drops it.
7334
- ...p.range("seed", -1, 2147483647, -1),
7335
- ...p.boolean("enableSafetyChecker", true, "Safety Checker")
7342
+ ...params.videoId([], "", {
7343
+ required: true,
7344
+ catalog: { workflow: "heygen/v1/catalog/avatars" }
7345
+ }),
7346
+ ...engineParam,
7347
+ ...params.resolution(["4k", "1080p", "720p"], "720p"),
7348
+ ...params.aspectRatio(["16:9", "9:16", "4:5", "5:4", "1:1", "auto"]),
7349
+ ...dynamicVoiceConfig,
7350
+ ...params.prompt({ minLength: 20, maxLength: 5e3, placeholder: "Write the script your avatar will speak (at least 20 characters)..." })
7336
7351
  }
7337
7352
  }
7338
7353
  ]);
@@ -7417,14 +7432,14 @@ var buildMinimaxH3MaxLipSyncPayload = (input) => ({
7417
7432
  ...input.seed != null && input.seed !== -1 ? { seed: input.seed } : {},
7418
7433
  enable_safety_checker: input.enableSafetyChecker ?? true
7419
7434
  });
7420
- registerPayloads(MODELS25, {
7435
+ registerPayloads(MODELS9, {
7421
7436
  "minimax-music-v3": buildMinimaxMusicV3Payload,
7422
7437
  "minimax-h3-max": buildMinimaxH3MaxPayload,
7423
7438
  "minimax-h3-max-turbo": buildMinimaxH3MaxTurboPayload,
7424
7439
  "minimax-h3-max-camera-controls": buildMinimaxH3MaxCameraControlsPayload,
7425
7440
  "minimax-h3-max-lip-sync": buildMinimaxH3MaxLipSyncPayload
7426
7441
  });
7427
- registerEditPayloads(MODELS25, {
7442
+ registerEditPayloads(MODELS9, {
7428
7443
  "minimax-h3-max-turbo": buildMinimaxH3MaxTurboPayload
7429
7444
  });
7430
7445
 
@@ -9994,7 +10009,6 @@ var ALL_MODELS = [
9994
10009
  ...MODELS6,
9995
10010
  ...MODELS7,
9996
10011
  ...MODELS8,
9997
- ...MODELS9,
9998
10012
  ...MODELS10,
9999
10013
  ...MODELS11,
10000
10014
  ...MODELS12,
@@ -10011,6 +10025,7 @@ var ALL_MODELS = [
10011
10025
  ...MODELS23,
10012
10026
  ...MODELS24,
10013
10027
  ...MODELS25,
10028
+ ...MODELS9,
10014
10029
  ...MODELS26,
10015
10030
  ...MODELS31,
10016
10031
  ...MODELS27,
@@ -11573,6 +11588,8 @@ var MAX_DRIVE_PROMPT_LENGTH = 18e3;
11573
11588
 
11574
11589
  // src/client/drive.ts
11575
11590
  var USER_REACTION_ATTR = "userReaction";
11591
+ var DRAFT_TASK_ATTR = "draft_task";
11592
+ var DRAFT_EXPIRES_AT_ATTR = "draft_expires_at";
11576
11593
  function inferResourceType(mode) {
11577
11594
  if (mode === "video") return "VIDEO";
11578
11595
  if (mode === "audio") return "AUDIO";
@@ -11619,6 +11636,14 @@ function parseAttributes(raw) {
11619
11636
  }
11620
11637
  return map;
11621
11638
  }
11639
+ function parseDraftInfo(attrs) {
11640
+ const draftTask = parseJsonAttr(attrs[DRAFT_TASK_ATTR]);
11641
+ if (!isSeedanceDraftTask(draftTask)) return {};
11642
+ return {
11643
+ draftTask: { id: draftTask.id, video_input: draftTask.video_input, signature: draftTask.signature },
11644
+ ...attrs[DRAFT_EXPIRES_AT_ATTR] ? { draftExpiresAt: attrs[DRAFT_EXPIRES_AT_ATTR] } : {}
11645
+ };
11646
+ }
11622
11647
  function parseReaction(value) {
11623
11648
  return value === "like" || value === "dislike" ? value : void 0;
11624
11649
  }
@@ -11691,7 +11716,8 @@ function toDetailedItem(file) {
11691
11716
  referenceAudioUrl: extras.referenceAudioUrl,
11692
11717
  aspectRatio: extras.aspectRatio,
11693
11718
  resolution: extras.resolution,
11694
- quality: extras.quality
11719
+ quality: extras.quality,
11720
+ ...parseDraftInfo(attrs)
11695
11721
  };
11696
11722
  }
11697
11723
  var LEGACY_TOOL_APP = {
@@ -11736,7 +11762,8 @@ function adaptLegacyGeneration(attrs) {
11736
11762
  appType: app?.appType,
11737
11763
  model: attrs.model || void 0,
11738
11764
  aiSDKPayload,
11739
- userReaction: parseReaction(attrs[USER_REACTION_ATTR])
11765
+ userReaction: parseReaction(attrs[USER_REACTION_ATTR]),
11766
+ ...parseDraftInfo(attrs)
11740
11767
  };
11741
11768
  }
11742
11769
  function parseGeneration(file) {
@@ -11749,7 +11776,8 @@ function parseGeneration(file) {
11749
11776
  appType: attrs.appType === "native" || attrs.appType === "miniapp" ? attrs.appType : void 0,
11750
11777
  model: attrs.model || void 0,
11751
11778
  aiSDKPayload: parseJsonAttr(attrs.aiSDKPayload),
11752
- userReaction: parseReaction(attrs[USER_REACTION_ATTR])
11779
+ userReaction: parseReaction(attrs[USER_REACTION_ATTR]),
11780
+ ...parseDraftInfo(attrs)
11753
11781
  };
11754
11782
  }
11755
11783
  function createDriveClient(f, apiUrl, rootFolderName) {