@shop-os/modelkit 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -375,8 +375,59 @@ var FAL_IMAGE_MODELS = [
375
375
  }
376
376
  ];
377
377
 
378
- // src/catalog/image.ts
378
+ // src/catalog/image-luma.ts
379
379
  var IMAGE_USAGE2 = { unit: "images" };
380
+ var EXPOSURE2 = ["cowork", "space_builder"];
381
+ var LOBE2 = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light";
382
+ var LUMA_LOGO = `${LOBE2}/luma-color.png`;
383
+ var UNI_RATIOS = ["adaptive", "16:9", "3:2", "1:1", "2:3", "9:16"];
384
+ function uniImage(id, label, providerModelId, description) {
385
+ return {
386
+ // https://docs.agents.lumalabs.ai: type "image" without references, else
387
+ // "image_edit" with the first reference as source and up to 8 more as
388
+ // image_ref. The output is always 2048 px on the long side.
389
+ id,
390
+ aliases: [],
391
+ label,
392
+ type: "image",
393
+ adapter: "luma",
394
+ providerModelId,
395
+ lifecycle: "active",
396
+ exposure: EXPOSURE2,
397
+ constraints: {
398
+ aspectRatios: [...UNI_RATIOS],
399
+ sizes: ["2K"],
400
+ references: { images: 9 },
401
+ supports: ["outputFormat"],
402
+ prompt: { referenceSyntax: "numbered", maxChars: 6e3 }
403
+ },
404
+ defaults: { size: "2K", aspectRatio: "adaptive" },
405
+ usage: { ...IMAGE_USAGE2 },
406
+ pricingKind: "image_size",
407
+ display: {
408
+ provider: "luma",
409
+ description,
410
+ logo: LUMA_LOGO
411
+ }
412
+ };
413
+ }
414
+ var LUMA_IMAGE_MODELS = [
415
+ uniImage(
416
+ "uni-1",
417
+ "Uni-1",
418
+ "uni-1",
419
+ "Luma Uni-1 fast image generation and editing; takes a prompt and up to 9 reference images and outputs 2K."
420
+ ),
421
+ uniImage(
422
+ "uni-1-max",
423
+ "Uni-1 Max",
424
+ "uni-1-max",
425
+ "Luma Uni-1 Max higher-quality image generation and editing; takes a prompt and up to 9 reference images and outputs 2K."
426
+ )
427
+ ];
428
+
429
+ // src/catalog/image.ts
430
+ var IMAGE_USAGE3 = { unit: "images" };
380
431
  var GEMINI_RATIOS = [
381
432
  "1:1",
382
433
  "2:3",
@@ -421,7 +472,7 @@ function geminiImage(id, label, aliases, sizes, display, exposure, references, p
421
472
  exposure,
422
473
  constraints,
423
474
  defaults: { size: "1K", aspectRatio: "1:1" },
424
- usage: { ...IMAGE_USAGE2, slug: "gemini" },
475
+ usage: { ...IMAGE_USAGE3, slug: "gemini" },
425
476
  pricingKind: "image_size",
426
477
  display
427
478
  };
@@ -534,7 +585,7 @@ var IMAGE_MODELS = [
534
585
  prompt: GPT_IMAGE_PROMPT
535
586
  },
536
587
  defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
537
- usage: { ...IMAGE_USAGE2, slug: "gpt" },
588
+ usage: { ...IMAGE_USAGE3, slug: "gpt" },
538
589
  pricingKind: "image_size",
539
590
  display: {
540
591
  provider: "openai",
@@ -570,7 +621,7 @@ var IMAGE_MODELS = [
570
621
  prompt: GPT_IMAGE_PROMPT
571
622
  },
572
623
  defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
573
- usage: { ...IMAGE_USAGE2 },
624
+ usage: { ...IMAGE_USAGE3 },
574
625
  pricingKind: "image_size",
575
626
  display: {
576
627
  provider: "openai",
@@ -605,7 +656,7 @@ var IMAGE_MODELS = [
605
656
  prompt: GPT_IMAGE_PROMPT
606
657
  },
607
658
  defaults: { size: "1K", aspectRatio: "adaptive", quality: "auto" },
608
- usage: { ...IMAGE_USAGE2 },
659
+ usage: { ...IMAGE_USAGE3 },
609
660
  pricingKind: "image_size",
610
661
  display: {
611
662
  provider: "openai",
@@ -640,7 +691,7 @@ var IMAGE_MODELS = [
640
691
  supports: ["count", "outputFormat"]
641
692
  },
642
693
  defaults: { size: "1K", aspectRatio: "1:1", outputFormat: "png", count: 1 },
643
- usage: { ...IMAGE_USAGE2, slug: "muse" },
694
+ usage: { ...IMAGE_USAGE3, slug: "muse" },
644
695
  pricingKind: "image_size",
645
696
  display: {
646
697
  provider: "meta",
@@ -667,7 +718,7 @@ var IMAGE_MODELS = [
667
718
  supports: ["quality", "count", "seed"]
668
719
  },
669
720
  defaults: { size: "2K", aspectRatio: "1:1", quality: "medium", count: 1 },
670
- usage: { ...IMAGE_USAGE2, slug: "ideogram" },
721
+ usage: { ...IMAGE_USAGE3, slug: "ideogram" },
671
722
  pricingKind: "image_size",
672
723
  display: {
673
724
  provider: "ideogram",
@@ -692,7 +743,7 @@ var IMAGE_MODELS = [
692
743
  supports: ["seed", "outputFormat"]
693
744
  },
694
745
  defaults: { size: "1K", aspectRatio: "1:1" },
695
- usage: { ...IMAGE_USAGE2 },
746
+ usage: { ...IMAGE_USAGE3 },
696
747
  pricingKind: "image_size",
697
748
  display: {
698
749
  provider: "bytedance",
@@ -730,7 +781,7 @@ var IMAGE_MODELS = [
730
781
  supports: ["seed"]
731
782
  },
732
783
  defaults: { size: "1K", aspectRatio: "1:1" },
733
- usage: { ...IMAGE_USAGE2 },
784
+ usage: { ...IMAGE_USAGE3 },
734
785
  pricingKind: "image_size",
735
786
  display: {
736
787
  provider: "bytedance",
@@ -756,7 +807,7 @@ var IMAGE_MODELS = [
756
807
  prompt: { referenceSyntax: "numbered", maxChars: 32768 }
757
808
  },
758
809
  defaults: { size: "1K", aspectRatio: "1:1" },
759
- usage: { ...IMAGE_USAGE2 },
810
+ usage: { ...IMAGE_USAGE3 },
760
811
  pricingKind: "image_size",
761
812
  display: {
762
813
  provider: "black-forest-labs",
@@ -783,7 +834,7 @@ var IMAGE_MODELS = [
783
834
  supports: []
784
835
  },
785
836
  defaults: { size: "1K", aspectRatio: "1:1" },
786
- usage: { ...IMAGE_USAGE2 },
837
+ usage: { ...IMAGE_USAGE3 },
787
838
  pricingKind: "image_size",
788
839
  display: {
789
840
  provider: "google",
@@ -808,7 +859,7 @@ var IMAGE_MODELS = [
808
859
  supports: []
809
860
  },
810
861
  defaults: { size: "1K", aspectRatio: "1:1" },
811
- usage: { ...IMAGE_USAGE2 },
862
+ usage: { ...IMAGE_USAGE3 },
812
863
  pricingKind: "image_size",
813
864
  display: {
814
865
  provider: "google",
@@ -894,7 +945,8 @@ var IMAGE_MODELS = [
894
945
  description: "Alibaba Qwen Image Edit Plus with Lightning and a skin-retouching LoRA; takes one or more reference images plus an edit prompt and outputs webp, jpg, or png."
895
946
  }
896
947
  },
897
- ...FAL_IMAGE_MODELS
948
+ ...FAL_IMAGE_MODELS,
949
+ ...LUMA_IMAGE_MODELS
898
950
  ];
899
951
 
900
952
  // src/catalog/audio-formats.ts
@@ -1090,7 +1142,7 @@ var VEED_SUBTITLE_LANGUAGES = [
1090
1142
  ];
1091
1143
 
1092
1144
  // src/catalog/canvas-tools.ts
1093
- var EXPOSURE2 = ["cowork", "space_builder"];
1145
+ var EXPOSURE3 = ["cowork", "space_builder"];
1094
1146
  var META_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/characters/1787909065794-k9a7zz.png";
1095
1147
  var VEED_SUBTITLE_PRESETS = [
1096
1148
  "simple",
@@ -1132,7 +1184,7 @@ var SAM_3_IMAGE = {
1132
1184
  adapter: "fal",
1133
1185
  providerModelId: "fal-ai/sam-3/image",
1134
1186
  lifecycle: "active",
1135
- exposure: EXPOSURE2,
1187
+ exposure: EXPOSURE3,
1136
1188
  constraints: {
1137
1189
  references: { images: 1 },
1138
1190
  requires: ["references"],
@@ -1155,7 +1207,7 @@ var VEED_SUBTITLES = {
1155
1207
  adapter: "fal",
1156
1208
  providerModelId: "veed/subtitles",
1157
1209
  lifecycle: "active",
1158
- exposure: EXPOSURE2,
1210
+ exposure: EXPOSURE3,
1159
1211
  constraints: {
1160
1212
  supports: [],
1161
1213
  // fal: up to 2 hours at 1080p or below, 1 hour above.
@@ -3260,7 +3312,7 @@ var GOOGLE_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/192485d2-a
3260
3312
  var MINIMAX_LOGO = "https://d31vvfpk2ppdyq.cloudfront.net/assets/image/f4c2e906-28b7-4a4a-b491-df8fef7808ed.png";
3261
3313
  var SYNC_LOGO = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light/sync.png";
3262
3314
  var PER_CALL = { unit: "images" };
3263
- var EXPOSURE3 = ["cowork", "space_builder"];
3315
+ var EXPOSURE4 = ["cowork", "space_builder"];
3264
3316
  var LIPSYNC_AUDIO = { audioSeconds: { max: 30 } };
3265
3317
  function elevenLabsTts(tts) {
3266
3318
  return {
@@ -3271,7 +3323,7 @@ function elevenLabsTts(tts) {
3271
3323
  adapter: "fal",
3272
3324
  providerModelId: tts.providerModelId,
3273
3325
  lifecycle: "active",
3274
- exposure: EXPOSURE3,
3326
+ exposure: EXPOSURE4,
3275
3327
  constraints: {
3276
3328
  voices: ELEVENLABS_VOICES,
3277
3329
  languages: tts.languages,
@@ -3296,7 +3348,7 @@ function geminiTts(id, label, description) {
3296
3348
  adapter: "fal",
3297
3349
  providerModelId,
3298
3350
  lifecycle: "active",
3299
- exposure: EXPOSURE3,
3351
+ exposure: EXPOSURE4,
3300
3352
  constraints: {
3301
3353
  voices: GEMINI_TTS_VOICES,
3302
3354
  supports: ["style"],
@@ -3319,7 +3371,7 @@ function syncLipsync(id, label, providerModelId, syncModel, description) {
3319
3371
  adapter: "fal",
3320
3372
  providerModelId,
3321
3373
  lifecycle: "active",
3322
- exposure: EXPOSURE3,
3374
+ exposure: EXPOSURE4,
3323
3375
  constraints: {
3324
3376
  requires: ["sourceVideo", "sourceAudio"],
3325
3377
  references: LIPSYNC_AUDIO,
@@ -3380,7 +3432,7 @@ var MEDIA_NODE_MODELS = [
3380
3432
  adapter: "fal",
3381
3433
  providerModelId: "fal-ai/minimax/speech-2.8-hd",
3382
3434
  lifecycle: "active",
3383
- exposure: EXPOSURE3,
3435
+ exposure: EXPOSURE4,
3384
3436
  constraints: {
3385
3437
  voices: MINIMAX_VOICES,
3386
3438
  languages: MINIMAX_SPEECH_LANGUAGES,
@@ -3405,7 +3457,7 @@ var MEDIA_NODE_MODELS = [
3405
3457
  adapter: "fal",
3406
3458
  providerModelId: "fal-ai/elevenlabs/voice-changer",
3407
3459
  lifecycle: "active",
3408
- exposure: EXPOSURE3,
3460
+ exposure: EXPOSURE4,
3409
3461
  constraints: {
3410
3462
  requires: ["sourceAudio"],
3411
3463
  // The flat price assumes at most a minute of speech.
@@ -3432,7 +3484,7 @@ var MEDIA_NODE_MODELS = [
3432
3484
  adapter: "fal",
3433
3485
  providerModelId: "fal-ai/elevenlabs/dubbing",
3434
3486
  lifecycle: "active",
3435
- exposure: EXPOSURE3,
3487
+ exposure: EXPOSURE4,
3436
3488
  constraints: {
3437
3489
  requiresOneOf: ["sourceVideo", "sourceAudio"],
3438
3490
  // The flat price covers 60 s of source.
@@ -3480,7 +3532,7 @@ var MEDIA_NODE_MODELS = [
3480
3532
  adapter: "fal",
3481
3533
  providerModelId: "veed/lipsync/v2",
3482
3534
  lifecycle: "active",
3483
- exposure: EXPOSURE3,
3535
+ exposure: EXPOSURE4,
3484
3536
  constraints: {
3485
3537
  requires: ["sourceVideo", "sourceAudio"],
3486
3538
  references: LIPSYNC_AUDIO,
@@ -3506,7 +3558,7 @@ var MEDIA_NODE_MODELS = [
3506
3558
  adapter: "fal",
3507
3559
  providerModelId: "minimax/h3-max/lip-sync/image-to-video",
3508
3560
  lifecycle: "active",
3509
- exposure: EXPOSURE3,
3561
+ exposure: EXPOSURE4,
3510
3562
  constraints: {
3511
3563
  requires: ["firstFrame", "sourceAudio"],
3512
3564
  resolutions: ["480p", "768p", "1080p", "2k"],
@@ -3532,7 +3584,7 @@ var MEDIA_NODE_MODELS = [
3532
3584
  adapter: "fal",
3533
3585
  providerModelId: "fal-ai/elevenlabs/speech-to-text/scribe-v2",
3534
3586
  lifecycle: "active",
3535
- exposure: EXPOSURE3,
3587
+ exposure: EXPOSURE4,
3536
3588
  constraints: {
3537
3589
  requires: ["sourceAudio"],
3538
3590
  // The flat price covers 10 minutes of audio.
@@ -5068,8 +5120,113 @@ var VIDEO_FAMILY_MODELS = [
5068
5120
  }
5069
5121
  ];
5070
5122
 
5071
- // src/catalog/video.ts
5123
+ // src/catalog/video-luma.ts
5072
5124
  var VIDEO_USAGE2 = { unit: "seconds" };
5125
+ var EXPOSURE5 = ["cowork", "space_builder"];
5126
+ var LUMA_LOGO2 = "https://cdn.jsdelivr.net/npm/@lobehub/icons-static-png@1.97.0/light/luma-color.png";
5127
+ var RAY_RATIOS = ["9:16", "3:4", "1:1", "4:3", "16:9", "21:9"];
5128
+ var RAY_RESOLUTIONS = ["360p", "720p", "1080p"];
5129
+ var RAY_PROMPT = { referenceSyntax: "numbered", maxChars: 6e3 };
5130
+ var LUMA_VIDEO_MODELS = [
5131
+ {
5132
+ // https://docs.agents.lumalabs.ai: type "video" with a fixed 5 s duration;
5133
+ // firstFrame and lastFrame ride as start_frame and end_frame.
5134
+ id: "ray-3.2",
5135
+ aliases: [],
5136
+ label: "Ray 3.2",
5137
+ type: "video",
5138
+ adapter: "luma",
5139
+ providerModelId: "ray-3.2",
5140
+ lifecycle: "active",
5141
+ exposure: EXPOSURE5,
5142
+ constraints: {
5143
+ aspectRatios: ["adaptive", ...RAY_RATIOS],
5144
+ resolutions: [...RAY_RESOLUTIONS],
5145
+ durations: [5],
5146
+ supports: ["lastFrame"],
5147
+ prompt: { ...RAY_PROMPT },
5148
+ audio: "never"
5149
+ },
5150
+ defaults: { durationSeconds: 5, resolution: "720p", aspectRatio: "adaptive" },
5151
+ usage: { ...VIDEO_USAGE2 },
5152
+ pricingKind: "video_per_second",
5153
+ display: {
5154
+ provider: "luma",
5155
+ description: "Luma Ray 3.2 text- and image-to-video with optional first and last frames, 5-second clips at up to 1080p.",
5156
+ logo: LUMA_LOGO2
5157
+ },
5158
+ capabilities: {}
5159
+ },
5160
+ {
5161
+ // https://docs.agents.lumalabs.ai: type "video_edit" with a source video of
5162
+ // up to 18 s; the output keeps the source length, so there is no durations
5163
+ // knob, no aspect_ratio and no end frame. Luma bills its 5 s price for any
5164
+ // source (live, 2026-10-21) and the row is priced per source second, so a
5165
+ // source under 5 s is refused: it would be billed below Luma's charge.
5166
+ id: "ray-3.2-edit",
5167
+ aliases: [],
5168
+ label: "Ray 3.2 Edit",
5169
+ type: "video",
5170
+ adapter: "luma",
5171
+ providerModelId: "ray-3.2",
5172
+ lifecycle: "active",
5173
+ exposure: EXPOSURE5,
5174
+ constraints: {
5175
+ resolutions: [...RAY_RESOLUTIONS],
5176
+ requires: ["sourceVideo"],
5177
+ sourceVideoSeconds: { min: 5, max: 18 },
5178
+ supports: [],
5179
+ prompt: { ...RAY_PROMPT },
5180
+ audio: "never"
5181
+ },
5182
+ defaults: { resolution: "720p" },
5183
+ usage: { ...VIDEO_USAGE2 },
5184
+ pricingKind: "video_per_second",
5185
+ display: {
5186
+ provider: "luma",
5187
+ description: "Luma Ray 3.2 video edit: changes a 5-18 second clip from a prompt, keeping its length.",
5188
+ logo: LUMA_LOGO2
5189
+ },
5190
+ capabilities: { acceptsSourceVideo: true, videoEdit: true }
5191
+ },
5192
+ {
5193
+ // https://docs.agents.lumalabs.ai: type "video_reframe" with a required
5194
+ // target aspect_ratio; the source is up to 10 s and the output keeps its
5195
+ // length whatever video.duration says, while Luma bills that duration
5196
+ // (live, 2026-10-21). So there is no durations knob, and a source under
5197
+ // 5 s is refused, as on ray-3.2-edit. 1080p is not offered for the
5198
+ // vertical targets 9:16 and 3:4.
5199
+ id: "ray-3.2-reframe",
5200
+ aliases: [],
5201
+ label: "Ray 3.2 Reframe",
5202
+ type: "video",
5203
+ adapter: "luma",
5204
+ providerModelId: "ray-3.2",
5205
+ lifecycle: "active",
5206
+ exposure: EXPOSURE5,
5207
+ constraints: {
5208
+ aspectRatios: [...RAY_RATIOS],
5209
+ resolutions: [...RAY_RESOLUTIONS],
5210
+ requires: ["sourceVideo"],
5211
+ sourceVideoSeconds: { min: 5, max: 10 },
5212
+ supports: [],
5213
+ prompt: { ...RAY_PROMPT },
5214
+ audio: "never"
5215
+ },
5216
+ defaults: { resolution: "720p", aspectRatio: "16:9" },
5217
+ usage: { ...VIDEO_USAGE2 },
5218
+ pricingKind: "video_per_second",
5219
+ display: {
5220
+ provider: "luma",
5221
+ description: "Luma Ray 3.2 Reframe: changes a 5-10 second clip to a new aspect ratio and fills the new area from the prompt.",
5222
+ logo: LUMA_LOGO2
5223
+ },
5224
+ capabilities: { acceptsSourceVideo: true, videoEdit: true }
5225
+ }
5226
+ ];
5227
+
5228
+ // src/catalog/video.ts
5229
+ var VIDEO_USAGE3 = { unit: "seconds" };
5073
5230
  var SEEDANCE_RATIOS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "adaptive"];
5074
5231
  var KLING_RATIOS2 = ["16:9", "9:16", "1:1", "adaptive"];
5075
5232
  var KLING_OMNI_RATIOS = ["16:9", "9:16", "1:1"];
@@ -5122,7 +5279,7 @@ var VIDEO_MODELS = [
5122
5279
  audio: "always"
5123
5280
  },
5124
5281
  defaults: { durationSeconds: 8, resolution: "720p", aspectRatio: "16:9" },
5125
- usage: { ...VIDEO_USAGE2, slug: "veo" },
5282
+ usage: { ...VIDEO_USAGE3, slug: "veo" },
5126
5283
  pricingKind: "video_per_second",
5127
5284
  display: {
5128
5285
  provider: "google",
@@ -5155,7 +5312,7 @@ var VIDEO_MODELS = [
5155
5312
  audio: "always"
5156
5313
  },
5157
5314
  defaults: { durationSeconds: 8, resolution: "720p", aspectRatio: "16:9" },
5158
- usage: { ...VIDEO_USAGE2 },
5315
+ usage: { ...VIDEO_USAGE3 },
5159
5316
  pricingKind: "video_per_second",
5160
5317
  display: {
5161
5318
  provider: "google",
@@ -5185,7 +5342,7 @@ var VIDEO_MODELS = [
5185
5342
  audio: "always"
5186
5343
  },
5187
5344
  defaults: { durationSeconds: 8, resolution: "720p", aspectRatio: "16:9" },
5188
- usage: { ...VIDEO_USAGE2 },
5345
+ usage: { ...VIDEO_USAGE3 },
5189
5346
  pricingKind: "video_per_second",
5190
5347
  display: {
5191
5348
  provider: "google",
@@ -5222,7 +5379,7 @@ var VIDEO_MODELS = [
5222
5379
  audio: "always"
5223
5380
  },
5224
5381
  defaults: {},
5225
- usage: { ...VIDEO_USAGE2, slug: "gemini_omni" },
5382
+ usage: { ...VIDEO_USAGE3, slug: "gemini_omni" },
5226
5383
  pricingKind: "video_per_second",
5227
5384
  display: {
5228
5385
  provider: "google",
@@ -5259,7 +5416,7 @@ var VIDEO_MODELS = [
5259
5416
  audio: "always"
5260
5417
  },
5261
5418
  defaults: {},
5262
- usage: { ...VIDEO_USAGE2 },
5419
+ usage: { ...VIDEO_USAGE3 },
5263
5420
  pricingKind: "video_per_second",
5264
5421
  display: {
5265
5422
  provider: "google",
@@ -5308,7 +5465,7 @@ var VIDEO_MODELS = [
5308
5465
  aspectRatio: "16:9",
5309
5466
  generateAudio: true
5310
5467
  },
5311
- usage: { ...VIDEO_USAGE2, slug: "seedance2", providerLabel: "byteplus" },
5468
+ usage: { ...VIDEO_USAGE3, slug: "seedance2", providerLabel: "byteplus" },
5312
5469
  pricingKind: "video_per_second",
5313
5470
  display: {
5314
5471
  provider: "bytedance",
@@ -5351,7 +5508,7 @@ var VIDEO_MODELS = [
5351
5508
  aspectRatio: "16:9",
5352
5509
  generateAudio: true
5353
5510
  },
5354
- usage: { ...VIDEO_USAGE2 },
5511
+ usage: { ...VIDEO_USAGE3 },
5355
5512
  pricingKind: "video_per_second",
5356
5513
  display: {
5357
5514
  provider: "bytedance",
@@ -5401,7 +5558,7 @@ var VIDEO_MODELS = [
5401
5558
  aspectRatio: "16:9",
5402
5559
  generateAudio: true
5403
5560
  },
5404
- usage: { ...VIDEO_USAGE2 },
5561
+ usage: { ...VIDEO_USAGE3 },
5405
5562
  pricingKind: "video_per_second",
5406
5563
  display: {
5407
5564
  provider: "bytedance",
@@ -5463,7 +5620,7 @@ var VIDEO_MODELS = [
5463
5620
  cfgScale: 0.5,
5464
5621
  negativePrompt: NEGATIVE_PROMPT
5465
5622
  },
5466
- usage: { ...VIDEO_USAGE2 },
5623
+ usage: { ...VIDEO_USAGE3 },
5467
5624
  pricingKind: "video_per_second",
5468
5625
  display: {
5469
5626
  provider: "kuaishou",
@@ -5495,7 +5652,7 @@ var VIDEO_MODELS = [
5495
5652
  cfgScale: 0.5,
5496
5653
  generateAudio: true
5497
5654
  },
5498
- usage: { ...VIDEO_USAGE2 },
5655
+ usage: { ...VIDEO_USAGE3 },
5499
5656
  pricingKind: "video_per_second",
5500
5657
  display: {
5501
5658
  provider: "kuaishou",
@@ -5563,7 +5720,7 @@ var VIDEO_MODELS = [
5563
5720
  aspectRatio: "16:9",
5564
5721
  promptExpansion: "balanced"
5565
5722
  },
5566
- usage: { ...VIDEO_USAGE2 },
5723
+ usage: { ...VIDEO_USAGE3 },
5567
5724
  pricingKind: "video_per_second",
5568
5725
  display: {
5569
5726
  provider: "minimax",
@@ -5597,7 +5754,7 @@ var VIDEO_MODELS = [
5597
5754
  aspectRatio: "16:9",
5598
5755
  promptExpansion: "balanced"
5599
5756
  },
5600
- usage: { ...VIDEO_USAGE2 },
5757
+ usage: { ...VIDEO_USAGE3 },
5601
5758
  pricingKind: "video_per_second",
5602
5759
  display: {
5603
5760
  provider: "minimax",
@@ -5639,7 +5796,7 @@ var VIDEO_MODELS = [
5639
5796
  aspectRatio: "adaptive",
5640
5797
  promptExpansion: "balanced"
5641
5798
  },
5642
- usage: { ...VIDEO_USAGE2 },
5799
+ usage: { ...VIDEO_USAGE3 },
5643
5800
  pricingKind: "video_per_second",
5644
5801
  display: {
5645
5802
  provider: "minimax",
@@ -5683,7 +5840,7 @@ var VIDEO_MODELS = [
5683
5840
  aspectRatio: "adaptive",
5684
5841
  promptExpansion: "balanced"
5685
5842
  },
5686
- usage: { ...VIDEO_USAGE2 },
5843
+ usage: { ...VIDEO_USAGE3 },
5687
5844
  pricingKind: "video_per_second",
5688
5845
  display: {
5689
5846
  provider: "minimax",
@@ -5728,7 +5885,7 @@ var VIDEO_MODELS = [
5728
5885
  aspectRatio: "adaptive",
5729
5886
  generateAudio: true
5730
5887
  },
5731
- usage: { ...VIDEO_USAGE2 },
5888
+ usage: { ...VIDEO_USAGE3 },
5732
5889
  pricingKind: "video_per_second",
5733
5890
  display: {
5734
5891
  provider: "alibaba",
@@ -5755,7 +5912,7 @@ var VIDEO_MODELS = [
5755
5912
  audio: "never"
5756
5913
  },
5757
5914
  defaults: { durationSeconds: 5, aspectRatio: "16:9" },
5758
- usage: { ...VIDEO_USAGE2 },
5915
+ usage: { ...VIDEO_USAGE3 },
5759
5916
  pricingKind: "video_per_second",
5760
5917
  display: {
5761
5918
  provider: "kuaishou",
@@ -5804,7 +5961,7 @@ var VIDEO_MODELS = [
5804
5961
  audio: "never"
5805
5962
  },
5806
5963
  defaults: {},
5807
- usage: { ...VIDEO_USAGE2 },
5964
+ usage: { ...VIDEO_USAGE3 },
5808
5965
  // Higgsfield bills DoP per generation: a fixed-length clip with no duration knob.
5809
5966
  pricingKind: "flat",
5810
5967
  display: {
@@ -5813,7 +5970,8 @@ var VIDEO_MODELS = [
5813
5970
  },
5814
5971
  capabilities: {}
5815
5972
  },
5816
- ...VIDEO_FAMILY_MODELS
5973
+ ...VIDEO_FAMILY_MODELS,
5974
+ ...LUMA_VIDEO_MODELS
5817
5975
  ];
5818
5976
 
5819
5977
  // src/catalog/video-tools.ts
@@ -6787,7 +6945,8 @@ var ADAPTER_IDS = [
6787
6945
  "replicate",
6788
6946
  "prodia",
6789
6947
  "higgsfield",
6790
- "xai-video"
6948
+ "xai-video",
6949
+ "luma"
6791
6950
  ];
6792
6951
  var ModelTypeSchema = z3.enum([
6793
6952
  "text",
@@ -13452,9 +13611,608 @@ var higgsfieldAdapter = {
13452
13611
  }
13453
13612
  };
13454
13613
 
13614
+ // src/adapters/luma.ts
13615
+ import { z as z25 } from "zod";
13616
+
13617
+ // src/adapters/mp4-duration.ts
13618
+ function mp4DurationSeconds(bytes) {
13619
+ const moov = findBox(bytes, 0, bytes.length, "moov");
13620
+ if (!moov) return void 0;
13621
+ const mvhd = findBox(bytes, moov.start + 8, moov.end, "mvhd");
13622
+ if (!mvhd) return void 0;
13623
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
13624
+ const size = mvhd.end - mvhd.start;
13625
+ if (size < 8 + 4) return void 0;
13626
+ const version = view.getUint8(mvhd.start + 8);
13627
+ if (version === 0) {
13628
+ if (size < 8 + 4 + 4 + 4 + 4 + 4) return void 0;
13629
+ const timescale = view.getUint32(mvhd.start + 8 + 4 + 4 + 4);
13630
+ const duration = view.getUint32(mvhd.start + 8 + 4 + 4 + 4 + 4);
13631
+ if (timescale === 0) return void 0;
13632
+ return duration / timescale;
13633
+ }
13634
+ if (version === 1) {
13635
+ if (size < 8 + 4 + 8 + 8 + 4 + 8) return void 0;
13636
+ const timescale = view.getUint32(mvhd.start + 8 + 4 + 8 + 8);
13637
+ const high = view.getUint32(mvhd.start + 8 + 4 + 8 + 8 + 4);
13638
+ const low = view.getUint32(mvhd.start + 8 + 4 + 8 + 8 + 4 + 4);
13639
+ if (timescale === 0) return void 0;
13640
+ return (high * 2 ** 32 + low) / timescale;
13641
+ }
13642
+ return void 0;
13643
+ }
13644
+ function findBox(bytes, from, to, type) {
13645
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
13646
+ let offset = from;
13647
+ while (offset + 8 <= to) {
13648
+ const size = view.getUint32(offset);
13649
+ const name = String.fromCharCode(
13650
+ view.getUint8(offset + 4),
13651
+ view.getUint8(offset + 5),
13652
+ view.getUint8(offset + 6),
13653
+ view.getUint8(offset + 7)
13654
+ );
13655
+ if (size < 8 || offset + size > to) return void 0;
13656
+ if (name === type) return { start: offset, end: offset + size };
13657
+ offset += size;
13658
+ }
13659
+ return void 0;
13660
+ }
13661
+
13662
+ // src/adapters/luma.ts
13663
+ var DEFAULT_BASE_URL = "https://agents.lumalabs.ai";
13664
+ var IMAGE_POLL_INTERVAL_MS = 3e3;
13665
+ var IMAGE_TIMEOUT_MS = 5 * 6e4;
13666
+ var VIDEO_POLL_INTERVAL_MS = 5e3;
13667
+ var VIDEO_TIMEOUT_MS = 20 * 6e4;
13668
+ var PROMPT_MAX_CHARS = 6e3;
13669
+ var LUMA_IMAGE_MODEL_IDS = /* @__PURE__ */ new Set(["uni-1", "uni-1-max"]);
13670
+ var LUMA_VIDEO_MODEL_ID = "ray-3.2";
13671
+ var LUMA_VIDEO_MODES = /* @__PURE__ */ new Map([
13672
+ ["ray-3.2", "generate"],
13673
+ ["ray-3.2-edit", "edit"],
13674
+ ["ray-3.2-reframe", "reframe"]
13675
+ ]);
13676
+ function lumaRowRefusal(row2) {
13677
+ const provider = row2.providerModelId.toLowerCase();
13678
+ if (row2.type === "image") {
13679
+ return LUMA_IMAGE_MODEL_IDS.has(provider) ? void 0 : "luma image rows need providerModelId uni-1 or uni-1-max";
13680
+ }
13681
+ if (row2.type === "video") {
13682
+ if (provider !== LUMA_VIDEO_MODEL_ID) return "luma video rows need providerModelId ray-3.2";
13683
+ return !LUMA_VIDEO_MODES.has(row2.id) ? `luma routes ray-3.2 rows by catalog id; it knows ${[...LUMA_VIDEO_MODES.keys()].join(", ")}` : void 0;
13684
+ }
13685
+ return `luma has no ${row2.type} rows`;
13686
+ }
13687
+ function lumaBase(ctx) {
13688
+ return ctx.endpoints?.luma ?? DEFAULT_BASE_URL;
13689
+ }
13690
+ function lumaKey(ctx) {
13691
+ const key = ctx.keys.luma;
13692
+ if (!key) {
13693
+ throw new ModelKitError({
13694
+ code: "auth",
13695
+ message: "Missing API key for luma",
13696
+ provider: "luma",
13697
+ model: ctx.model.id,
13698
+ retryable: false
13699
+ });
13700
+ }
13701
+ return key;
13702
+ }
13703
+ function checkPrompt(req, ctx) {
13704
+ const prompt = req.prompt.trim();
13705
+ if (prompt.length === 0) {
13706
+ throw new ModelKitError({
13707
+ code: "missing_input",
13708
+ message: `${ctx.model.id} needs a prompt`,
13709
+ provider: "luma",
13710
+ model: ctx.model.id,
13711
+ retryable: false
13712
+ });
13713
+ }
13714
+ if (req.prompt.length > PROMPT_MAX_CHARS) {
13715
+ throw new ModelKitError({
13716
+ code: "unsupported_option",
13717
+ message: `${ctx.model.id} takes a prompt of at most ${PROMPT_MAX_CHARS} characters`,
13718
+ provider: "luma",
13719
+ model: ctx.model.id,
13720
+ retryable: false
13721
+ });
13722
+ }
13723
+ return req.prompt;
13724
+ }
13725
+ function imageRef(ref) {
13726
+ if ("url" in ref) return { url: ref.url };
13727
+ return { data: mediaRefToBase64(ref.bytes), media_type: ref.mime };
13728
+ }
13729
+ function videoRef(ref) {
13730
+ if ("url" in ref) return { url: ref.url, media_type: "video/mp4" };
13731
+ const mediaType = ref.mime.startsWith("video/") ? ref.mime : "video/mp4";
13732
+ return { data: mediaRefToBase64(ref.bytes), media_type: mediaType };
13733
+ }
13734
+ function lumaOutputFormat(req, ctx) {
13735
+ const format = req.outputFormat;
13736
+ if (format === void 0 || format === "png" || format === "jpeg") return format;
13737
+ if ((ctx.call.onUnsupported ?? "error") === "error") {
13738
+ throw new ModelKitError({
13739
+ code: "unsupported_option",
13740
+ message: `${ctx.model.id} has no outputFormat "${format}". Supported: png, jpeg.`,
13741
+ provider: "luma",
13742
+ model: ctx.model.id,
13743
+ retryable: false
13744
+ });
13745
+ }
13746
+ ctx.warnings.push({
13747
+ code: "option_ignored",
13748
+ field: "outputFormat",
13749
+ from: format,
13750
+ to: "png",
13751
+ message: `${ctx.model.id} has no outputFormat "${format}"; using png`
13752
+ });
13753
+ return "png";
13754
+ }
13755
+ function refuseOrDrop(ctx, field, message) {
13756
+ if ((ctx.call.onUnsupported ?? "error") === "error") {
13757
+ throw new ModelKitError({
13758
+ code: "unsupported_option",
13759
+ message,
13760
+ provider: "luma",
13761
+ model: ctx.model.id,
13762
+ retryable: false
13763
+ });
13764
+ }
13765
+ ctx.warnings.push({ code: "option_ignored", field, message });
13766
+ }
13767
+ function lumaVideoMode(ctx) {
13768
+ const mode = LUMA_VIDEO_MODES.get(ctx.model.id);
13769
+ if (mode === void 0) {
13770
+ throw new ModelKitError({
13771
+ code: "unsupported_option",
13772
+ message: `luma routes ray-3.2 rows by catalog id; "${ctx.model.id}" is not a luma video row`,
13773
+ provider: "luma",
13774
+ model: ctx.model.id,
13775
+ retryable: false
13776
+ });
13777
+ }
13778
+ return mode;
13779
+ }
13780
+ function reframeTooTall(req) {
13781
+ const vertical = req.aspectRatio === "9:16" || req.aspectRatio === "3:4";
13782
+ return vertical && req.resolution === "1080p";
13783
+ }
13784
+ function sentResolution(req, mode) {
13785
+ return mode === "reframe" && reframeTooTall(req) ? "720p" : req.resolution;
13786
+ }
13787
+ function lumaReframeResolution(req, ctx) {
13788
+ const requested = req.resolution;
13789
+ if (requested === void 0 || !reframeTooTall(req)) return requested;
13790
+ const message = `${ctx.model.id} renders 9:16 and 3:4 reframes at up to 720p`;
13791
+ if ((ctx.call.onUnsupported ?? "error") === "error") {
13792
+ throw new ModelKitError({
13793
+ code: "unsupported_option",
13794
+ message,
13795
+ provider: "luma",
13796
+ model: ctx.model.id,
13797
+ retryable: false
13798
+ });
13799
+ }
13800
+ ctx.warnings.push({
13801
+ code: "resolution_coerced",
13802
+ field: "resolution",
13803
+ from: requested,
13804
+ to: "720p",
13805
+ message
13806
+ });
13807
+ return "720p";
13808
+ }
13809
+ var lumaGenerationSchema = z25.looseObject({
13810
+ id: z25.string(),
13811
+ state: z25.string().optional(),
13812
+ output: z25.array(
13813
+ z25.looseObject({
13814
+ type: z25.string().optional(),
13815
+ url: z25.string().optional()
13816
+ })
13817
+ ).optional(),
13818
+ failure_reason: z25.string().optional().nullable(),
13819
+ failure_code: z25.string().optional().nullable()
13820
+ });
13821
+ function lumaFailure(ctx, code, reason) {
13822
+ const detail = reason ?? code ?? "unknown_error";
13823
+ const message = `Luma generation failed: ${detail}`;
13824
+ switch (code) {
13825
+ case "content_moderated":
13826
+ return new ModelKitError({
13827
+ code: "content_blocked",
13828
+ message,
13829
+ provider: "luma",
13830
+ model: ctx.model.id,
13831
+ retryable: false
13832
+ });
13833
+ case "rate_limited":
13834
+ return new ModelKitError({
13835
+ code: "rate_limited",
13836
+ message,
13837
+ provider: "luma",
13838
+ model: ctx.model.id
13839
+ });
13840
+ case "generation_failed":
13841
+ case "output_not_found":
13842
+ return new ModelKitError({
13843
+ code: "provider_rejected",
13844
+ message,
13845
+ provider: "luma",
13846
+ model: ctx.model.id,
13847
+ retryable: true
13848
+ });
13849
+ case "budget_exhausted":
13850
+ return new ModelKitError({
13851
+ code: "provider_billing",
13852
+ message,
13853
+ provider: "luma",
13854
+ model: ctx.model.id,
13855
+ retryable: false
13856
+ });
13857
+ case "image_too_large":
13858
+ case "unsupported_format":
13859
+ return new ModelKitError({
13860
+ code: "unsupported_option",
13861
+ message,
13862
+ provider: "luma",
13863
+ model: ctx.model.id,
13864
+ retryable: false
13865
+ });
13866
+ default:
13867
+ return new ModelKitError({
13868
+ code: "provider_rejected",
13869
+ message,
13870
+ provider: "luma",
13871
+ model: ctx.model.id,
13872
+ retryable: false
13873
+ });
13874
+ }
13875
+ }
13876
+ function requestInit2(ctx, key, body) {
13877
+ const init = {
13878
+ method: body !== void 0 ? "POST" : "GET",
13879
+ headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" }
13880
+ };
13881
+ if (body !== void 0) init.body = body;
13882
+ if (ctx.signal !== void 0) init.signal = ctx.signal;
13883
+ return init;
13884
+ }
13885
+ async function lumaRequest(ctx, url, body) {
13886
+ const key = lumaKey(ctx);
13887
+ let res;
13888
+ try {
13889
+ res = await ctx.fetch(url, requestInit2(ctx, key, body));
13890
+ } catch (cause) {
13891
+ throw classifyThrown("luma", cause, ctx.model.id);
13892
+ }
13893
+ if (!res.ok) {
13894
+ throw classifyHttpError(res.status, await readBody(res), "luma", ctx.model.id);
13895
+ }
13896
+ return res;
13897
+ }
13898
+ async function submitGeneration(ctx, body) {
13899
+ const retryOptions = mediaRetryOptions(ctx.call, ctx.signal);
13900
+ const res = await withRetry(
13901
+ () => lumaRequest(ctx, `${lumaBase(ctx)}/v1/generations`, JSON.stringify(body)),
13902
+ retryOptions
13903
+ );
13904
+ const parsed = lumaGenerationSchema.safeParse(await res.json());
13905
+ if (!parsed.success) {
13906
+ throw new ModelKitError({
13907
+ code: "provider_rejected",
13908
+ message: "Luma submit returned no generation id",
13909
+ provider: "luma",
13910
+ model: ctx.model.id,
13911
+ retryable: false
13912
+ });
13913
+ }
13914
+ return parsed.data;
13915
+ }
13916
+ async function readGeneration(ctx, id) {
13917
+ const res = await lumaRequest(ctx, `${lumaBase(ctx)}/v1/generations/${id}`);
13918
+ const payload = JsonValueSchema.parse(await res.json());
13919
+ const parsed = lumaGenerationSchema.safeParse(payload);
13920
+ if (!parsed.success) {
13921
+ throw new ModelKitError({
13922
+ code: "provider_rejected",
13923
+ message: "Luma returned an unreadable generation",
13924
+ provider: "luma",
13925
+ model: ctx.model.id,
13926
+ retryable: false
13927
+ });
13928
+ }
13929
+ return parsed.data;
13930
+ }
13931
+ function outputUrl(generation, type) {
13932
+ const outputs = generation.output ?? [];
13933
+ const match = outputs.find((item) => item.type === type && item.url);
13934
+ if (match?.url) return match.url;
13935
+ const fallback = outputs.find((item) => item.url);
13936
+ return fallback?.url;
13937
+ }
13938
+ async function readGenerationOnce(ctx, id) {
13939
+ try {
13940
+ return await readGeneration(ctx, id);
13941
+ } catch (cause) {
13942
+ if (ctx.call.maxRetries === 0) throw cause;
13943
+ if (isModelKitError(cause) && (cause.code === "network" || cause.status === 429 || (cause.status ?? 0) >= 500)) {
13944
+ return void 0;
13945
+ }
13946
+ throw cause;
13947
+ }
13948
+ }
13949
+ async function checkGeneration(ctx, id, type) {
13950
+ const generation = await readGenerationOnce(ctx, id);
13951
+ if (generation === void 0) return void 0;
13952
+ if (generation.state === "completed") {
13953
+ const url = outputUrl(generation, type);
13954
+ if (!url) {
13955
+ throw new ModelKitError({
13956
+ code: "provider_rejected",
13957
+ message: "Luma completed without an output URL",
13958
+ provider: "luma",
13959
+ model: ctx.model.id,
13960
+ retryable: false
13961
+ });
13962
+ }
13963
+ return url;
13964
+ }
13965
+ if (generation.state === "failed") {
13966
+ throw lumaFailure(
13967
+ ctx,
13968
+ generation.failure_code ?? void 0,
13969
+ generation.failure_reason ?? void 0
13970
+ );
13971
+ }
13972
+ return void 0;
13973
+ }
13974
+ async function downloadBytes(ctx, url, fallback) {
13975
+ let bytes;
13976
+ let header;
13977
+ try {
13978
+ const out = await mediaRefToBytes({ url }, ctx.fetch, {
13979
+ ...ctx.signal !== void 0 && { signal: ctx.signal },
13980
+ provider: "luma",
13981
+ model: ctx.model.id
13982
+ });
13983
+ bytes = out.bytes;
13984
+ header = out.mime;
13985
+ } catch (cause) {
13986
+ throw classifyThrown("luma", cause, ctx.model.id);
13987
+ }
13988
+ const kind = fallback.split("/")[0];
13989
+ const usable = header !== void 0 && header.startsWith(`${kind}/`);
13990
+ return { bytes, mime: usable ? header : fallback };
13991
+ }
13992
+ function imageUsage2(ctx) {
13993
+ return {
13994
+ model: ctx.model.id,
13995
+ provider: "luma",
13996
+ modality: "IMAGE",
13997
+ unit: "images",
13998
+ quantity: 1,
13999
+ imageSize: "2K"
14000
+ };
14001
+ }
14002
+ var GENERATE_SECONDS = 5;
14003
+ var MAX_PLAUSIBLE_SECONDS = 60;
14004
+ function billedSeconds(req, mode, bytes) {
14005
+ if (mode === "generate") return GENERATE_SECONDS;
14006
+ const measured = mp4DurationSeconds(bytes);
14007
+ const source2 = req.sourceVideo;
14008
+ const length = measured !== void 0 && measured > 0 && measured <= MAX_PLAUSIBLE_SECONDS ? measured : source2 !== void 0 && "url" in source2 ? source2.durationSeconds : void 0;
14009
+ return Math.max(length ?? 0, GENERATE_SECONDS);
14010
+ }
14011
+ function videoUsage4(ctx, req, mode, bytes) {
14012
+ const seconds3 = billedSeconds(req, mode, bytes);
14013
+ const event = {
14014
+ model: ctx.model.id,
14015
+ provider: "luma",
14016
+ modality: "VIDEO",
14017
+ unit: "seconds",
14018
+ quantity: seconds3,
14019
+ durationSeconds: seconds3
14020
+ };
14021
+ const resolution = sentResolution(req, mode);
14022
+ if (resolution !== void 0) event.resolution = resolution;
14023
+ return event;
14024
+ }
14025
+ function imageBody(req, ctx, outputFormat) {
14026
+ const prompt = checkPrompt(req, ctx);
14027
+ const model = ctx.model.providerModelId;
14028
+ const ratio = req.aspectRatio !== "adaptive" ? req.aspectRatio : void 0;
14029
+ const [source2, ...rest] = req.references ?? [];
14030
+ if (source2 === void 0) {
14031
+ const body2 = { model, type: "image", prompt };
14032
+ if (ratio !== void 0) body2["aspect_ratio"] = ratio;
14033
+ if (outputFormat !== void 0) body2["output_format"] = outputFormat;
14034
+ return body2;
14035
+ }
14036
+ if (ratio !== void 0) {
14037
+ ctx.warnings.push({
14038
+ code: "option_ignored",
14039
+ field: "aspectRatio",
14040
+ message: `${ctx.model.id} keeps the source image's aspect ratio on an edit`
14041
+ });
14042
+ }
14043
+ const body = {
14044
+ model,
14045
+ type: "image_edit",
14046
+ prompt,
14047
+ source: imageRef(source2)
14048
+ };
14049
+ if (rest.length > 0) body["image_ref"] = rest.map(imageRef);
14050
+ if (outputFormat !== void 0) body["output_format"] = outputFormat;
14051
+ return body;
14052
+ }
14053
+ function videoBody(req, ctx, mode) {
14054
+ const prompt = checkPrompt(req, ctx);
14055
+ const model = ctx.model.providerModelId;
14056
+ const hasReferences = (req.referenceImages ?? []).length + (req.referenceVideos ?? []).length + (req.referenceAudios ?? []).length > 0;
14057
+ if (hasReferences) {
14058
+ refuseOrDrop(
14059
+ ctx,
14060
+ "referenceImages",
14061
+ `${ctx.model.id} takes no reference images, videos or audio`
14062
+ );
14063
+ }
14064
+ if (mode === "generate") {
14065
+ const body = {
14066
+ model,
14067
+ type: "video",
14068
+ prompt,
14069
+ video: {
14070
+ ...req.resolution !== void 0 && { resolution: req.resolution },
14071
+ duration: "5s",
14072
+ ...req.firstFrame !== void 0 && { start_frame: imageRef(req.firstFrame) },
14073
+ ...req.lastFrame !== void 0 && { end_frame: imageRef(req.lastFrame) }
14074
+ }
14075
+ };
14076
+ if (req.aspectRatio !== void 0 && req.aspectRatio !== "adaptive") {
14077
+ body["aspect_ratio"] = req.aspectRatio;
14078
+ }
14079
+ return body;
14080
+ }
14081
+ const sourceVideo = req.sourceVideo;
14082
+ if (!sourceVideo) {
14083
+ throw new ModelKitError({
14084
+ code: "missing_input",
14085
+ message: `${ctx.model.id} requires sourceVideo`,
14086
+ provider: "luma",
14087
+ model: ctx.model.id,
14088
+ retryable: false
14089
+ });
14090
+ }
14091
+ if (mode === "edit") {
14092
+ if (req.lastFrame !== void 0) {
14093
+ refuseOrDrop(ctx, "lastFrame", `${ctx.model.id} takes no end frame`);
14094
+ }
14095
+ const body = {
14096
+ model,
14097
+ type: "video_edit",
14098
+ prompt,
14099
+ source: videoRef(sourceVideo),
14100
+ video: {
14101
+ ...req.resolution !== void 0 && { resolution: req.resolution },
14102
+ ...req.firstFrame !== void 0 && { start_frame: imageRef(req.firstFrame) },
14103
+ edit: { auto_controls: true }
14104
+ }
14105
+ };
14106
+ return body;
14107
+ }
14108
+ if (req.firstFrame !== void 0) {
14109
+ refuseOrDrop(ctx, "firstFrame", `${ctx.model.id} takes no start frame`);
14110
+ }
14111
+ if (req.lastFrame !== void 0) {
14112
+ refuseOrDrop(ctx, "lastFrame", `${ctx.model.id} takes no end frame`);
14113
+ }
14114
+ const aspectRatio = req.aspectRatio;
14115
+ if (aspectRatio === void 0 || aspectRatio === "adaptive") {
14116
+ throw new ModelKitError({
14117
+ code: "missing_input",
14118
+ message: `${ctx.model.id} needs an aspectRatio target`,
14119
+ provider: "luma",
14120
+ model: ctx.model.id,
14121
+ retryable: false
14122
+ });
14123
+ }
14124
+ const resolution = lumaReframeResolution(req, ctx);
14125
+ return {
14126
+ model,
14127
+ type: "video_reframe",
14128
+ prompt,
14129
+ aspect_ratio: aspectRatio,
14130
+ source: videoRef(sourceVideo),
14131
+ video: { ...resolution !== void 0 && { resolution } }
14132
+ };
14133
+ }
14134
+ async function generateLumaImage(req, ctx) {
14135
+ throwIfAborted(ctx.signal);
14136
+ lumaKey(ctx);
14137
+ if (!LUMA_IMAGE_MODEL_IDS.has(ctx.model.providerModelId.toLowerCase())) {
14138
+ throw new ModelKitError({
14139
+ code: "unsupported_option",
14140
+ message: `luma has no image mapping for "${ctx.model.providerModelId}"`,
14141
+ provider: "luma",
14142
+ model: ctx.model.id,
14143
+ retryable: false
14144
+ });
14145
+ }
14146
+ const format = lumaOutputFormat(req, ctx);
14147
+ const created = await submitGeneration(ctx, imageBody(req, ctx, format));
14148
+ const url = await pollUntil(checkGeneration.bind(null, ctx, created.id, "image"), {
14149
+ intervalMs: IMAGE_POLL_INTERVAL_MS,
14150
+ timeoutMs: IMAGE_TIMEOUT_MS,
14151
+ ...ctx.signal !== void 0 && { signal: ctx.signal }
14152
+ });
14153
+ const image = await downloadBytes(ctx, url, format === "jpeg" ? "image/jpeg" : "image/png");
14154
+ return {
14155
+ images: [image],
14156
+ usage: imageUsage2(ctx),
14157
+ raw: created
14158
+ };
14159
+ }
14160
+ function lumaVideoJob(ctx, req, mode, id) {
14161
+ return {
14162
+ jobId: id,
14163
+ async poll() {
14164
+ const generation = await readGenerationOnce(ctx, id);
14165
+ if (generation?.state === "failed") return { status: "failed" };
14166
+ if (generation?.state === "completed") return { status: "succeeded" };
14167
+ return { status: "pending" };
14168
+ },
14169
+ async result(opts) {
14170
+ const signal = opts?.signal ?? ctx.signal;
14171
+ const url = await pollUntil(checkGeneration.bind(null, ctx, id, "video"), {
14172
+ intervalMs: opts?.pollIntervalMs ?? VIDEO_POLL_INTERVAL_MS,
14173
+ timeoutMs: opts?.timeoutMs ?? VIDEO_TIMEOUT_MS,
14174
+ ...signal !== void 0 && { signal }
14175
+ });
14176
+ const video = await downloadBytes(ctx, url, "video/mp4");
14177
+ const result = {
14178
+ video,
14179
+ usage: videoUsage4(ctx, req, mode, video.bytes),
14180
+ raw: { id }
14181
+ };
14182
+ return result;
14183
+ }
14184
+ };
14185
+ }
14186
+ async function generateLumaVideo(req, ctx) {
14187
+ throwIfAborted(ctx.signal);
14188
+ lumaKey(ctx);
14189
+ if (ctx.model.providerModelId.toLowerCase() !== LUMA_VIDEO_MODEL_ID) {
14190
+ throw new ModelKitError({
14191
+ code: "unsupported_option",
14192
+ message: `luma has no video mapping for "${ctx.model.providerModelId}"`,
14193
+ provider: "luma",
14194
+ model: ctx.model.id,
14195
+ retryable: false
14196
+ });
14197
+ }
14198
+ const mode = lumaVideoMode(ctx);
14199
+ const created = await submitGeneration(ctx, videoBody(req, ctx, mode));
14200
+ return lumaVideoJob(ctx, req, mode, created.id);
14201
+ }
14202
+ var lumaAdapter = {
14203
+ id: "luma",
14204
+ generateImage: generateLumaImage,
14205
+ generateVideo: generateLumaVideo,
14206
+ resumeVideo: (req, jobId, ctx) => {
14207
+ throwIfAborted(ctx.signal);
14208
+ lumaKey(ctx);
14209
+ return lumaVideoJob(ctx, req, lumaVideoMode(ctx), jobId);
14210
+ }
14211
+ };
14212
+
13455
14213
  // src/adapters/openai-image.ts
13456
14214
  import OpenAI from "openai";
13457
- import { z as z25 } from "zod";
14215
+ import { z as z26 } from "zod";
13458
14216
  var MAX_EDIT_REFERENCES = 16;
13459
14217
  function aspectRatioToSize(ratio, tier) {
13460
14218
  const TIER_LONG_EDGE = {
@@ -13537,9 +14295,9 @@ function toProviderError2(cause, ctx) {
13537
14295
  ctx.model.id
13538
14296
  );
13539
14297
  }
13540
- var openaiUsageSchema = z25.object({
13541
- input_tokens: z25.number().optional(),
13542
- output_tokens: z25.number().optional()
14298
+ var openaiUsageSchema = z26.object({
14299
+ input_tokens: z26.number().optional(),
14300
+ output_tokens: z26.number().optional()
13543
14301
  });
13544
14302
  var openaiImageAdapter = {
13545
14303
  id: "openai-image",
@@ -13951,13 +14709,13 @@ var prodiaAdapter = {
13951
14709
  import Replicate from "replicate";
13952
14710
 
13953
14711
  // src/adapters/replicate-http.ts
13954
- import { z as z26 } from "zod";
13955
- var predictionSchema = z26.object({
13956
- id: z26.string(),
13957
- status: z26.string(),
14712
+ import { z as z27 } from "zod";
14713
+ var predictionSchema = z27.object({
14714
+ id: z27.string(),
14715
+ status: z27.string(),
13958
14716
  output: JsonValueSchema.optional(),
13959
14717
  error: JsonValueSchema.optional(),
13960
- metrics: z26.object({ predict_time: z26.number().optional() }).optional()
14718
+ metrics: z27.object({ predict_time: z27.number().optional() }).optional()
13961
14719
  });
13962
14720
  function createReplicateHttpClient(opts) {
13963
14721
  async function request(path, body, signal) {
@@ -14008,7 +14766,7 @@ function createReplicateHttpClient(opts) {
14008
14766
  }
14009
14767
 
14010
14768
  // src/adapters/replicate.ts
14011
- import { z as z27 } from "zod";
14769
+ import { z as z28 } from "zod";
14012
14770
  var POLL_INTERVAL_MS = 1e3;
14013
14771
  var POLL_TIMEOUT_MS = 15 * 6e4;
14014
14772
  var defaultReplicateDeps = {
@@ -14048,8 +14806,8 @@ function route(providerModelId) {
14048
14806
  if (!providerModelId.includes("/")) return { version: providerModelId };
14049
14807
  return { model: providerModelId };
14050
14808
  }
14051
- var replicateResponseStatusSchema = z27.object({
14052
- response: z27.object({ status: z27.number() }).partial()
14809
+ var replicateResponseStatusSchema = z28.object({
14810
+ response: z28.object({ status: z28.number() }).partial()
14053
14811
  });
14054
14812
  function replicateError(cause, ctx) {
14055
14813
  if (isModelKitError(cause)) return cause;
@@ -14072,7 +14830,7 @@ function isUrlFn(value) {
14072
14830
  function extractOutputUrl(output) {
14073
14831
  const root = Array.isArray(output) ? output[0] : output;
14074
14832
  if (root === void 0) throw noOutput();
14075
- const asString = z27.string().safeParse(root);
14833
+ const asString = z28.string().safeParse(root);
14076
14834
  if (asString.success && asString.data) return asString.data;
14077
14835
  if (root instanceof URL) return root.href;
14078
14836
  if (isObjectOutput(root)) {
@@ -14081,15 +14839,15 @@ function extractOutputUrl(output) {
14081
14839
  if (urlValue !== void 0 && isUrlFn(urlValue)) {
14082
14840
  const called = urlValue();
14083
14841
  if (called instanceof URL) return called.href;
14084
- const calledString = z27.string().safeParse(called);
14842
+ const calledString = z28.string().safeParse(called);
14085
14843
  if (calledString.success && calledString.data) return calledString.data;
14086
14844
  } else {
14087
- const direct = z27.string().safeParse(urlValue);
14845
+ const direct = z28.string().safeParse(urlValue);
14088
14846
  if (direct.success && direct.data) return direct.data;
14089
14847
  }
14090
14848
  }
14091
14849
  if ("default" in root) {
14092
- const fallback = z27.string().safeParse(root.default);
14850
+ const fallback = z28.string().safeParse(root.default);
14093
14851
  if (fallback.success && fallback.data) return fallback.data;
14094
14852
  }
14095
14853
  }
@@ -14150,7 +14908,7 @@ async function runPrediction(ctx, input, deps) {
14150
14908
  }
14151
14909
  return void 0;
14152
14910
  }, pollOpts);
14153
- const predictTime = z27.number().safeParse(done.metrics?.predict_time);
14911
+ const predictTime = z28.number().safeParse(done.metrics?.predict_time);
14154
14912
  if (done.output === void 0) throw noOutput();
14155
14913
  return {
14156
14914
  url: extractOutputUrl(done.output),
@@ -14158,7 +14916,7 @@ async function runPrediction(ctx, input, deps) {
14158
14916
  raw: done
14159
14917
  };
14160
14918
  }
14161
- function imageUsage2(ctx, req) {
14919
+ function imageUsage3(ctx, req) {
14162
14920
  const event = {
14163
14921
  model: ctx.model.id,
14164
14922
  provider: "replicate",
@@ -14170,7 +14928,7 @@ function imageUsage2(ctx, req) {
14170
14928
  if (req.quality !== void 0) event.imageQuality = req.quality;
14171
14929
  return event;
14172
14930
  }
14173
- function videoUsage4(ctx, req) {
14931
+ function videoUsage5(ctx, req) {
14174
14932
  const event = {
14175
14933
  model: ctx.model.id,
14176
14934
  provider: "replicate",
@@ -14396,7 +15154,7 @@ async function generateReplicateImage(req, ctx, deps) {
14396
15154
  }
14397
15155
  const { url, predictTime, raw } = await runPrediction(ctx, input, deps);
14398
15156
  const file = await download(ctx, url);
14399
- const usage3 = imageUsage2(ctx, req);
15157
+ const usage3 = imageUsage3(ctx, req);
14400
15158
  usage3.quantity = predictTime;
14401
15159
  return { images: [{ bytes: file.bytes, mime: file.mime }], usage: usage3, raw, outputUrl: url };
14402
15160
  }
@@ -14503,7 +15261,7 @@ async function generateReplicateVideo(req, ctx, deps) {
14503
15261
  throw replicateError(cause, ctx);
14504
15262
  }
14505
15263
  if (current.status === "succeeded") {
14506
- const predictTime = z27.number().safeParse(current.metrics?.predict_time);
15264
+ const predictTime = z28.number().safeParse(current.metrics?.predict_time);
14507
15265
  if (current.output === void 0) throw noOutput();
14508
15266
  return {
14509
15267
  url: extractOutputUrl(current.output),
@@ -14522,7 +15280,7 @@ async function generateReplicateVideo(req, ctx, deps) {
14522
15280
  return void 0;
14523
15281
  };
14524
15282
  const toResult = (done) => {
14525
- const usage3 = videoUsage4(ctx, req);
15283
+ const usage3 = videoUsage5(ctx, req);
14526
15284
  usage3.quantity = done.predictTime;
14527
15285
  return { video: { url: done.url }, usage: usage3, raw: done.raw };
14528
15286
  };
@@ -14645,7 +15403,7 @@ async function generateReplicateAudio(req, ctx, deps) {
14645
15403
  // src/adapters/xai-video.ts
14646
15404
  import { experimental_generateVideo } from "ai";
14647
15405
  import { createXai as createXai2 } from "@ai-sdk/xai";
14648
- import { z as z28 } from "zod";
15406
+ import { z as z29 } from "zod";
14649
15407
  var POLL_INTERVAL_MS2 = 1e4;
14650
15408
  var POLL_TIMEOUT_MS2 = 15 * 6e4;
14651
15409
  var defaultXaiDeps = {
@@ -14658,8 +15416,8 @@ function createXaiVideoAdapter(deps = defaultXaiDeps) {
14658
15416
  };
14659
15417
  }
14660
15418
  var xaiVideoAdapter = createXaiVideoAdapter();
14661
- var xaiResponseStatusSchema = z28.object({
14662
- response: z28.object({ status: z28.number() }).partial()
15419
+ var xaiResponseStatusSchema = z29.object({
15420
+ response: z29.object({ status: z29.number() }).partial()
14663
15421
  });
14664
15422
  function xaiError(cause, ctx) {
14665
15423
  const info = describeThrown(cause);
@@ -14667,12 +15425,12 @@ function xaiError(cause, ctx) {
14667
15425
  const status = info.status ?? (nested.success ? nested.data.response?.status : void 0);
14668
15426
  return classifyThrown("xai-video", cause, ctx.model.id, { status, body: info.body });
14669
15427
  }
14670
- var xaiMetaSchema = z28.object({
15428
+ var xaiMetaSchema = z29.object({
14671
15429
  videoUrl: JsonValueSchema.optional(),
14672
15430
  costInUsdTicks: JsonValueSchema.optional(),
14673
15431
  duration: JsonValueSchema.optional()
14674
15432
  });
14675
- var xaiProviderMetadataSchema = z28.object({ xai: xaiMetaSchema });
15433
+ var xaiProviderMetadataSchema = z29.object({ xai: xaiMetaSchema });
14676
15434
  function metaOf(result) {
14677
15435
  const parsed = xaiProviderMetadataSchema.partial().safeParse(result.providerMetadata);
14678
15436
  if (!parsed.success) return {};
@@ -14788,7 +15546,7 @@ async function mapResult(ctx, req, result) {
14788
15546
  };
14789
15547
  }
14790
15548
  const meta = metaOf(result);
14791
- const videoUrl = z28.string().safeParse(meta.videoUrl);
15549
+ const videoUrl = z29.string().safeParse(meta.videoUrl);
14792
15550
  if (videoUrl.success && videoUrl.data) {
14793
15551
  const res = await ctx.fetch(videoUrl.data);
14794
15552
  if (!res.ok) {
@@ -14811,7 +15569,7 @@ async function mapResult(ctx, req, result) {
14811
15569
  });
14812
15570
  }
14813
15571
  function usage2(ctx, req, meta) {
14814
- const costTicks = z28.number().safeParse(meta.costInUsdTicks);
15572
+ const costTicks = z29.number().safeParse(meta.costInUsdTicks);
14815
15573
  const cost = costTicks.success ? costTicks.data / 1e10 : void 0;
14816
15574
  const event = {
14817
15575
  model: ctx.model.id,
@@ -14859,7 +15617,8 @@ var DEFAULT_ADAPTERS = {
14859
15617
  replicate: replicateAdapter,
14860
15618
  prodia: prodiaAdapter,
14861
15619
  higgsfield: higgsfieldAdapter,
14862
- "xai-video": xaiVideoAdapter
15620
+ "xai-video": xaiVideoAdapter,
15621
+ luma: lumaAdapter
14863
15622
  };
14864
15623
 
14865
15624
  // src/catalog/data-only.ts
@@ -14975,6 +15734,10 @@ function dataOnlyCapable(row2) {
14975
15734
  reason: "higgsfield providerModelId must be a platform path, not a URL"
14976
15735
  };
14977
15736
  }
15737
+ case "luma": {
15738
+ const reason = lumaRowRefusal(row2);
15739
+ return reason === void 0 ? { ok: true } : { ok: false, reason };
15740
+ }
14978
15741
  default: {
14979
15742
  return assertNever(row2.adapter);
14980
15743
  }
@@ -14994,22 +15757,22 @@ var liveState = store[KEY] ??= {
14994
15757
  var liveCatalog = liveState.catalog;
14995
15758
 
14996
15759
  // src/catalog/load.ts
14997
- import { z as z29 } from "zod";
15760
+ import { z as z30 } from "zod";
14998
15761
  function rowCount() {
14999
15762
  return liveCatalog.list({ lifecycle: ["active", "deprecated", "retired"] }).length;
15000
15763
  }
15001
15764
  function authHeaders(auth) {
15002
15765
  return "bearer" in auth ? { Authorization: `Bearer ${auth.bearer}` } : { "x-api-key": auth.apiKey };
15003
15766
  }
15004
- var EnvelopeSchema = z29.object({
15005
- rows: z29.array(JsonValueSchema),
15006
- revision: z29.string().optional()
15767
+ var EnvelopeSchema = z30.object({
15768
+ rows: z30.array(JsonValueSchema),
15769
+ revision: z30.string().optional()
15007
15770
  });
15008
15771
  function dropReason(path, message) {
15009
15772
  return path.length === 0 ? message : `${path.map((part) => String(part)).join(".")} ${message}`;
15010
15773
  }
15011
15774
  function rowId(raw) {
15012
- const parsed = z29.object({ id: z29.string().min(1) }).loose().safeParse(raw);
15775
+ const parsed = z30.object({ id: z30.string().min(1) }).loose().safeParse(raw);
15013
15776
  if (!parsed.success) return "?";
15014
15777
  return parsed.data.id;
15015
15778
  }
@@ -16058,7 +16821,8 @@ var ADAPTER_KEYS = {
16058
16821
  replicate: ["replicate"],
16059
16822
  prodia: ["prodia"],
16060
16823
  higgsfield: ["higgsfield"],
16061
- "xai-video": ["xai"]
16824
+ "xai-video": ["xai"],
16825
+ luma: ["luma"]
16062
16826
  };
16063
16827
  function hasAll(keys, names) {
16064
16828
  if (!keys) return false;
@@ -16116,6 +16880,7 @@ function keysFromEnv(env, prefix = "") {
16116
16880
  set("prodia", pick2("PRODIA_API_KEY", "PRODIA_TOKEN"));
16117
16881
  set("higgsfield", pick2("HIGGSFIELD_API_KEY"));
16118
16882
  set("higgsfieldSecret", pick2("HIGGSFIELD_API_SECRET"));
16883
+ set("luma", pick2("LUMA_API_KEY", "LUMA_AGENTS_API_KEY"));
16119
16884
  set("cloudflare", pick2("CLOUDFLARE_AI_GATEWAY_TOKEN", "CF_AIG_TOKEN"));
16120
16885
  const bearer = pick2("AWS_BEARER_TOKEN_BEDROCK");
16121
16886
  const accessKeyId = pick2("AWS_ACCESS_KEY_ID");
@@ -16138,7 +16903,7 @@ function keysFromEnv(env, prefix = "") {
16138
16903
 
16139
16904
  // src/text.ts
16140
16905
  import "ai";
16141
- import { z as z30 } from "zod";
16906
+ import { z as z31 } from "zod";
16142
16907
  function mergeTextProviderOptions(catalog, caller) {
16143
16908
  const merged = {};
16144
16909
  const catalogOptions = catalog.providerOptions ?? {};
@@ -16177,8 +16942,8 @@ function passthroughTextModel(model) {
16177
16942
  pricingKind: "token"
16178
16943
  };
16179
16944
  }
16180
- var perplexityCostSchema = z30.object({
16181
- perplexity: z30.object({ cost: z30.object({ totalCost: z30.number().finite().nonnegative() }) })
16945
+ var perplexityCostSchema = z31.object({
16946
+ perplexity: z31.object({ cost: z31.object({ totalCost: z31.number().finite().nonnegative() }) })
16182
16947
  });
16183
16948
  function reportedCostUsd(metadata) {
16184
16949
  const parsed = perplexityCostSchema.safeParse(metadata);
@@ -16258,7 +17023,7 @@ import {
16258
17023
  streamObject as aiStreamObject2,
16259
17024
  streamText as aiStreamText2
16260
17025
  } from "ai";
16261
- import { z as z31 } from "zod";
17026
+ import { z as z32 } from "zod";
16262
17027
 
16263
17028
  // src/generated-image.ts
16264
17029
  function toGeneratedImage(image) {
@@ -16607,10 +17372,10 @@ function textAbortOptions(sdk, context) {
16607
17372
  if (abortSignal) return { abortSignal };
16608
17373
  return {};
16609
17374
  }
16610
- var catalogId = z31.string();
16611
- var exactSize = z31.templateLiteral([z31.number(), "x", z31.number()]);
16612
- var sdkRatio = z31.templateLiteral([z31.number(), ":", z31.number()]);
16613
- var sizeTier = z31.enum(["512", "1K", "2K", "4K"]);
17375
+ var catalogId = z32.string();
17376
+ var exactSize = z32.templateLiteral([z32.number(), "x", z32.number()]);
17377
+ var sdkRatio = z32.templateLiteral([z32.number(), ":", z32.number()]);
17378
+ var sizeTier = z32.enum(["512", "1K", "2K", "4K"]);
16614
17379
  function isCatalogId(model) {
16615
17380
  return catalogId.safeParse(model).success;
16616
17381
  }
@@ -17354,6 +18119,7 @@ export {
17354
18119
  languageModel,
17355
18120
  liveCatalog,
17356
18121
  liveState,
18122
+ lumaAdapter,
17357
18123
  mergeTextProviderOptions,
17358
18124
  missingTierKeys,
17359
18125
  model3dCall,