@marlinjai/studio-mcp 0.4.0 → 0.5.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/cli.js CHANGED
@@ -95,7 +95,11 @@ function apiMessageFromBody(body, fallback) {
95
95
  if (typeof o.error === "string" && o.error.length > 0) {
96
96
  const detail = typeof o.message === "string" ? o.message : "";
97
97
  const adds = detail.length > 0 && !o.error.includes(detail) && !detail.includes(o.error);
98
- const headline = adds ? `${o.error}: ${detail}` : o.error;
98
+ let headline = adds ? `${o.error}: ${detail}` : o.error;
99
+ const hint = typeof o.hint === "string" ? o.hint.trim() : "";
100
+ if (hint.length > 0 && !headline.includes(hint)) headline = `${headline}${/[.!?]$/.test(headline) ? "" : "."} ${hint}`;
101
+ const code2 = typeof o.code === "string" ? o.code.trim() : "";
102
+ if (code2.length > 0 && !headline.includes(code2)) headline = `${headline} (code: ${code2})`;
99
103
  const quota = quotaDetail(o);
100
104
  return quota ? `${headline} (${quota})` : headline;
101
105
  }
@@ -155,6 +159,14 @@ var StudioClient = class {
155
159
  return this.json("GET", "/api/v1/projects");
156
160
  }
157
161
  // ── Generation jobs ──
162
+ /**
163
+ * One image job. `brandSlug` is optional: without it the image is generated
164
+ * with no brand. A `brandSlug` or `characterSlug` the key's company cannot
165
+ * use is refused with 404 `input_not_found` before anything is spent (since
166
+ * 2026-10-11; it used to generate without the brand). `projectId` chooses the
167
+ * project; without it the job lands in the project named after the brand, or
168
+ * in `default`.
169
+ */
158
170
  generate(body) {
159
171
  return this.json("POST", "/api/generate", { json: body });
160
172
  }
@@ -443,7 +455,124 @@ function studio3dCreditsToUsd(credits) {
443
455
  return Math.round(credits * STUDIO3D_USD_PER_CREDIT * 1e3) / 1e3;
444
456
  }
445
457
  var FAL_MESHY_RIG_USD = 0.2;
458
+ var veo = (tier, label) => {
459
+ const slug2 = tier ? `veo-3-1-${tier}` : "veo-3-1";
460
+ return {
461
+ kind: "video",
462
+ id: `route/${slug2}`,
463
+ label: `${label} (KIE, fal fallback)`,
464
+ primary: `kie/${slug2}`,
465
+ fallback: { start: `fal/${slug2}-image-to-video`, startAndEnd: `fal/${slug2}-first-last-frame` }
466
+ };
467
+ };
468
+ var image = (slug2, label, defaultTier) => ({
469
+ kind: "image",
470
+ id: `route/${slug2}`,
471
+ label: `${label} (KIE, fal fallback)`,
472
+ primary: `kie/${slug2}`,
473
+ fallback: `fal/${slug2}`,
474
+ defaultTier
475
+ });
476
+ var ROUTES = [
477
+ veo("lite", "Veo 3.1 Lite"),
478
+ veo("fast", "Veo 3.1 Fast"),
479
+ veo("", "Veo 3.1"),
480
+ {
481
+ kind: "video",
482
+ id: "route/grok-imagine",
483
+ label: "Grok Imagine (KIE, fal fallback)",
484
+ primary: "kie/grok-imagine-image-to-video",
485
+ fallback: { start: "fal/grok-imagine-image-to-video" }
486
+ },
487
+ {
488
+ kind: "video",
489
+ id: "route/grok-imagine-1-5",
490
+ label: "Grok Imagine 1.5 (KIE, fal fallback)",
491
+ primary: "kie/grok-imagine-1-5-image-to-video",
492
+ fallback: { start: "fal/grok-imagine-1-5-image-to-video" }
493
+ },
494
+ {
495
+ kind: "video",
496
+ id: "route/kling-2-5-turbo-pro",
497
+ label: "Kling 2.5 Turbo Pro (KIE, fal fallback)",
498
+ primary: "kie/kling-v2-5-turbo-pro-image-to-video",
499
+ fallback: { start: "fal/kling-v2-5-image-to-video" },
500
+ // fal needs an aspect ratio (16:9, 9:16 or 1:1) and KIE takes none, so a
501
+ // fallback would crop a start frame of another shape. The ratio comes from
502
+ // the start frame once that is built (decision 7 of the plan).
503
+ noFallbackYet: "fal renders Kling 2.5 at a set aspect ratio (16:9, 9:16 or 1:1) and KIE takes none, so a fallback could crop the start frame. Pin the fal Kling 2.5 entry to choose the ratio yourself."
504
+ },
505
+ {
506
+ kind: "video",
507
+ id: "route/kling-2-6",
508
+ label: "Kling 2.6 (KIE, fal fallback)",
509
+ primary: "kie/kling-2-6-image-to-video",
510
+ fallback: { start: "fal/kling-2-6-image-to-video" }
511
+ },
512
+ {
513
+ kind: "video",
514
+ id: "route/kling-o3",
515
+ label: "Kling O3 (KIE, fal fallback)",
516
+ primary: "kie/kling-o3-image-to-video",
517
+ // fal sells Kling O3 as tiers and takes no resolution. Its schema (read
518
+ // 2026-10-10) declares length, audio and the frames but states no output
519
+ // size, so Standard for 720p and Pro for 1080p follows Kling's own tier
520
+ // naming and is still to be confirmed by measuring a rendered clip.
521
+ fallback: {
522
+ byResolution: {
523
+ "720p": "fal/kling-o3-standard-image-to-video",
524
+ "1080p": "fal/kling-o3-pro-image-to-video"
525
+ },
526
+ label: "fal renders Kling O3 at 720p (Standard) or 1080p (Pro)"
527
+ }
528
+ },
529
+ {
530
+ kind: "video",
531
+ id: "route/seedance-2-0",
532
+ label: "Seedance 2.0 (KIE, fal fallback)",
533
+ primary: "kie/seedance-2-0-image-to-video",
534
+ fallback: { start: "fal/seedance-2-0-image-to-video" }
535
+ },
536
+ {
537
+ kind: "video",
538
+ id: "route/seedance-2-5",
539
+ label: "Seedance 2.5 (KIE, fal fallback)",
540
+ primary: "kie/seedance-2-5-image-to-video",
541
+ fallback: { start: "fal/seedance-2-5-image-to-video" }
542
+ },
543
+ {
544
+ kind: "video",
545
+ id: "route/wan-3-0",
546
+ label: "Wan 3.0 (KIE, fal fallback)",
547
+ primary: "kie/wan-3-0-image-to-video",
548
+ fallback: { start: "fal/wan-3-0-image-to-video" }
549
+ },
550
+ image("nano-banana-2", "Nano Banana 2", "1K"),
551
+ image("nano-banana-2-edit", "Nano Banana 2 Edit", "1K"),
552
+ image("nano-banana-pro", "Nano Banana Pro", "1K"),
553
+ image("nano-banana-pro-edit", "Nano Banana Pro Edit", "1K"),
554
+ image("seedream-5-lite", "Seedream 5 Lite", "2K"),
555
+ image("seedream-5-lite-edit", "Seedream 5 Lite Edit", "2K"),
556
+ image("seedream-5-pro", "Seedream 5 Pro", "1K"),
557
+ image("seedream-5-pro-edit", "Seedream 5 Pro Edit", "1K")
558
+ ];
559
+ var MODEL_ROUTES = Object.fromEntries(ROUTES.map((r) => [r.id, r]));
560
+ function getRoute(modelId) {
561
+ return modelId ? MODEL_ROUTES[modelId] : void 0;
562
+ }
446
563
  var MODEL_CATALOG = [
564
+ // LIST PRICES (`listPrice`, pricing plan of 2026-10-10). Every KIE entry
565
+ // names where the official list price of its requests is found, because KIE
566
+ // is the reseller route that sells below list and the price floor needs the
567
+ // figure. The rule: the model owner's own published price where the catalog
568
+ // records one (Google's Veo price, billed unchanged by the OpenRouter
569
+ // entries; Vertex AI's Imagen 4 Fast price), else the fal entry for the same
570
+ // upstream model. `unrecorded` means neither exists in this catalog, and the
571
+ // floor then does not apply: Seedream V4, MiniMax H3 and Gemini Omni Flash
572
+ // have no fal entry, Kling O3 on KIE does not say whether it is fal's
573
+ // Standard or Pro tier, Suno has no per-song list price, and the Claude
574
+ // entries bill per token. Entries of every other provider carry no
575
+ // reference: their route bills the list price itself.
447
576
  // ── 3D AI Studio: 3D generation ────────────────────────────────────────
448
577
  // `credits` is what the model's DEFAULT request bills (the options its
449
578
  // builder sends when a node sets none), priced by providers/studio3d/
@@ -540,6 +669,7 @@ var MODEL_CATALOG = [
540
669
  label: "Nano Banana 2 (KIE)",
541
670
  description: "Google Gemini 3.1 Flash Image via KIE. $0.04 at 1K, $0.06 at 2K, $0.09 at 4K. Kept as fallback aggregator path; default flipped to fal/nano-banana-2 in 2026-06-04 fal slice.",
542
671
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
672
+ listPrice: { source: "fal", model: "fal/nano-banana-2" },
543
673
  capabilities: { resolutions: ["1K", "2K", "4K"] }
544
674
  },
545
675
  {
@@ -552,6 +682,7 @@ var MODEL_CATALOG = [
552
682
  label: "Imagen 4 Fast (KIE)",
553
683
  description: "Google Imagen 4 Fast via KIE. No price advantage vs Vertex direct. No IP indemnity. Vertex AI lists Imagen 4 Fast at $0.02 per image (2026-09-27), which matches the estimate; the KIE price itself is still unmeasured.",
554
684
  cost: { usd: 0.02 },
685
+ listPrice: { source: "owner", usd: 0.02 },
555
686
  capabilities: { resolutions: [] }
556
687
  },
557
688
  {
@@ -561,11 +692,15 @@ var MODEL_CATALOG = [
561
692
  task: "text-to-image",
562
693
  label: "Seedream V4 (KIE)",
563
694
  description: "ByteDance Seedream V4 via KIE. Stylized output not available on Vertex. No IP indemnity.",
564
- cost: { usd: 0.0175 }
695
+ cost: { usd: 0.0175 },
696
+ listPrice: { source: "unrecorded" }
565
697
  },
566
698
  // ── KIE.ai: newer image models, the cheaper route (price feed 2026-09-23) ─
567
699
  // Billed from the credits KIE reports on each task. KIE offers no data
568
- // processing agreement, so real-person uploads belong on the fal entries.
700
+ // processing agreement. Decided 2026-09-23 (Q5 C of the routing plan,
701
+ // docs/plans/2026-09-26-kie-first-fal-fallback.md): the automatic routes try
702
+ // KIE first for everything, a recorded risk; pinning the fal entry of a model
703
+ // is how a job with a real person's photo is kept off KIE.
569
704
  {
570
705
  id: "kie/nano-banana-2-edit",
571
706
  provider: "kie",
@@ -574,6 +709,7 @@ var MODEL_CATALOG = [
574
709
  label: "Nano Banana 2 Edit (KIE)",
575
710
  description: "Nano Banana 2 instruction edit via KIE with up to 8 reference images, half the fal price: $0.04 at 1K, $0.06 at 2K, $0.09 at 4K.",
576
711
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
712
+ listPrice: { source: "fal", model: "fal/nano-banana-2-edit" },
577
713
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
578
714
  },
579
715
  {
@@ -584,6 +720,7 @@ var MODEL_CATALOG = [
584
720
  label: "Nano Banana Pro (KIE)",
585
721
  description: "Google Gemini 3 Pro Image via KIE: stronger on complex scenes, text in images and new characters than Nano Banana 2 (a relighting edit showed no difference on 2026-09-23). $0.09 at 1K or 2K, $0.12 at 4K (fal: $0.15, $0.30).",
586
722
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
723
+ listPrice: { source: "fal", model: "fal/nano-banana-pro" },
587
724
  capabilities: { resolutions: ["1K", "2K", "4K"], maxInputImages: 0 }
588
725
  },
589
726
  {
@@ -594,6 +731,7 @@ var MODEL_CATALOG = [
594
731
  label: "Nano Banana Pro Edit (KIE)",
595
732
  description: "Nano Banana Pro instruction edit via KIE with up to 8 reference images. $0.09 at 1K or 2K, $0.12 at 4K (fal: $0.15, $0.30).",
596
733
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
734
+ listPrice: { source: "fal", model: "fal/nano-banana-pro-edit" },
597
735
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
598
736
  },
599
737
  {
@@ -604,6 +742,7 @@ var MODEL_CATALOG = [
604
742
  label: "Seedream 5 Lite (KIE)",
605
743
  description: "ByteDance Seedream 5 Lite via KIE, 2K or 4K. $0.0275 per image (fal: $0.035).",
606
744
  cost: { usd: 0.0275 },
745
+ listPrice: { source: "fal", model: "fal/seedream-5-lite" },
607
746
  capabilities: { resolutions: ["2K", "4K"], maxInputImages: 0 }
608
747
  },
609
748
  {
@@ -614,6 +753,7 @@ var MODEL_CATALOG = [
614
753
  label: "Seedream 5 Lite Edit (KIE)",
615
754
  description: "ByteDance Seedream 5 Lite image-to-image via KIE, up to 10 reference images, 2K or 4K. $0.0275 per image (fal: $0.035).",
616
755
  cost: { usd: 0.0275 },
756
+ listPrice: { source: "fal", model: "fal/seedream-5-lite-edit" },
617
757
  capabilities: { resolutions: ["2K", "4K"], needsImageInput: true, maxInputImages: 10 }
618
758
  },
619
759
  {
@@ -624,6 +764,7 @@ var MODEL_CATALOG = [
624
764
  label: "Seedream 5 Pro (KIE)",
625
765
  description: "ByteDance Seedream 5 Pro via KIE, 1K or 2K. $0.035 at 1K, $0.07 at 2K (fal: $0.0675 to $0.135).",
626
766
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
767
+ listPrice: { source: "fal", model: "fal/seedream-5-pro" },
627
768
  capabilities: { resolutions: ["1K", "2K"], maxInputImages: 0 }
628
769
  },
629
770
  {
@@ -634,6 +775,7 @@ var MODEL_CATALOG = [
634
775
  label: "Seedream 5 Pro Edit (KIE)",
635
776
  description: "ByteDance Seedream 5 Pro image-to-image via KIE, up to 10 reference images, 1K or 2K. $0.035 at 1K, $0.07 at 2K, plus $0.0025 per extra reference.",
636
777
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
778
+ listPrice: { source: "fal", model: "fal/seedream-5-pro-edit" },
637
779
  capabilities: { resolutions: ["1K", "2K"], needsImageInput: true, maxInputImages: 10 }
638
780
  },
639
781
  // ── fal.ai: image gen ─────────────────────────────────────────────────
@@ -928,8 +1070,11 @@ var MODEL_CATALOG = [
928
1070
  // ── KIE.ai video: the cheaper route to the same upstream models ──────────
929
1071
  // Async via KIE's Veo and createTask APIs (providers/kie-video.ts). 20 to 85%
930
1072
  // below fal (KIE price feed, 2026-09-23). KIE offers no data processing
931
- // agreement, so real-person uploads belong on fal. Anchors are the model's
932
- // default clip at 720p (Kling: its fixed size). KIE docs vetted 2026-09-23.
1073
+ // agreement. Decided 2026-09-23 (Q5 C of the routing plan): the automatic
1074
+ // routes try KIE first for everything, a recorded risk; pinning the fal
1075
+ // entry of a model is how a job with a real person's photo is kept off KIE.
1076
+ // Anchors are the model's default clip at 720p (Kling: its fixed size). KIE
1077
+ // docs vetted 2026-09-23.
933
1078
  {
934
1079
  id: "kie/veo-3-1-lite",
935
1080
  provider: "kie",
@@ -938,6 +1083,7 @@ var MODEL_CATALOG = [
938
1083
  label: "Veo 3.1 Lite (KIE)",
939
1084
  description: "Google Veo 3.1 Lite via KIE. A start frame, or a start and an end frame; 4, 6 or 8 s; 720p, 1080p or 4K; always with audio. Accepted a minted character's photoreal stills on 2026-09-23. Priced per clip: $0.15 at 720p, $0.175 at 1080p, $0.75 at 4K (fal: $0.24 silent, $0.40 with audio, for 8 s at 720p). KIE's Auto aspect letterboxes, so pick 16:9 or 9:16.",
940
1085
  cost: { usd: 0.15 },
1086
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-lite" },
941
1087
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
942
1088
  },
943
1089
  {
@@ -948,6 +1094,7 @@ var MODEL_CATALOG = [
948
1094
  label: "Veo 3.1 Fast (KIE)",
949
1095
  description: "Google Veo 3.1 Fast via KIE. A start frame, or a start and an end frame; 4, 6 or 8 s; 720p, 1080p or 4K; always with audio. Accepted a minted character's photoreal stills on 2026-09-23. Priced per clip: $0.30 at 720p, $0.325 at 1080p, $0.90 at 4K (fal: $1.20 with audio for 8 s). Pick 16:9 or 9:16.",
950
1096
  cost: { usd: 0.3 },
1097
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-fast" },
951
1098
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
952
1099
  },
953
1100
  {
@@ -958,6 +1105,7 @@ var MODEL_CATALOG = [
958
1105
  label: "Veo 3.1 (KIE)",
959
1106
  description: "Google Veo 3.1 (quality tier) via KIE. A start frame, or a start and an end frame; 4, 6 or 8 s; 720p, 1080p or 4K; always with audio. Same upstream model as the tested Lite and Fast tiers. Priced per clip: $1.25 at 720p, $1.275 at 1080p, $1.85 at 4K (fal: $3.20 with audio).",
960
1107
  cost: { usd: 1.25 },
1108
+ listPrice: { source: "owner", model: "openrouter/veo-3-1" },
961
1109
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
962
1110
  },
963
1111
  // ── OpenRouter video: Google Veo 3.1 at Google's own prices ───────────────
@@ -1004,6 +1152,7 @@ var MODEL_CATALOG = [
1004
1152
  label: "Grok Imagine (KIE)",
1005
1153
  description: "xAI Grok Imagine via KIE. A start frame, 6 to 30 s, 480p, 720p or 1080p, with audio. Accepted a minted character's photoreal stills on 2026-09-23. $0.012 per second at 480p, $0.0225 at 720p, $0.04 at 1080p (fal: $0.07 at 720p). No end frame.",
1006
1154
  cost: { usd: 0.135 },
1155
+ listPrice: { source: "fal", model: "fal/grok-imagine-image-to-video" },
1007
1156
  capabilities: { needsImageInput: true, maxInputImages: 1 }
1008
1157
  },
1009
1158
  {
@@ -1014,6 +1163,7 @@ var MODEL_CATALOG = [
1014
1163
  label: "Grok Imagine 1.5 (KIE, preview)",
1015
1164
  description: "xAI Grok Imagine 1.5 preview via KIE. A start frame, 1 to 15 s, 480p or 720p. Not yet tested with a minted character. $0.012 per second at 480p, $0.0225 at 720p (fal: $0.14 at 720p). No end frame.",
1016
1165
  cost: { usd: 0.18 },
1166
+ listPrice: { source: "fal", model: "fal/grok-imagine-1-5-image-to-video" },
1017
1167
  capabilities: { needsImageInput: true, maxInputImages: 1 }
1018
1168
  },
1019
1169
  {
@@ -1024,6 +1174,7 @@ var MODEL_CATALOG = [
1024
1174
  label: "Kling 2.5 Turbo Pro (KIE)",
1025
1175
  description: "Kuaishou Kling 2.5 Turbo Pro via KIE. A start frame and an optional end frame, 5 or 10 s, fixed size. The fal route accepted minted characters; this KIE route is not yet tested with a minted character. $0.21 for 5 s, $0.42 for 10 s (fal: $0.35 for 5 s).",
1026
1176
  cost: { usd: 0.21 },
1177
+ listPrice: { source: "fal", model: "fal/kling-v2-5-image-to-video" },
1027
1178
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1028
1179
  },
1029
1180
  {
@@ -1034,6 +1185,7 @@ var MODEL_CATALOG = [
1034
1185
  label: "Kling 2.6 (KIE)",
1035
1186
  description: "Kuaishou Kling 2.6 via KIE. A start frame, 5 or 10 s, fixed size, silent unless the job sets generateAudio. Not yet tested with a minted character. $0.275 for 5 s silent, $0.55 with sound, 10 s double (fal: $0.35 or $0.70).",
1036
1187
  cost: { usd: 0.275 },
1188
+ listPrice: { source: "fal", model: "fal/kling-2-6-image-to-video" },
1037
1189
  capabilities: { needsImageInput: true, maxInputImages: 1 }
1038
1190
  },
1039
1191
  {
@@ -1045,6 +1197,7 @@ var MODEL_CATALOG = [
1045
1197
  refusesPhotorealPeople: true,
1046
1198
  description: "ByteDance Seedance 2.0 via KIE. A start frame and an optional end frame, 4 to 15 s, 480p to 4K. Seedance 2.0 refuses photoreal people, generated ones included: on fal it refused them in every mode (observed 2026-09-23). The refusal comes from the moderation of the model itself, so this route carries the same caution; the KIE route itself has not been run with a person. $0.095 per second at 480p, $0.205 at 720p, $0.51 at 1080p, $1.04 at 4K (fal: about $0.30 at 720p).",
1047
1199
  cost: { usd: 1.025 },
1200
+ listPrice: { source: "fal", model: "fal/seedance-2-0-image-to-video" },
1048
1201
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1049
1202
  },
1050
1203
  {
@@ -1056,6 +1209,7 @@ var MODEL_CATALOG = [
1056
1209
  refusesPhotorealPeople: true,
1057
1210
  description: "ByteDance Seedance 2.5 via KIE. A start frame and an optional end frame, 4 to 30 s, 480p to 1080p. Seedance 2.5 refuses photoreal people, generated ones included: on fal it refused them in every mode (observed 2026-08-21 and 2026-09-23). The refusal comes from the moderation of the model itself, so this route carries the same caution; the KIE route itself has not been run with a person. $0.14 per second at 480p, $0.315 at 720p, $0.79 at 1080p.",
1058
1211
  cost: { usd: 1.575 },
1212
+ listPrice: { source: "fal", model: "fal/seedance-2-5-image-to-video" },
1059
1213
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1060
1214
  },
1061
1215
  {
@@ -1066,6 +1220,7 @@ var MODEL_CATALOG = [
1066
1220
  label: "Wan 3.0 (KIE)",
1067
1221
  description: "Alibaba Wan 3.0 via KIE (hosted only, no downloadable weights). A start frame and an optional end frame, 2 to 30 s, 480p, 720p or 1080p, audio included. Accepted a minted character's photoreal stills on 2026-09-24. $0.04 per second at 480p, $0.08 at 720p, $0.16 at 1080p (fal and Alibaba: $0.10 at 720p).",
1068
1222
  cost: { usd: 0.4 },
1223
+ listPrice: { source: "fal", model: "fal/wan-3-0-image-to-video" },
1069
1224
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1070
1225
  },
1071
1226
  {
@@ -1076,6 +1231,7 @@ var MODEL_CATALOG = [
1076
1231
  label: "MiniMax H3 (KIE)",
1077
1232
  description: "MiniMax H3 via KIE. A start frame and an optional end frame, 4 to 15 s, 768p (the 720p setting) or 2K (the 1080p setting). Accepted a minted character's photoreal stills on 2026-09-24. Its open weights exclude the EU by license; this is KIE's hosted service. $0.04 per second at 768p, $0.065 at 2K.",
1078
1233
  cost: { usd: 0.24 },
1234
+ listPrice: { source: "unrecorded" },
1079
1235
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1080
1236
  },
1081
1237
  {
@@ -1086,6 +1242,7 @@ var MODEL_CATALOG = [
1086
1242
  label: "Gemini Omni 1.1 Flash (KIE)",
1087
1243
  description: "Google Gemini Omni 1.1 Flash via KIE, first-and-last-frame mode. A start frame and an optional end frame, 4, 6, 8 or 10 s, 720p to 4K, 16:9 or 9:16. KIE refuses registered characters in this mode, so identity comes from the frames alone. Accepted a minted character's photoreal stills on 2026-09-24 (set 9:16 for a portrait still: it defaults to 16:9 and crops). $0.315 for 4 s up to $0.63 for 10 s at 720p or 1080p, $0.735 to $1.05 at 4K. No fal equivalent.",
1088
1244
  cost: { usd: 0.525 },
1245
+ listPrice: { source: "unrecorded" },
1089
1246
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1090
1247
  },
1091
1248
  {
@@ -1096,6 +1253,7 @@ var MODEL_CATALOG = [
1096
1253
  label: "Kling O3 (KIE)",
1097
1254
  description: "Kuaishou Kling O3 (KIE calls it Kling 3.0 Omni) via KIE. A start frame and an optional end frame, 3 to 15 s, 720p to 4K in the start frame's shape, silent unless the job sets generateAudio. Accepted a minted character's photoreal stills on 2026-09-24. $0.07 per second at 720p, $0.09 at 1080p, $0.335 at 4K; with sound $0.10 and $0.135.",
1098
1255
  cost: { usd: 0.35 },
1256
+ listPrice: { source: "unrecorded" },
1099
1257
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
1100
1258
  },
1101
1259
  {
@@ -1414,6 +1572,7 @@ var MODEL_CATALOG = [
1414
1572
  label: "Suno V5 (KIE)",
1415
1573
  description: "Suno V5 music generation via KIE. Text-to-music with or without lyrics; ~4 min cap per generation. Default text-to-audio. No IP indemnity (KIE aggregator). KIE's site now features Suno v5.5 and no longer lists a V5 price (checked 2026-09-27), so the $0.04 is an unverified hint.",
1416
1574
  cost: { usd: 0.04 },
1575
+ listPrice: { source: "unrecorded" },
1417
1576
  isDefault: true
1418
1577
  },
1419
1578
  // ── audio: voice via ElevenLabs (B2) ───────────────────────────────────
@@ -1462,6 +1621,7 @@ var MODEL_CATALOG = [
1462
1621
  label: "Claude Sonnet 4.5 (KIE)",
1463
1622
  description: "Anthropic Claude Sonnet 4.5 via KIE (Anthropic Messages API at a lower rate). Default LLM director: prompt improvement + shot descriptions. Cost billed per token; catalog usd is a hint.",
1464
1623
  cost: { usd: 0.01 },
1624
+ listPrice: { source: "unrecorded" },
1465
1625
  isDefault: true
1466
1626
  },
1467
1627
  {
@@ -1471,7 +1631,8 @@ var MODEL_CATALOG = [
1471
1631
  task: "text-generation",
1472
1632
  label: "Claude Opus 4.5 (KIE)",
1473
1633
  description: "Anthropic Claude Opus 4.5 via KIE. Higher quality, higher cost; use for harder director reasoning. Cost billed per token; catalog usd is a hint.",
1474
- cost: { usd: 0.05 }
1634
+ cost: { usd: 0.05 },
1635
+ listPrice: { source: "unrecorded" }
1475
1636
  },
1476
1637
  {
1477
1638
  id: "openrouter/claude-sonnet-4-6",
@@ -1536,8 +1697,26 @@ var MODEL_CATALOG = [
1536
1697
  description: "Vertex AI direct, newest flash variant. Global region. Preview status."
1537
1698
  }
1538
1699
  ];
1700
+ var routeEntries = /* @__PURE__ */ new Map();
1701
+ function routeEntry(id) {
1702
+ const cached = routeEntries.get(id);
1703
+ if (cached) return cached;
1704
+ const route = getRoute(id);
1705
+ const primary = route ? MODEL_CATALOG.find((m) => m.id === route.primary) : void 0;
1706
+ if (!route || !primary) return void 0;
1707
+ const entry = {
1708
+ ...primary,
1709
+ // A route is never the default of its task (that is slice 3's switch).
1710
+ isDefault: void 0,
1711
+ id: route.id,
1712
+ label: route.label,
1713
+ description: `Runs on KIE first and, when KIE fails on its side, again on fal with the same settings. ${primary.description ?? ""}`.trim()
1714
+ };
1715
+ routeEntries.set(id, entry);
1716
+ return entry;
1717
+ }
1539
1718
  function tryGetModel(id) {
1540
- return MODEL_CATALOG.find((m) => m.id === id);
1719
+ return MODEL_CATALOG.find((m) => m.id === id) ?? routeEntry(id);
1541
1720
  }
1542
1721
  var ROWS = [
1543
1722
  [0, "Idle", "DailyActions", "Idle"],
@@ -2465,7 +2644,10 @@ var VIDEO_UNSUPPORTED_PARAMS = {
2465
2644
  ...Object.fromEntries(KLING_O3_I2V_ENDPOINTS.map((e) => [e, ["resolution", "aspectRatio", "seed", "negativePrompt"]])),
2466
2645
  ...Object.fromEntries(LTX_25_I2V_ENDPOINTS.map((e) => [e, ["seed", "negativePrompt"]]))
2467
2646
  };
2468
- var perSecond = (rates) => (s, r) => (rates[r] ?? 0) * s;
2647
+ var perSecond = (rates) => (s, r) => {
2648
+ const rate = rates[r];
2649
+ return rate === void 0 ? void 0 : rate * s;
2650
+ };
2469
2651
  function veoSpec(model, label, perClip) {
2470
2652
  return {
2471
2653
  family: "veo",
@@ -2479,7 +2661,7 @@ function veoSpec(model, label, perClip) {
2479
2661
  audioSwitch: false,
2480
2662
  defaultAudio: true,
2481
2663
  // KIE prices Veo per clip whatever its length, and always renders audio.
2482
- price: (_s, r) => perClip[r] ?? 0,
2664
+ price: (_s, r) => perClip[r],
2483
2665
  build: (i) => ({
2484
2666
  prompt: i.prompt,
2485
2667
  model,
@@ -2686,7 +2868,7 @@ var KIE_VIDEO_SPECS = {
2686
2868
  audioSwitch: false,
2687
2869
  defaultAudio: false,
2688
2870
  // Priced per clip by length; 4K carries a surcharge.
2689
- price: (s, r) => (r === "4k" ? { 4: 0.735, 6: 0.84, 8: 0.945, 10: 1.05 } : { 4: 0.315, 6: 0.42, 8: 0.525, 10: 0.63 })[s] ?? 0,
2871
+ price: (s, r) => (r === "4k" ? { 4: 0.735, 6: 0.84, 8: 0.945, 10: 1.05 } : { 4: 0.315, 6: 0.42, 8: 0.525, 10: 0.63 })[s],
2690
2872
  // The first-and-last-frame mode: KIE refuses it combined with registered
2691
2873
  // characters, reference images, audio or video, so none are sent.
2692
2874
  build: (i) => ({
@@ -2712,7 +2894,7 @@ var KIE_VIDEO_SPECS = {
2712
2894
  audioSwitch: true,
2713
2895
  defaultAudio: false,
2714
2896
  // With-audio rates from KIE's price feed, the higher of its two listings.
2715
- price: (s, r, audio) => ((audio ? { "720p": 0.1, "1080p": 0.135, "4k": 0.335 } : { "720p": 0.07, "1080p": 0.09, "4k": 0.335 })[r] ?? 0) * s,
2897
+ price: (s, r, audio) => perSecond(audio ? { "720p": 0.1, "1080p": 0.135, "4k": 0.335 } : { "720p": 0.07, "1080p": 0.09, "4k": 0.335 })(s, r),
2716
2898
  build: (i) => ({
2717
2899
  prompt: i.prompt,
2718
2900
  image_urls: i.end ? [i.start, i.end] : [i.start],
@@ -3154,7 +3336,124 @@ function imageResolutionTier(value) {
3154
3336
  return null;
3155
3337
  }
3156
3338
  }
3339
+ var veo2 = (tier, label) => {
3340
+ const slug2 = tier ? `veo-3-1-${tier}` : "veo-3-1";
3341
+ return {
3342
+ kind: "video",
3343
+ id: `route/${slug2}`,
3344
+ label: `${label} (KIE, fal fallback)`,
3345
+ primary: `kie/${slug2}`,
3346
+ fallback: { start: `fal/${slug2}-image-to-video`, startAndEnd: `fal/${slug2}-first-last-frame` }
3347
+ };
3348
+ };
3349
+ var image2 = (slug2, label, defaultTier) => ({
3350
+ kind: "image",
3351
+ id: `route/${slug2}`,
3352
+ label: `${label} (KIE, fal fallback)`,
3353
+ primary: `kie/${slug2}`,
3354
+ fallback: `fal/${slug2}`,
3355
+ defaultTier
3356
+ });
3357
+ var ROUTES2 = [
3358
+ veo2("lite", "Veo 3.1 Lite"),
3359
+ veo2("fast", "Veo 3.1 Fast"),
3360
+ veo2("", "Veo 3.1"),
3361
+ {
3362
+ kind: "video",
3363
+ id: "route/grok-imagine",
3364
+ label: "Grok Imagine (KIE, fal fallback)",
3365
+ primary: "kie/grok-imagine-image-to-video",
3366
+ fallback: { start: "fal/grok-imagine-image-to-video" }
3367
+ },
3368
+ {
3369
+ kind: "video",
3370
+ id: "route/grok-imagine-1-5",
3371
+ label: "Grok Imagine 1.5 (KIE, fal fallback)",
3372
+ primary: "kie/grok-imagine-1-5-image-to-video",
3373
+ fallback: { start: "fal/grok-imagine-1-5-image-to-video" }
3374
+ },
3375
+ {
3376
+ kind: "video",
3377
+ id: "route/kling-2-5-turbo-pro",
3378
+ label: "Kling 2.5 Turbo Pro (KIE, fal fallback)",
3379
+ primary: "kie/kling-v2-5-turbo-pro-image-to-video",
3380
+ fallback: { start: "fal/kling-v2-5-image-to-video" },
3381
+ // fal needs an aspect ratio (16:9, 9:16 or 1:1) and KIE takes none, so a
3382
+ // fallback would crop a start frame of another shape. The ratio comes from
3383
+ // the start frame once that is built (decision 7 of the plan).
3384
+ noFallbackYet: "fal renders Kling 2.5 at a set aspect ratio (16:9, 9:16 or 1:1) and KIE takes none, so a fallback could crop the start frame. Pin the fal Kling 2.5 entry to choose the ratio yourself."
3385
+ },
3386
+ {
3387
+ kind: "video",
3388
+ id: "route/kling-2-6",
3389
+ label: "Kling 2.6 (KIE, fal fallback)",
3390
+ primary: "kie/kling-2-6-image-to-video",
3391
+ fallback: { start: "fal/kling-2-6-image-to-video" }
3392
+ },
3393
+ {
3394
+ kind: "video",
3395
+ id: "route/kling-o3",
3396
+ label: "Kling O3 (KIE, fal fallback)",
3397
+ primary: "kie/kling-o3-image-to-video",
3398
+ // fal sells Kling O3 as tiers and takes no resolution. Its schema (read
3399
+ // 2026-10-10) declares length, audio and the frames but states no output
3400
+ // size, so Standard for 720p and Pro for 1080p follows Kling's own tier
3401
+ // naming and is still to be confirmed by measuring a rendered clip.
3402
+ fallback: {
3403
+ byResolution: {
3404
+ "720p": "fal/kling-o3-standard-image-to-video",
3405
+ "1080p": "fal/kling-o3-pro-image-to-video"
3406
+ },
3407
+ label: "fal renders Kling O3 at 720p (Standard) or 1080p (Pro)"
3408
+ }
3409
+ },
3410
+ {
3411
+ kind: "video",
3412
+ id: "route/seedance-2-0",
3413
+ label: "Seedance 2.0 (KIE, fal fallback)",
3414
+ primary: "kie/seedance-2-0-image-to-video",
3415
+ fallback: { start: "fal/seedance-2-0-image-to-video" }
3416
+ },
3417
+ {
3418
+ kind: "video",
3419
+ id: "route/seedance-2-5",
3420
+ label: "Seedance 2.5 (KIE, fal fallback)",
3421
+ primary: "kie/seedance-2-5-image-to-video",
3422
+ fallback: { start: "fal/seedance-2-5-image-to-video" }
3423
+ },
3424
+ {
3425
+ kind: "video",
3426
+ id: "route/wan-3-0",
3427
+ label: "Wan 3.0 (KIE, fal fallback)",
3428
+ primary: "kie/wan-3-0-image-to-video",
3429
+ fallback: { start: "fal/wan-3-0-image-to-video" }
3430
+ },
3431
+ image2("nano-banana-2", "Nano Banana 2", "1K"),
3432
+ image2("nano-banana-2-edit", "Nano Banana 2 Edit", "1K"),
3433
+ image2("nano-banana-pro", "Nano Banana Pro", "1K"),
3434
+ image2("nano-banana-pro-edit", "Nano Banana Pro Edit", "1K"),
3435
+ image2("seedream-5-lite", "Seedream 5 Lite", "2K"),
3436
+ image2("seedream-5-lite-edit", "Seedream 5 Lite Edit", "2K"),
3437
+ image2("seedream-5-pro", "Seedream 5 Pro", "1K"),
3438
+ image2("seedream-5-pro-edit", "Seedream 5 Pro Edit", "1K")
3439
+ ];
3440
+ var MODEL_ROUTES2 = Object.fromEntries(ROUTES2.map((r) => [r.id, r]));
3441
+ function getRoute2(modelId) {
3442
+ return modelId ? MODEL_ROUTES2[modelId] : void 0;
3443
+ }
3157
3444
  var MODEL_CATALOG2 = [
3445
+ // LIST PRICES (`listPrice`, pricing plan of 2026-10-10). Every KIE entry
3446
+ // names where the official list price of its requests is found, because KIE
3447
+ // is the reseller route that sells below list and the price floor needs the
3448
+ // figure. The rule: the model owner's own published price where the catalog
3449
+ // records one (Google's Veo price, billed unchanged by the OpenRouter
3450
+ // entries; Vertex AI's Imagen 4 Fast price), else the fal entry for the same
3451
+ // upstream model. `unrecorded` means neither exists in this catalog, and the
3452
+ // floor then does not apply: Seedream V4, MiniMax H3 and Gemini Omni Flash
3453
+ // have no fal entry, Kling O3 on KIE does not say whether it is fal's
3454
+ // Standard or Pro tier, Suno has no per-song list price, and the Claude
3455
+ // entries bill per token. Entries of every other provider carry no
3456
+ // reference: their route bills the list price itself.
3158
3457
  // ── 3D AI Studio: 3D generation ────────────────────────────────────────
3159
3458
  // `credits` is what the model's DEFAULT request bills (the options its
3160
3459
  // builder sends when a node sets none), priced by providers/studio3d/
@@ -3251,6 +3550,7 @@ var MODEL_CATALOG2 = [
3251
3550
  label: "Nano Banana 2 (KIE)",
3252
3551
  description: "Google Gemini 3.1 Flash Image via KIE. $0.04 at 1K, $0.06 at 2K, $0.09 at 4K. Kept as fallback aggregator path; default flipped to fal/nano-banana-2 in 2026-06-04 fal slice.",
3253
3552
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
3553
+ listPrice: { source: "fal", model: "fal/nano-banana-2" },
3254
3554
  capabilities: { resolutions: ["1K", "2K", "4K"] }
3255
3555
  },
3256
3556
  {
@@ -3263,6 +3563,7 @@ var MODEL_CATALOG2 = [
3263
3563
  label: "Imagen 4 Fast (KIE)",
3264
3564
  description: "Google Imagen 4 Fast via KIE. No price advantage vs Vertex direct. No IP indemnity. Vertex AI lists Imagen 4 Fast at $0.02 per image (2026-09-27), which matches the estimate; the KIE price itself is still unmeasured.",
3265
3565
  cost: { usd: 0.02 },
3566
+ listPrice: { source: "owner", usd: 0.02 },
3266
3567
  capabilities: { resolutions: [] }
3267
3568
  },
3268
3569
  {
@@ -3272,11 +3573,15 @@ var MODEL_CATALOG2 = [
3272
3573
  task: "text-to-image",
3273
3574
  label: "Seedream V4 (KIE)",
3274
3575
  description: "ByteDance Seedream V4 via KIE. Stylized output not available on Vertex. No IP indemnity.",
3275
- cost: { usd: 0.0175 }
3576
+ cost: { usd: 0.0175 },
3577
+ listPrice: { source: "unrecorded" }
3276
3578
  },
3277
3579
  // ── KIE.ai: newer image models, the cheaper route (price feed 2026-09-23) ─
3278
3580
  // Billed from the credits KIE reports on each task. KIE offers no data
3279
- // processing agreement, so real-person uploads belong on the fal entries.
3581
+ // processing agreement. Decided 2026-09-23 (Q5 C of the routing plan,
3582
+ // docs/plans/2026-09-26-kie-first-fal-fallback.md): the automatic routes try
3583
+ // KIE first for everything, a recorded risk; pinning the fal entry of a model
3584
+ // is how a job with a real person's photo is kept off KIE.
3280
3585
  {
3281
3586
  id: "kie/nano-banana-2-edit",
3282
3587
  provider: "kie",
@@ -3285,6 +3590,7 @@ var MODEL_CATALOG2 = [
3285
3590
  label: "Nano Banana 2 Edit (KIE)",
3286
3591
  description: "Nano Banana 2 instruction edit via KIE with up to 8 reference images, half the fal price: $0.04 at 1K, $0.06 at 2K, $0.09 at 4K.",
3287
3592
  cost: { usd: 0.04, usdByResolution: { "1K": 0.04, "2K": 0.06, "4K": 0.09 } },
3593
+ listPrice: { source: "fal", model: "fal/nano-banana-2-edit" },
3288
3594
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
3289
3595
  },
3290
3596
  {
@@ -3295,6 +3601,7 @@ var MODEL_CATALOG2 = [
3295
3601
  label: "Nano Banana Pro (KIE)",
3296
3602
  description: "Google Gemini 3 Pro Image via KIE: stronger on complex scenes, text in images and new characters than Nano Banana 2 (a relighting edit showed no difference on 2026-09-23). $0.09 at 1K or 2K, $0.12 at 4K (fal: $0.15, $0.30).",
3297
3603
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
3604
+ listPrice: { source: "fal", model: "fal/nano-banana-pro" },
3298
3605
  capabilities: { resolutions: ["1K", "2K", "4K"], maxInputImages: 0 }
3299
3606
  },
3300
3607
  {
@@ -3305,6 +3612,7 @@ var MODEL_CATALOG2 = [
3305
3612
  label: "Nano Banana Pro Edit (KIE)",
3306
3613
  description: "Nano Banana Pro instruction edit via KIE with up to 8 reference images. $0.09 at 1K or 2K, $0.12 at 4K (fal: $0.15, $0.30).",
3307
3614
  cost: { usd: 0.09, usdByResolution: { "1K": 0.09, "2K": 0.09, "4K": 0.12 } },
3615
+ listPrice: { source: "fal", model: "fal/nano-banana-pro-edit" },
3308
3616
  capabilities: { resolutions: ["1K", "2K", "4K"], needsImageInput: true, maxInputImages: 8 }
3309
3617
  },
3310
3618
  {
@@ -3315,6 +3623,7 @@ var MODEL_CATALOG2 = [
3315
3623
  label: "Seedream 5 Lite (KIE)",
3316
3624
  description: "ByteDance Seedream 5 Lite via KIE, 2K or 4K. $0.0275 per image (fal: $0.035).",
3317
3625
  cost: { usd: 0.0275 },
3626
+ listPrice: { source: "fal", model: "fal/seedream-5-lite" },
3318
3627
  capabilities: { resolutions: ["2K", "4K"], maxInputImages: 0 }
3319
3628
  },
3320
3629
  {
@@ -3325,6 +3634,7 @@ var MODEL_CATALOG2 = [
3325
3634
  label: "Seedream 5 Lite Edit (KIE)",
3326
3635
  description: "ByteDance Seedream 5 Lite image-to-image via KIE, up to 10 reference images, 2K or 4K. $0.0275 per image (fal: $0.035).",
3327
3636
  cost: { usd: 0.0275 },
3637
+ listPrice: { source: "fal", model: "fal/seedream-5-lite-edit" },
3328
3638
  capabilities: { resolutions: ["2K", "4K"], needsImageInput: true, maxInputImages: 10 }
3329
3639
  },
3330
3640
  {
@@ -3335,6 +3645,7 @@ var MODEL_CATALOG2 = [
3335
3645
  label: "Seedream 5 Pro (KIE)",
3336
3646
  description: "ByteDance Seedream 5 Pro via KIE, 1K or 2K. $0.035 at 1K, $0.07 at 2K (fal: $0.0675 to $0.135).",
3337
3647
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
3648
+ listPrice: { source: "fal", model: "fal/seedream-5-pro" },
3338
3649
  capabilities: { resolutions: ["1K", "2K"], maxInputImages: 0 }
3339
3650
  },
3340
3651
  {
@@ -3345,6 +3656,7 @@ var MODEL_CATALOG2 = [
3345
3656
  label: "Seedream 5 Pro Edit (KIE)",
3346
3657
  description: "ByteDance Seedream 5 Pro image-to-image via KIE, up to 10 reference images, 1K or 2K. $0.035 at 1K, $0.07 at 2K, plus $0.0025 per extra reference.",
3347
3658
  cost: { usd: 0.035, usdByResolution: { "1K": 0.035, "2K": 0.07 } },
3659
+ listPrice: { source: "fal", model: "fal/seedream-5-pro-edit" },
3348
3660
  capabilities: { resolutions: ["1K", "2K"], needsImageInput: true, maxInputImages: 10 }
3349
3661
  },
3350
3662
  // ── fal.ai: image gen ─────────────────────────────────────────────────
@@ -3639,8 +3951,11 @@ var MODEL_CATALOG2 = [
3639
3951
  // ── KIE.ai video: the cheaper route to the same upstream models ──────────
3640
3952
  // Async via KIE's Veo and createTask APIs (providers/kie-video.ts). 20 to 85%
3641
3953
  // below fal (KIE price feed, 2026-09-23). KIE offers no data processing
3642
- // agreement, so real-person uploads belong on fal. Anchors are the model's
3643
- // default clip at 720p (Kling: its fixed size). KIE docs vetted 2026-09-23.
3954
+ // agreement. Decided 2026-09-23 (Q5 C of the routing plan): the automatic
3955
+ // routes try KIE first for everything, a recorded risk; pinning the fal
3956
+ // entry of a model is how a job with a real person's photo is kept off KIE.
3957
+ // Anchors are the model's default clip at 720p (Kling: its fixed size). KIE
3958
+ // docs vetted 2026-09-23.
3644
3959
  {
3645
3960
  id: "kie/veo-3-1-lite",
3646
3961
  provider: "kie",
@@ -3649,6 +3964,7 @@ var MODEL_CATALOG2 = [
3649
3964
  label: "Veo 3.1 Lite (KIE)",
3650
3965
  description: "Google Veo 3.1 Lite via KIE. A start frame, or a start and an end frame; 4, 6 or 8 s; 720p, 1080p or 4K; always with audio. Accepted a minted character's photoreal stills on 2026-09-23. Priced per clip: $0.15 at 720p, $0.175 at 1080p, $0.75 at 4K (fal: $0.24 silent, $0.40 with audio, for 8 s at 720p). KIE's Auto aspect letterboxes, so pick 16:9 or 9:16.",
3651
3966
  cost: { usd: 0.15 },
3967
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-lite" },
3652
3968
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3653
3969
  },
3654
3970
  {
@@ -3659,6 +3975,7 @@ var MODEL_CATALOG2 = [
3659
3975
  label: "Veo 3.1 Fast (KIE)",
3660
3976
  description: "Google Veo 3.1 Fast via KIE. A start frame, or a start and an end frame; 4, 6 or 8 s; 720p, 1080p or 4K; always with audio. Accepted a minted character's photoreal stills on 2026-09-23. Priced per clip: $0.30 at 720p, $0.325 at 1080p, $0.90 at 4K (fal: $1.20 with audio for 8 s). Pick 16:9 or 9:16.",
3661
3977
  cost: { usd: 0.3 },
3978
+ listPrice: { source: "owner", model: "openrouter/veo-3-1-fast" },
3662
3979
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3663
3980
  },
3664
3981
  {
@@ -3669,6 +3986,7 @@ var MODEL_CATALOG2 = [
3669
3986
  label: "Veo 3.1 (KIE)",
3670
3987
  description: "Google Veo 3.1 (quality tier) via KIE. A start frame, or a start and an end frame; 4, 6 or 8 s; 720p, 1080p or 4K; always with audio. Same upstream model as the tested Lite and Fast tiers. Priced per clip: $1.25 at 720p, $1.275 at 1080p, $1.85 at 4K (fal: $3.20 with audio).",
3671
3988
  cost: { usd: 1.25 },
3989
+ listPrice: { source: "owner", model: "openrouter/veo-3-1" },
3672
3990
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3673
3991
  },
3674
3992
  // ── OpenRouter video: Google Veo 3.1 at Google's own prices ───────────────
@@ -3715,6 +4033,7 @@ var MODEL_CATALOG2 = [
3715
4033
  label: "Grok Imagine (KIE)",
3716
4034
  description: "xAI Grok Imagine via KIE. A start frame, 6 to 30 s, 480p, 720p or 1080p, with audio. Accepted a minted character's photoreal stills on 2026-09-23. $0.012 per second at 480p, $0.0225 at 720p, $0.04 at 1080p (fal: $0.07 at 720p). No end frame.",
3717
4035
  cost: { usd: 0.135 },
4036
+ listPrice: { source: "fal", model: "fal/grok-imagine-image-to-video" },
3718
4037
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3719
4038
  },
3720
4039
  {
@@ -3725,6 +4044,7 @@ var MODEL_CATALOG2 = [
3725
4044
  label: "Grok Imagine 1.5 (KIE, preview)",
3726
4045
  description: "xAI Grok Imagine 1.5 preview via KIE. A start frame, 1 to 15 s, 480p or 720p. Not yet tested with a minted character. $0.012 per second at 480p, $0.0225 at 720p (fal: $0.14 at 720p). No end frame.",
3727
4046
  cost: { usd: 0.18 },
4047
+ listPrice: { source: "fal", model: "fal/grok-imagine-1-5-image-to-video" },
3728
4048
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3729
4049
  },
3730
4050
  {
@@ -3735,6 +4055,7 @@ var MODEL_CATALOG2 = [
3735
4055
  label: "Kling 2.5 Turbo Pro (KIE)",
3736
4056
  description: "Kuaishou Kling 2.5 Turbo Pro via KIE. A start frame and an optional end frame, 5 or 10 s, fixed size. The fal route accepted minted characters; this KIE route is not yet tested with a minted character. $0.21 for 5 s, $0.42 for 10 s (fal: $0.35 for 5 s).",
3737
4057
  cost: { usd: 0.21 },
4058
+ listPrice: { source: "fal", model: "fal/kling-v2-5-image-to-video" },
3738
4059
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3739
4060
  },
3740
4061
  {
@@ -3745,6 +4066,7 @@ var MODEL_CATALOG2 = [
3745
4066
  label: "Kling 2.6 (KIE)",
3746
4067
  description: "Kuaishou Kling 2.6 via KIE. A start frame, 5 or 10 s, fixed size, silent unless the job sets generateAudio. Not yet tested with a minted character. $0.275 for 5 s silent, $0.55 with sound, 10 s double (fal: $0.35 or $0.70).",
3747
4068
  cost: { usd: 0.275 },
4069
+ listPrice: { source: "fal", model: "fal/kling-2-6-image-to-video" },
3748
4070
  capabilities: { needsImageInput: true, maxInputImages: 1 }
3749
4071
  },
3750
4072
  {
@@ -3756,6 +4078,7 @@ var MODEL_CATALOG2 = [
3756
4078
  refusesPhotorealPeople: true,
3757
4079
  description: "ByteDance Seedance 2.0 via KIE. A start frame and an optional end frame, 4 to 15 s, 480p to 4K. Seedance 2.0 refuses photoreal people, generated ones included: on fal it refused them in every mode (observed 2026-09-23). The refusal comes from the moderation of the model itself, so this route carries the same caution; the KIE route itself has not been run with a person. $0.095 per second at 480p, $0.205 at 720p, $0.51 at 1080p, $1.04 at 4K (fal: about $0.30 at 720p).",
3758
4080
  cost: { usd: 1.025 },
4081
+ listPrice: { source: "fal", model: "fal/seedance-2-0-image-to-video" },
3759
4082
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3760
4083
  },
3761
4084
  {
@@ -3767,6 +4090,7 @@ var MODEL_CATALOG2 = [
3767
4090
  refusesPhotorealPeople: true,
3768
4091
  description: "ByteDance Seedance 2.5 via KIE. A start frame and an optional end frame, 4 to 30 s, 480p to 1080p. Seedance 2.5 refuses photoreal people, generated ones included: on fal it refused them in every mode (observed 2026-08-21 and 2026-09-23). The refusal comes from the moderation of the model itself, so this route carries the same caution; the KIE route itself has not been run with a person. $0.14 per second at 480p, $0.315 at 720p, $0.79 at 1080p.",
3769
4092
  cost: { usd: 1.575 },
4093
+ listPrice: { source: "fal", model: "fal/seedance-2-5-image-to-video" },
3770
4094
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3771
4095
  },
3772
4096
  {
@@ -3777,6 +4101,7 @@ var MODEL_CATALOG2 = [
3777
4101
  label: "Wan 3.0 (KIE)",
3778
4102
  description: "Alibaba Wan 3.0 via KIE (hosted only, no downloadable weights). A start frame and an optional end frame, 2 to 30 s, 480p, 720p or 1080p, audio included. Accepted a minted character's photoreal stills on 2026-09-24. $0.04 per second at 480p, $0.08 at 720p, $0.16 at 1080p (fal and Alibaba: $0.10 at 720p).",
3779
4103
  cost: { usd: 0.4 },
4104
+ listPrice: { source: "fal", model: "fal/wan-3-0-image-to-video" },
3780
4105
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3781
4106
  },
3782
4107
  {
@@ -3787,6 +4112,7 @@ var MODEL_CATALOG2 = [
3787
4112
  label: "MiniMax H3 (KIE)",
3788
4113
  description: "MiniMax H3 via KIE. A start frame and an optional end frame, 4 to 15 s, 768p (the 720p setting) or 2K (the 1080p setting). Accepted a minted character's photoreal stills on 2026-09-24. Its open weights exclude the EU by license; this is KIE's hosted service. $0.04 per second at 768p, $0.065 at 2K.",
3789
4114
  cost: { usd: 0.24 },
4115
+ listPrice: { source: "unrecorded" },
3790
4116
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3791
4117
  },
3792
4118
  {
@@ -3797,6 +4123,7 @@ var MODEL_CATALOG2 = [
3797
4123
  label: "Gemini Omni 1.1 Flash (KIE)",
3798
4124
  description: "Google Gemini Omni 1.1 Flash via KIE, first-and-last-frame mode. A start frame and an optional end frame, 4, 6, 8 or 10 s, 720p to 4K, 16:9 or 9:16. KIE refuses registered characters in this mode, so identity comes from the frames alone. Accepted a minted character's photoreal stills on 2026-09-24 (set 9:16 for a portrait still: it defaults to 16:9 and crops). $0.315 for 4 s up to $0.63 for 10 s at 720p or 1080p, $0.735 to $1.05 at 4K. No fal equivalent.",
3799
4125
  cost: { usd: 0.525 },
4126
+ listPrice: { source: "unrecorded" },
3800
4127
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3801
4128
  },
3802
4129
  {
@@ -3807,6 +4134,7 @@ var MODEL_CATALOG2 = [
3807
4134
  label: "Kling O3 (KIE)",
3808
4135
  description: "Kuaishou Kling O3 (KIE calls it Kling 3.0 Omni) via KIE. A start frame and an optional end frame, 3 to 15 s, 720p to 4K in the start frame's shape, silent unless the job sets generateAudio. Accepted a minted character's photoreal stills on 2026-09-24. $0.07 per second at 720p, $0.09 at 1080p, $0.335 at 4K; with sound $0.10 and $0.135.",
3809
4136
  cost: { usd: 0.35 },
4137
+ listPrice: { source: "unrecorded" },
3810
4138
  capabilities: { needsImageInput: true, maxInputImages: 1, acceptsEndFrame: true }
3811
4139
  },
3812
4140
  {
@@ -4125,6 +4453,7 @@ var MODEL_CATALOG2 = [
4125
4453
  label: "Suno V5 (KIE)",
4126
4454
  description: "Suno V5 music generation via KIE. Text-to-music with or without lyrics; ~4 min cap per generation. Default text-to-audio. No IP indemnity (KIE aggregator). KIE's site now features Suno v5.5 and no longer lists a V5 price (checked 2026-09-27), so the $0.04 is an unverified hint.",
4127
4455
  cost: { usd: 0.04 },
4456
+ listPrice: { source: "unrecorded" },
4128
4457
  isDefault: true
4129
4458
  },
4130
4459
  // ── audio: voice via ElevenLabs (B2) ───────────────────────────────────
@@ -4173,6 +4502,7 @@ var MODEL_CATALOG2 = [
4173
4502
  label: "Claude Sonnet 4.5 (KIE)",
4174
4503
  description: "Anthropic Claude Sonnet 4.5 via KIE (Anthropic Messages API at a lower rate). Default LLM director: prompt improvement + shot descriptions. Cost billed per token; catalog usd is a hint.",
4175
4504
  cost: { usd: 0.01 },
4505
+ listPrice: { source: "unrecorded" },
4176
4506
  isDefault: true
4177
4507
  },
4178
4508
  {
@@ -4182,7 +4512,8 @@ var MODEL_CATALOG2 = [
4182
4512
  task: "text-generation",
4183
4513
  label: "Claude Opus 4.5 (KIE)",
4184
4514
  description: "Anthropic Claude Opus 4.5 via KIE. Higher quality, higher cost; use for harder director reasoning. Cost billed per token; catalog usd is a hint.",
4185
- cost: { usd: 0.05 }
4515
+ cost: { usd: 0.05 },
4516
+ listPrice: { source: "unrecorded" }
4186
4517
  },
4187
4518
  {
4188
4519
  id: "openrouter/claude-sonnet-4-6",
@@ -4247,8 +4578,26 @@ var MODEL_CATALOG2 = [
4247
4578
  description: "Vertex AI direct, newest flash variant. Global region. Preview status."
4248
4579
  }
4249
4580
  ];
4581
+ var routeEntries2 = /* @__PURE__ */ new Map();
4582
+ function routeEntry2(id) {
4583
+ const cached = routeEntries2.get(id);
4584
+ if (cached) return cached;
4585
+ const route = getRoute2(id);
4586
+ const primary = route ? MODEL_CATALOG2.find((m) => m.id === route.primary) : void 0;
4587
+ if (!route || !primary) return void 0;
4588
+ const entry = {
4589
+ ...primary,
4590
+ // A route is never the default of its task (that is slice 3's switch).
4591
+ isDefault: void 0,
4592
+ id: route.id,
4593
+ label: route.label,
4594
+ description: `Runs on KIE first and, when KIE fails on its side, again on fal with the same settings. ${primary.description ?? ""}`.trim()
4595
+ };
4596
+ routeEntries2.set(id, entry);
4597
+ return entry;
4598
+ }
4250
4599
  function tryGetModel2(id) {
4251
- return MODEL_CATALOG2.find((m) => m.id === id);
4600
+ return MODEL_CATALOG2.find((m) => m.id === id) ?? routeEntry2(id);
4252
4601
  }
4253
4602
  function supportedResolutions(model) {
4254
4603
  const declared = model?.capabilities?.resolutions;
@@ -5895,7 +6244,7 @@ function nodeUrl(res, nodeId) {
5895
6244
  }
5896
6245
  var studioListProjects = defineTool({
5897
6246
  name: "studio_list_projects",
5898
- description: 'List the Studio projects visible to this API key (id, slug, name). A projectId is required by studio_run_workflow, studio_mint_character and studio_replace_character_in_video; call this first to find it (the usual one is slug "default").',
6247
+ description: 'List the Studio projects visible to this API key (id, slug, name). A projectId is required by studio_run_workflow, studio_run_published, studio_mint_character, studio_rig_3d and studio_replace_character_in_video; call this first to find it (the usual one is slug "default"). It is optional on studio_generate_image and studio_generate_3d, where it chooses the project the result is filed in.',
5899
6248
  inputSchema: {},
5900
6249
  annotations: { readOnlyHint: true },
5901
6250
  handler: async (_args, ctx) => {
@@ -5905,17 +6254,26 @@ var studioListProjects = defineTool({
5905
6254
  });
5906
6255
  var studioGenerateImage = defineTool({
5907
6256
  name: "studio_generate_image",
5908
- description: "Generate one image with a brand's style applied (the Studio injects the brand's style prefix and reference images server-side). Optionally pin a minted character so its identity references are injected too. Submits a job and, when wait=true (default), polls until it finishes and returns the signed image URL and cost in USD. With wait=false it returns the jobId immediately for studio_get_job. Typical cost ~$0.04 per image.",
6257
+ description: 'Generate an image from a prompt. A brand is optional: pass brandSlug to have the Studio apply that brand\'s style prefix and reference images server-side, or leave it out for an image without a brand. Optionally pin a character (characterSlug) so its identity references are injected too. A brandSlug or characterSlug that names nothing your company can use is refused before anything is spent (404, code input_not_found), and the error text says what to do next: act on it or relay it as written. The image is filed in projectId when given, otherwise in the project named after the brand (created on first use), otherwise in the project "default". Submits a job and, when wait=true (default), polls until it finishes and returns the signed image URL and costUsd (the provider cost of the job; what a company on prepaid credits pays for it is at least that cost times its rate, see studio_get_usage). With wait=false it returns the jobId immediately for studio_get_job. candidateCount asks for up to four candidates, but some models render one image regardless of the count: the result carries `candidates: { requested, rendered, mayRender? }`, and a `notice` to pass on to the user whenever fewer images will be made than were asked for. Typical cost ~$0.04 per image.',
5909
6258
  inputSchema: {
5910
- brandSlug: slug.describe('Brand to apply (e.g. "lola-stories"). Required by the API.'),
5911
- brandMode: z8.string().optional().describe('Optional brand mode (e.g. "illustration"); modes are defined per brand.'),
5912
- prompt: z8.string().min(1).describe("What to generate. The brand style prefix is prepended server-side."),
5913
- characterSlug: slug.optional().describe("Optional minted character whose identity references are injected."),
6259
+ brandSlug: slug.optional().describe(
6260
+ "Optional. The slug of a brand of your company (create one with studio_create_brand). Omit it to generate without a brand. A slug that names no brand of your company is refused with 404 input_not_found; it is never used as a project name."
6261
+ ),
6262
+ brandMode: z8.string().optional().describe('Optional brand mode (e.g. "illustration"); modes are defined per brand. Only meaningful with brandSlug.'),
6263
+ prompt: z8.string().min(1).describe("What to generate. With brandSlug, the brand style prefix is prepended server-side."),
6264
+ characterSlug: slug.optional().describe(
6265
+ "Optional character whose identity references are injected: one of your company's, or one another company shared (see studio_list_characters). A slug your company cannot use is refused with 404 input_not_found."
6266
+ ),
6267
+ projectId: z8.string().min(1).optional().describe(
6268
+ 'Optional project to file the image under (an id from studio_list_projects; it must be a project of your company, anything else is a 404). Omit it to use the project named after brandSlug, or "default" when there is no brand.'
6269
+ ),
5914
6270
  model: z8.string().optional().describe('Image model id (e.g. "kie/nano-banana-2", "fal/nano-banana-2"). Omit for the Studio default.'),
5915
6271
  aspectRatio: z8.string().optional().describe('Aspect ratio such as "1:1", "16:9", "3:4".'),
5916
6272
  imageSize: z8.string().optional().describe('Provider size hint, e.g. "1K", "2K".'),
5917
6273
  seed: z8.number().int().optional(),
5918
- candidateCount: z8.number().int().min(1).max(4).optional().describe("Number of candidates (1 to 4)."),
6274
+ candidateCount: z8.number().int().min(1).max(4).optional().describe(
6275
+ "Number of candidates (1 to 4). Not every model takes a count: most KIE image models render one image whatever is asked, and a `route/...` model runs on KIE first, so expect one there too (its fal fallback may render up to four). The result carries `candidates: { requested, rendered, mayRender? }`, plus a `notice` whenever fewer will be rendered than were asked for; only rendered images are charged."
6276
+ ),
5919
6277
  wait: z8.boolean().default(true).describe("Poll until the job finishes (default true)."),
5920
6278
  waitSeconds
5921
6279
  },
@@ -5927,19 +6285,24 @@ var studioGenerateImage = defineTool({
5927
6285
  if (args.seed !== void 0) settings.seed = args.seed;
5928
6286
  if (args.candidateCount !== void 0) settings.candidateCount = args.candidateCount;
5929
6287
  const submitted = await ctx.client.generate({
5930
- brandSlug: args.brandSlug,
6288
+ ...args.brandSlug ? { brandSlug: args.brandSlug } : {},
5931
6289
  ...args.brandMode ? { brandMode: args.brandMode } : {},
5932
6290
  prompt: args.prompt,
5933
6291
  ...args.characterSlug ? { characterSlug: args.characterSlug } : {},
6292
+ ...args.projectId ? { projectId: args.projectId } : {},
5934
6293
  ...Object.keys(settings).length > 0 ? { settings } : {}
5935
6294
  });
5936
- if (!args.wait) return { jobId: submitted.jobId, kind: submitted.kind, status: "queued" };
6295
+ const plan = {
6296
+ ...submitted.candidates ? { candidates: submitted.candidates } : {},
6297
+ ...typeof submitted.notice === "string" && submitted.notice.length > 0 ? { notice: submitted.notice } : {}
6298
+ };
6299
+ if (!args.wait) return { jobId: submitted.jobId, kind: submitted.kind, status: "queued", ...plan };
5937
6300
  const done = await waitForJob(ctx.client, submitted.jobId, pollOpts(ctx, args.waitSeconds));
5938
6301
  const out = projectJob(done);
5939
6302
  if (done.job.status !== "succeeded") {
5940
6303
  throw new Error(`Job ${submitted.jobId} ${done.job.status}: ${done.job.errorMessage ?? "no error message from the API"}`);
5941
6304
  }
5942
- return { ...out, kind: submitted.kind };
6305
+ return { ...out, kind: submitted.kind, ...plan };
5943
6306
  }
5944
6307
  });
5945
6308
  var GENERATE_3D_TASKS = /* @__PURE__ */ new Set(["text-to-3d", "image-to-3d"]);
@@ -6085,7 +6448,7 @@ var studioGetJob = defineTool({
6085
6448
  });
6086
6449
  var studioListCharacters = defineTool({
6087
6450
  name: "studio_list_characters",
6088
- description: "List all minted characters (slug, name, descriptor prompt, default model). Use a slug with studio_get_character for its reference images, or with studio_generate_image to render that character.",
6451
+ description: "List the characters this API key can use (slug, name, descriptor prompt, default model, shared, owned): the ones its company minted, plus the ones another company shared with everyone (owned: false, usable in generations but read-only). A slug is unique within one company; when your company and a shared character use the same slug, yours is the one listed and used. Use a slug with studio_get_character for its reference images, or with studio_generate_image to render that character.",
6089
6452
  inputSchema: {},
6090
6453
  annotations: { readOnlyHint: true },
6091
6454
  handler: async (_args, ctx) => {
@@ -6095,7 +6458,7 @@ var studioListCharacters = defineTool({
6095
6458
  });
6096
6459
  var studioGetCharacter = defineTool({
6097
6460
  name: "studio_get_character",
6098
- description: "Fetch one character by slug with its full context: descriptor and negative prompts plus every frozen reference image (category such as face, three-quarter, profile, full-body, close-up, lighting-warm, lighting-cool; freshly signed URL). The full-body or face reference URL is what studio_replace_character_in_video needs as characterImageUrl.",
6461
+ description: "Fetch one character by slug with its full context: descriptor and negative prompts plus every frozen reference image (category such as face, three-quarter, profile, full-body, close-up, lighting-warm, lighting-cool; URL). The full-body or face reference URL is what studio_replace_character_in_video needs as characterImageUrl. For a character your company owns the URL is a direct image URL. For a character another company shared (owned: false) it is a Studio address: pass it unchanged as an image input to any Studio tool and the Studio resolves it when the job runs, but it is not a public image URL, and it stops working the moment the owner stops sharing.",
6099
6462
  inputSchema: { slug },
6100
6463
  annotations: { readOnlyHint: true },
6101
6464
  handler: async (args, ctx) => {
@@ -6110,7 +6473,7 @@ function assertLikenessRightsConfirmed(value) {
6110
6473
  }
6111
6474
  var studioCreateCharacter = defineTool({
6112
6475
  name: "studio_create_character",
6113
- description: "Create an empty character record (no reference images yet). Prefer studio_mint_character, which generates the 7-view identity sheet and freezes it as references in one call. Use this only when you will upload your own reference images. Requires likenessRightsConfirmed: true, which you may only pass after the human you act for confirmed it for this character. Fails with 409 if the slug exists.",
6476
+ description: "Create an empty character record (no reference images yet). Prefer studio_mint_character, which generates the 7-view identity sheet and freezes it as references in one call. Use this only when you will upload your own reference images. Requires likenessRightsConfirmed: true, which you may only pass after the human you act for confirmed it for this character. Fails with 409 (code slug_taken) only if your own company already has a character with this slug; a slug another company uses is free to take.",
6114
6477
  inputSchema: {
6115
6478
  name: z8.string().min(1).describe('Display name, e.g. "Bruno Hartmann".'),
6116
6479
  slug,
@@ -6202,7 +6565,7 @@ var studioMintCharacter = defineTool({
6202
6565
  });
6203
6566
  var studioCreateBrand = defineTool({
6204
6567
  name: "studio_create_brand",
6205
- description: "Create a brand (a style preset: name, slug, optional style prefix prepended to every prompt, optional free-text context). Upload reference images afterwards with studio_upload_brand_reference. Fails with 409 if the slug exists.",
6568
+ description: "Create a brand (a style preset: name, slug, optional style prefix prepended to every prompt, optional free-text context). Upload reference images afterwards with studio_upload_brand_reference. Fails with 409 (code slug_taken) only if your own company already has a brand with this slug; a slug another company uses is free to take.",
6206
6569
  inputSchema: {
6207
6570
  name: z8.string().min(1),
6208
6571
  slug,
@@ -6227,7 +6590,7 @@ var studioUploadBrandReference = defineTool({
6227
6590
  name: "studio_upload_brand_reference",
6228
6591
  description: `Upload one reference image to a brand's library from a local file path or an http(s) URL. The Studio converts it to a 512px WebP and injects it into generations under that brand (up to the brand's maxReferenceImages). Category groups references (default "general"); tags and a 1 to 10 quality score are optional.`,
6229
6592
  inputSchema: {
6230
- brandSlug: slug,
6593
+ brandSlug: slug.describe("The slug of a brand of your company (from studio_create_brand). A slug that names no brand of your company is a 404, and nothing is uploaded."),
6231
6594
  source: z8.string().min(1).describe("Local file path or http(s) URL of an image."),
6232
6595
  category: z8.string().optional().describe('Library category, default "general".'),
6233
6596
  label: z8.string().optional().describe("Optional file name / label."),
@@ -6349,7 +6712,9 @@ var studioReplaceCharacterInVideo = defineTool({
6349
6712
  inputSchema: {
6350
6713
  projectId: z8.string().min(1).describe("Project id from studio_list_projects."),
6351
6714
  sceneFrameUrl: httpUrl.describe("Public or signed URL of ONE frame of the target shot (png/jpg)."),
6352
- characterSlug: slug.optional().describe("Minted character to insert; its full-body reference (fallback three-quarter, face) is resolved automatically."),
6715
+ characterSlug: slug.optional().describe(
6716
+ "Character to insert: one of your company's, or one another company shared. Its full-body reference (fallback three-quarter, face) is resolved automatically. A slug your company cannot use is a 404 before the run starts, so nothing is spent."
6717
+ ),
6353
6718
  characterReferenceUrl: httpUrl.optional().describe("Explicit character reference image URL (use instead of characterSlug)."),
6354
6719
  endFrameUrl: httpUrl.optional().describe("Optional second approved keyframe for the END of the clip. Switches the video model to Kling (start + end frame) unless videoModel is set explicitly."),
6355
6720
  scope: z8.enum(SWAP_SCOPES).optional().describe("How much of the person to replace. identity (default): face, head and hair from the character, the footage keeps its body. full_body: also the character body proportions; the silhouette changes, so the background behind the old body and every contact point must be re-derived."),
@@ -6422,7 +6787,7 @@ var studioReplaceCharacterInVideo = defineTool({
6422
6787
  });
6423
6788
  var studioGetSpend = defineTool({
6424
6789
  name: "studio_get_spend",
6425
- description: `Read the spend ledger: total USD and job counts for today, the last 7 days and the last 30 days, plus the most recent paid actions. tzOffsetMinutes is the viewer's Date.getTimezoneOffset() so "today" rolls at local midnight (default UTC).`,
6790
+ description: "Read the spend ledger: total USD and job counts for today, the last 7 days and the last 30 days, plus the most recent paid actions. The amounts are provider cost (each job's costUsd); for a company on prepaid credits, what left its balance is in studio_get_usage (`credits`). tzOffsetMinutes is the viewer's Date.getTimezoneOffset() so \"today\" rolls at local midnight (default UTC).",
6426
6791
  inputSchema: {
6427
6792
  tzOffsetMinutes: z8.number().int().min(-840).max(840).optional()
6428
6793
  },
@@ -6434,7 +6799,7 @@ var studioGetSpend = defineTool({
6434
6799
  });
6435
6800
  var studioGetUsage = defineTool({
6436
6801
  name: "studio_get_usage",
6437
- description: `Read this key's tenant against its plan (docs/plans/2026-08-21-studio-api-keys-and-quotas.md): month-to-date USD spend and job count, the plan's monthly cap and requests-per-minute limit (null = unlimited), and a per-model breakdown, most expensive first. A company with no plan of its own is held to the default monthly cap (capSource "default", 20 USD unless the operator configured otherwise); only a plan the Lumitra operator set can be unlimited. Call this before a large batch to check remaining budget, or after a 402/429 to see what tripped; a 402 means the monthly limit is reached and only the Lumitra operator can raise it before it resets. Every /api/generate, /api/v1/workflows/run and /api/try-on call carries this key's tenant. usedUsd includes in-flight (not yet settled) job estimates, so it can go DOWN as well as up: a job that later fails releases its estimate.`,
6802
+ description: "Read what limits the company this key belongs to, and what it has used: month-to-date spend (usedUsd, realizedUsd, inFlightEstimatedUsd) and job count at provider cost, the monthly cap (capUsd) and requests-per-minute limit (rpm), null meaning none, and a per-model breakdown, most expensive first. A company on prepaid credits (every new company; a company the Studio already knew keeps the caps it had) has no monthly cap unless the Lumitra operator set one; `credits` says what limits it instead: `balanceUsd` (the prepaid balance), `dailyLimitUsd` (null: none), `usedTodayUsd` (what left the balance today, running jobs included) and `dailyResetsAt` (midnight UTC). Each finished generation is debited from the balance at provider cost times the company's rate (1.50 on pay as you go), or 80 percent of the model's list price where that is higher, so the credit figures are higher than the provider-cost figures beside them. balanceUsd does not yet subtract what is reserved for requests that are still starting or running, so the room for a new request can be smaller than balanceUsd. A company not on credits has `credits` null and is held to its monthly cap (20 USD by default, capSource \"default\", unless the Lumitra operator set a plan). Call this before a large batch to check remaining room, or after a 402 or 429 to see what tripped. The refusal's own error text says which limit it was and what to do, so relay it as written; it also names the code: `credits_exhausted` (the balance does not cover the request: add credits on the Billing page, or wait a few seconds when part of the balance is only reserved), `daily_cap_reached` (on credits a company owner or admin raises the daily limit on the Billing page; it resets at midnight UTC), `monthly_cap_reached` (only the Lumitra operator can raise it) or `velocity_limit_reached` (too many paid requests in a short time: wait as long as the text says). usedUsd includes in-flight (not yet settled) job estimates, so it can go DOWN as well as up: a job that later fails releases its estimate.",
6438
6803
  inputSchema: {},
6439
6804
  annotations: { readOnlyHint: true },
6440
6805
  handler: async (_args, ctx) => {
@@ -6554,7 +6919,7 @@ function registerTools(server, ctx) {
6554
6919
 
6555
6920
  // src/server.ts
6556
6921
  var SERVER_NAME = "lumitra-studio";
6557
- var SERVER_VERSION = "0.4.0";
6922
+ var SERVER_VERSION = "0.5.0";
6558
6923
  function createStudioMcpServer(opts = {}) {
6559
6924
  const config = opts.config ?? resolveConfig(opts.env ?? process.env);
6560
6925
  const client = new StudioClient(config, {
@@ -6566,7 +6931,7 @@ function createStudioMcpServer(opts = {}) {
6566
6931
  {
6567
6932
  instructions: [
6568
6933
  "Lumitra Studio: brand-consistent image generation, character minting, workflow runs and the still-swap video recipe over the hosted Studio API.",
6569
- "Start with studio_list_projects to get a projectId. Every generation costs real money (roughly $0.04 per image, $0.30 per character mint, $0.70 per video take); check studio_get_spend when in doubt.",
6934
+ "Start with studio_list_projects to get a projectId. Every generation costs real money (roughly $0.04 per image, $0.30 per character mint, $0.70 per video take); check studio_get_spend when in doubt, and studio_get_usage for what limits the company (a prepaid credit balance with a daily limit, or a monthly cap). A refusal carries the Studio's own sentence with the next step in it: act on it or relay it as written.",
6570
6935
  "Long jobs: tools wait by default (up to timeoutSeconds, 600 by default) and return ids you can poll with studio_get_job / studio_get_run if they time out.",
6571
6936
  "Authoring a workflow is free and revisioned: studio_workflow_describe for the vocabulary, studio_workflow_create to build one from ops, studio_workflow_apply to change it on top of a baseRevision (a stale_revision result means re-read, rebase, retry), studio_workflow_validate to dry-run before spending, then studio_run_workflow with its savedId.",
6572
6937
  "Workflow hygiene: one deliverable is ONE saved workflow with a node per item, and a refinement of an item (edit, upscale, variation) is a NEW NODE appended to that workflow with studio_workflow_apply (addNode plus a connect from the node it refines, so the earlier generation is its input), then studio_run_workflow with savedId and nodeIds limited to the new node. Never send a fresh inline definition for a follow-up: every listed inline run is saved as its own workflow, which scatters one deliverable across duplicates."